Implement mod communication channels (#6351)
[oweals/minetest.git] / src / network / clientpackethandler.cpp
1 /*
2 Minetest
3 Copyright (C) 2015 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
4
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.
9
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.
14
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.
18 */
19
20 #include "client.h"
21
22 #include "util/base64.h"
23 #include "chatmessage.h"
24 #include "clientmedia.h"
25 #include "log.h"
26 #include "map.h"
27 #include "mapsector.h"
28 #include "minimap.h"
29 #include "modchannels.h"
30 #include "nodedef.h"
31 #include "serialization.h"
32 #include "server.h"
33 #include "util/strfnd.h"
34 #include "client/clientevent.h"
35 #include "network/clientopcodes.h"
36 #include "network/connection.h"
37 #include "script/scripting_client.h"
38 #include "util/serialize.h"
39 #include "util/srp.h"
40 #include "tileanimation.h"
41 #include "gettext.h"
42
43 void Client::handleCommand_Deprecated(NetworkPacket* pkt)
44 {
45         infostream << "Got deprecated command "
46                         << toClientCommandTable[pkt->getCommand()].name << " from peer "
47                         << pkt->getPeerId() << "!" << std::endl;
48 }
49
50 void Client::handleCommand_Hello(NetworkPacket* pkt)
51 {
52         if (pkt->getSize() < 1)
53                 return;
54
55         u8 serialization_ver;
56         u16 proto_ver;
57         u16 compression_mode;
58         u32 auth_mechs;
59         std::string username_legacy; // for case insensitivity
60         *pkt >> serialization_ver >> compression_mode >> proto_ver
61                 >> auth_mechs >> username_legacy;
62
63         // Chose an auth method we support
64         AuthMechanism chosen_auth_mechanism = choseAuthMech(auth_mechs);
65
66         infostream << "Client: TOCLIENT_HELLO received with "
67                         << "serialization_ver=" << (u32)serialization_ver
68                         << ", auth_mechs=" << auth_mechs
69                         << ", proto_ver=" << proto_ver
70                         << ", compression_mode=" << compression_mode
71                         << ". Doing auth with mech " << chosen_auth_mechanism << std::endl;
72
73         if (!ser_ver_supported(serialization_ver)) {
74                 infostream << "Client: TOCLIENT_HELLO: Server sent "
75                                 << "unsupported ser_fmt_ver"<< std::endl;
76                 return;
77         }
78
79         m_server_ser_ver = serialization_ver;
80         m_proto_ver = proto_ver;
81
82         //TODO verify that username_legacy matches sent username, only
83         // differs in casing (make both uppercase and compare)
84         // This is only neccessary though when we actually want to add casing support
85
86         if (m_chosen_auth_mech != AUTH_MECHANISM_NONE) {
87                 // we recieved a TOCLIENT_HELLO while auth was already going on
88                 errorstream << "Client: TOCLIENT_HELLO while auth was already going on"
89                         << "(chosen_mech=" << m_chosen_auth_mech << ")." << std::endl;
90                 if (m_chosen_auth_mech == AUTH_MECHANISM_SRP ||
91                                 m_chosen_auth_mech == AUTH_MECHANISM_LEGACY_PASSWORD) {
92                         srp_user_delete((SRPUser *) m_auth_data);
93                         m_auth_data = 0;
94                 }
95         }
96
97         // Authenticate using that method, or abort if there wasn't any method found
98         if (chosen_auth_mechanism != AUTH_MECHANISM_NONE) {
99                 startAuth(chosen_auth_mechanism);
100         } else {
101                 m_chosen_auth_mech = AUTH_MECHANISM_NONE;
102                 m_access_denied = true;
103                 m_access_denied_reason = "Unknown";
104                 m_con->Disconnect();
105         }
106
107 }
108
109 void Client::handleCommand_AuthAccept(NetworkPacket* pkt)
110 {
111         deleteAuthData();
112
113         v3f playerpos;
114         *pkt >> playerpos >> m_map_seed >> m_recommended_send_interval
115                 >> m_sudo_auth_methods;
116
117         playerpos -= v3f(0, BS / 2, 0);
118
119         // Set player position
120         LocalPlayer *player = m_env.getLocalPlayer();
121         assert(player != NULL);
122         player->setPosition(playerpos);
123
124         infostream << "Client: received map seed: " << m_map_seed << std::endl;
125         infostream << "Client: received recommended send interval "
126                                         << m_recommended_send_interval<<std::endl;
127
128         // Reply to server
129         std::string lang = gettext("LANG_CODE");
130         if (lang == "LANG_CODE")
131                 lang = "";
132
133         NetworkPacket resp_pkt(TOSERVER_INIT2, sizeof(u16) + lang.size());
134         resp_pkt << lang;
135         Send(&resp_pkt);
136
137         m_state = LC_Init;
138 }
139 void Client::handleCommand_AcceptSudoMode(NetworkPacket* pkt)
140 {
141         deleteAuthData();
142
143         m_password = m_new_password;
144
145         verbosestream << "Client: Recieved TOCLIENT_ACCEPT_SUDO_MODE." << std::endl;
146
147         // send packet to actually set the password
148         startAuth(AUTH_MECHANISM_FIRST_SRP);
149
150         // reset again
151         m_chosen_auth_mech = AUTH_MECHANISM_NONE;
152 }
153 void Client::handleCommand_DenySudoMode(NetworkPacket* pkt)
154 {
155         ChatMessage *chatMessage = new ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
156                         L"Password change denied. Password NOT changed.");
157         pushToChatQueue(chatMessage);
158         // reset everything and be sad
159         deleteAuthData();
160 }
161
162 void Client::handleCommand_AccessDenied(NetworkPacket* pkt)
163 {
164         // The server didn't like our password. Note, this needs
165         // to be processed even if the serialisation format has
166         // not been agreed yet, the same as TOCLIENT_INIT.
167         m_access_denied = true;
168         m_access_denied_reason = "Unknown";
169
170         if (pkt->getCommand() != TOCLIENT_ACCESS_DENIED) {
171                 // 13/03/15 Legacy code from 0.4.12 and lesser but is still used
172                 // in some places of the server code
173                 if (pkt->getSize() >= 2) {
174                         std::wstring wide_reason;
175                         *pkt >> wide_reason;
176                         m_access_denied_reason = wide_to_utf8(wide_reason);
177                 }
178                 return;
179         }
180
181         if (pkt->getSize() < 1)
182                 return;
183
184         u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA;
185         *pkt >> denyCode;
186         if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN ||
187                         denyCode == SERVER_ACCESSDENIED_CRASH) {
188                 *pkt >> m_access_denied_reason;
189                 if (m_access_denied_reason.empty()) {
190                         m_access_denied_reason = accessDeniedStrings[denyCode];
191                 }
192                 u8 reconnect;
193                 *pkt >> reconnect;
194                 m_access_denied_reconnect = reconnect & 1;
195         } else if (denyCode == SERVER_ACCESSDENIED_CUSTOM_STRING) {
196                 *pkt >> m_access_denied_reason;
197         } else if (denyCode < SERVER_ACCESSDENIED_MAX) {
198                 m_access_denied_reason = accessDeniedStrings[denyCode];
199         } else {
200                 // Allow us to add new error messages to the
201                 // protocol without raising the protocol version, if we want to.
202                 // Until then (which may be never), this is outside
203                 // of the defined protocol.
204                 *pkt >> m_access_denied_reason;
205                 if (m_access_denied_reason.empty()) {
206                         m_access_denied_reason = "Unknown";
207                 }
208         }
209 }
210
211 void Client::handleCommand_RemoveNode(NetworkPacket* pkt)
212 {
213         if (pkt->getSize() < 6)
214                 return;
215
216         v3s16 p;
217         *pkt >> p;
218         removeNode(p);
219 }
220
221 void Client::handleCommand_AddNode(NetworkPacket* pkt)
222 {
223         if (pkt->getSize() < 6 + MapNode::serializedLength(m_server_ser_ver))
224                 return;
225
226         v3s16 p;
227         *pkt >> p;
228
229         MapNode n;
230         n.deSerialize(pkt->getU8Ptr(6), m_server_ser_ver);
231
232         bool remove_metadata = true;
233         u32 index = 6 + MapNode::serializedLength(m_server_ser_ver);
234         if ((pkt->getSize() >= index + 1) && pkt->getU8(index)) {
235                 remove_metadata = false;
236         }
237
238         addNode(p, n, remove_metadata);
239 }
240 void Client::handleCommand_BlockData(NetworkPacket* pkt)
241 {
242         // Ignore too small packet
243         if (pkt->getSize() < 6)
244                 return;
245
246         v3s16 p;
247         *pkt >> p;
248
249         std::string datastring(pkt->getString(6), pkt->getSize() - 6);
250         std::istringstream istr(datastring, std::ios_base::binary);
251
252         MapSector *sector;
253         MapBlock *block;
254
255         v2s16 p2d(p.X, p.Z);
256         sector = m_env.getMap().emergeSector(p2d);
257
258         assert(sector->getPos() == p2d);
259
260         block = sector->getBlockNoCreateNoEx(p.Y);
261         if (block) {
262                 /*
263                         Update an existing block
264                 */
265                 block->deSerialize(istr, m_server_ser_ver, false);
266                 block->deSerializeNetworkSpecific(istr);
267         }
268         else {
269                 /*
270                         Create a new block
271                 */
272                 block = new MapBlock(&m_env.getMap(), p, this);
273                 block->deSerialize(istr, m_server_ser_ver, false);
274                 block->deSerializeNetworkSpecific(istr);
275                 sector->insertBlock(block);
276         }
277
278         if (m_localdb) {
279                 ServerMap::saveBlock(block, m_localdb);
280         }
281
282         /*
283                 Add it to mesh update queue and set it to be acknowledged after update.
284         */
285         addUpdateMeshTaskWithEdge(p, true);
286 }
287
288 void Client::handleCommand_Inventory(NetworkPacket* pkt)
289 {
290         if (pkt->getSize() < 1)
291                 return;
292
293         std::string datastring(pkt->getString(0), pkt->getSize());
294         std::istringstream is(datastring, std::ios_base::binary);
295
296         LocalPlayer *player = m_env.getLocalPlayer();
297         assert(player != NULL);
298
299         player->inventory.deSerialize(is);
300
301         m_inventory_updated = true;
302
303         delete m_inventory_from_server;
304         m_inventory_from_server = new Inventory(player->inventory);
305         m_inventory_from_server_age = 0.0;
306 }
307
308 void Client::handleCommand_TimeOfDay(NetworkPacket* pkt)
309 {
310         if (pkt->getSize() < 2)
311                 return;
312
313         u16 time_of_day;
314
315         *pkt >> time_of_day;
316
317         time_of_day      = time_of_day % 24000;
318         float time_speed = 0;
319
320         if (pkt->getSize() >= 2 + 4) {
321                 *pkt >> time_speed;
322         }
323         else {
324                 // Old message; try to approximate speed of time by ourselves
325                 float time_of_day_f = (float)time_of_day / 24000.0f;
326                 float tod_diff_f = 0;
327
328                 if (time_of_day_f < 0.2 && m_last_time_of_day_f > 0.8)
329                         tod_diff_f = time_of_day_f - m_last_time_of_day_f + 1.0f;
330                 else
331                         tod_diff_f = time_of_day_f - m_last_time_of_day_f;
332
333                 m_last_time_of_day_f       = time_of_day_f;
334                 float time_diff            = m_time_of_day_update_timer;
335                 m_time_of_day_update_timer = 0;
336
337                 if (m_time_of_day_set) {
338                         time_speed = (3600.0f * 24.0f) * tod_diff_f / time_diff;
339                         infostream << "Client: Measured time_of_day speed (old format): "
340                                         << time_speed << " tod_diff_f=" << tod_diff_f
341                                         << " time_diff=" << time_diff << std::endl;
342                 }
343         }
344
345         // Update environment
346         m_env.setTimeOfDay(time_of_day);
347         m_env.setTimeOfDaySpeed(time_speed);
348         m_time_of_day_set = true;
349
350         u32 dr = m_env.getDayNightRatio();
351         infostream << "Client: time_of_day=" << time_of_day
352                         << " time_speed=" << time_speed
353                         << " dr=" << dr << std::endl;
354 }
355
356 void Client::handleCommand_ChatMessageOld(NetworkPacket *pkt)
357 {
358         /*
359                 u16 command
360                 u16 length
361                 wstring message
362         */
363         u16 len, read_wchar;
364
365         *pkt >> len;
366
367         std::wstring message;
368         for (u32 i = 0; i < len; i++) {
369                 *pkt >> read_wchar;
370                 message += (wchar_t)read_wchar;
371         }
372
373         // If chat message not consummed by client lua API
374         // @TODO send this to CSM using ChatMessage object
375         if (!moddingEnabled() || !m_script->on_receiving_message(wide_to_utf8(message))) {
376                 pushToChatQueue(new ChatMessage(message));
377         }
378 }
379
380 void Client::handleCommand_ChatMessage(NetworkPacket *pkt)
381 {
382         /*
383                 u8 version
384                 u8 message_type
385                 u16 sendername length
386                 wstring sendername
387                 u16 length
388                 wstring message
389          */
390
391         ChatMessage *chatMessage = new ChatMessage();
392         u8 version, message_type;
393         *pkt >> version >> message_type;
394
395         if (version != 1 || message_type >= CHATMESSAGE_TYPE_MAX) {
396                 delete chatMessage;
397                 return;
398         }
399
400         *pkt >> chatMessage->sender >> chatMessage->message >> chatMessage->timestamp;
401
402         chatMessage->type = (ChatMessageType) message_type;
403
404         // @TODO send this to CSM using ChatMessage object
405         if (!moddingEnabled() || !m_script->on_receiving_message(
406                         wide_to_utf8(chatMessage->message))) {
407                 pushToChatQueue(chatMessage);
408         } else {
409                 // Message was consumed by CSM and should not handled by client, destroying
410                 delete chatMessage;
411         }
412 }
413
414 void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt)
415 {
416         /*
417                 u16 count of removed objects
418                 for all removed objects {
419                         u16 id
420                 }
421                 u16 count of added objects
422                 for all added objects {
423                         u16 id
424                         u8 type
425                         u32 initialization data length
426                         string initialization data
427                 }
428         */
429
430         try {
431                 u8 type;
432                 u16 removed_count, added_count, id;
433
434                 // Read removed objects
435                 *pkt >> removed_count;
436
437                 for (u16 i = 0; i < removed_count; i++) {
438                         *pkt >> id;
439                         m_env.removeActiveObject(id);
440                 }
441
442                 // Read added objects
443                 *pkt >> added_count;
444
445                 for (u16 i = 0; i < added_count; i++) {
446                         *pkt >> id >> type;
447                         m_env.addActiveObject(id, type, pkt->readLongString());
448                 }
449         } catch (PacketError &e) {
450                 infostream << "handleCommand_ActiveObjectRemoveAdd: " << e.what()
451                                 << ". The packet is unreliable, ignoring" << std::endl;
452         }
453 }
454
455 void Client::handleCommand_ActiveObjectMessages(NetworkPacket* pkt)
456 {
457         /*
458                 for all objects
459                 {
460                         u16 id
461                         u16 message length
462                         string message
463                 }
464         */
465         std::string datastring(pkt->getString(0), pkt->getSize());
466         std::istringstream is(datastring, std::ios_base::binary);
467
468         try {
469                 while (is.good()) {
470                         u16 id = readU16(is);
471                         if (!is.good())
472                                 break;
473
474                         std::string message = deSerializeString(is);
475
476                         // Pass on to the environment
477                         m_env.processActiveObjectMessage(id, message);
478                 }
479         } catch (SerializationError &e) {
480                 errorstream << "Client::handleCommand_ActiveObjectMessages: "
481                         << "caught SerializationError: " << e.what() << std::endl;
482         }
483 }
484
485 void Client::handleCommand_Movement(NetworkPacket* pkt)
486 {
487         LocalPlayer *player = m_env.getLocalPlayer();
488         assert(player != NULL);
489
490         float mad, maa, maf, msw, mscr, msf, mscl, msj, lf, lfs, ls, g;
491
492         *pkt >> mad >> maa >> maf >> msw >> mscr >> msf >> mscl >> msj
493                 >> lf >> lfs >> ls >> g;
494
495         player->movement_acceleration_default   = mad * BS;
496         player->movement_acceleration_air       = maa * BS;
497         player->movement_acceleration_fast      = maf * BS;
498         player->movement_speed_walk             = msw * BS;
499         player->movement_speed_crouch           = mscr * BS;
500         player->movement_speed_fast             = msf * BS;
501         player->movement_speed_climb            = mscl * BS;
502         player->movement_speed_jump             = msj * BS;
503         player->movement_liquid_fluidity        = lf * BS;
504         player->movement_liquid_fluidity_smooth = lfs * BS;
505         player->movement_liquid_sink            = ls * BS;
506         player->movement_gravity                = g * BS;
507 }
508
509 void Client::handleCommand_HP(NetworkPacket* pkt)
510 {
511
512         LocalPlayer *player = m_env.getLocalPlayer();
513         assert(player != NULL);
514
515         u16 oldhp   = player->hp;
516
517         u16 hp;
518         *pkt >> hp;
519
520         player->hp = hp;
521
522         if (moddingEnabled()) {
523                 m_script->on_hp_modification(hp);
524         }
525
526         if (hp < oldhp) {
527                 // Add to ClientEvent queue
528                 ClientEvent *event = new ClientEvent();
529                 event->type = CE_PLAYER_DAMAGE;
530                 event->player_damage.amount = oldhp - hp;
531                 m_client_event_queue.push(event);
532         }
533 }
534
535 void Client::handleCommand_Breath(NetworkPacket* pkt)
536 {
537         LocalPlayer *player = m_env.getLocalPlayer();
538         assert(player != NULL);
539
540         u16 breath;
541
542         *pkt >> breath;
543
544         player->setBreath(breath);
545 }
546
547 void Client::handleCommand_MovePlayer(NetworkPacket* pkt)
548 {
549         LocalPlayer *player = m_env.getLocalPlayer();
550         assert(player != NULL);
551
552         v3f pos;
553         f32 pitch, yaw;
554
555         *pkt >> pos >> pitch >> yaw;
556
557         player->setPosition(pos);
558
559         infostream << "Client got TOCLIENT_MOVE_PLAYER"
560                         << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")"
561                         << " pitch=" << pitch
562                         << " yaw=" << yaw
563                         << std::endl;
564
565         /*
566                 Add to ClientEvent queue.
567                 This has to be sent to the main program because otherwise
568                 it would just force the pitch and yaw values to whatever
569                 the camera points to.
570         */
571         ClientEvent *event = new ClientEvent();
572         event->type = CE_PLAYER_FORCE_MOVE;
573         event->player_force_move.pitch = pitch;
574         event->player_force_move.yaw = yaw;
575         m_client_event_queue.push(event);
576
577         // Ignore damage for a few seconds, so that the player doesn't
578         // get damage from falling on ground
579         m_ignore_damage_timer = 3.0;
580 }
581
582 void Client::handleCommand_DeathScreen(NetworkPacket* pkt)
583 {
584         bool set_camera_point_target;
585         v3f camera_point_target;
586
587         *pkt >> set_camera_point_target;
588         *pkt >> camera_point_target;
589
590         ClientEvent *event = new ClientEvent();
591         event->type                                = CE_DEATHSCREEN;
592         event->deathscreen.set_camera_point_target = set_camera_point_target;
593         event->deathscreen.camera_point_target_x   = camera_point_target.X;
594         event->deathscreen.camera_point_target_y   = camera_point_target.Y;
595         event->deathscreen.camera_point_target_z   = camera_point_target.Z;
596         m_client_event_queue.push(event);
597 }
598
599 void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt)
600 {
601         u16 num_files;
602
603         *pkt >> num_files;
604
605         infostream << "Client: Received media announcement: packet size: "
606                         << pkt->getSize() << std::endl;
607
608         if (m_media_downloader == NULL ||
609                         m_media_downloader->isStarted()) {
610                 const char *problem = m_media_downloader ?
611                         "we already saw another announcement" :
612                         "all media has been received already";
613                 errorstream << "Client: Received media announcement but "
614                         << problem << "! "
615                         << " files=" << num_files
616                         << " size=" << pkt->getSize() << std::endl;
617                 return;
618         }
619
620         // Mesh update thread must be stopped while
621         // updating content definitions
622         sanity_check(!m_mesh_update_thread.isRunning());
623
624         for (u16 i = 0; i < num_files; i++) {
625                 std::string name, sha1_base64;
626
627                 *pkt >> name >> sha1_base64;
628
629                 std::string sha1_raw = base64_decode(sha1_base64);
630                 m_media_downloader->addFile(name, sha1_raw);
631         }
632
633         try {
634                 std::string str;
635
636                 *pkt >> str;
637
638                 Strfnd sf(str);
639                 while(!sf.at_end()) {
640                         std::string baseurl = trim(sf.next(","));
641                         if (!baseurl.empty())
642                                 m_media_downloader->addRemoteServer(baseurl);
643                 }
644         }
645         catch(SerializationError& e) {
646                 // not supported by server or turned off
647         }
648
649         m_media_downloader->step(this);
650 }
651
652 void Client::handleCommand_Media(NetworkPacket* pkt)
653 {
654         /*
655                 u16 command
656                 u16 total number of file bunches
657                 u16 index of this bunch
658                 u32 number of files in this bunch
659                 for each file {
660                         u16 length of name
661                         string name
662                         u32 length of data
663                         data
664                 }
665         */
666         u16 num_bunches;
667         u16 bunch_i;
668         u32 num_files;
669
670         *pkt >> num_bunches >> bunch_i >> num_files;
671
672         infostream << "Client: Received files: bunch " << bunch_i << "/"
673                         << num_bunches << " files=" << num_files
674                         << " size=" << pkt->getSize() << std::endl;
675
676         if (num_files == 0)
677                 return;
678
679         if (!m_media_downloader || !m_media_downloader->isStarted()) {
680                 const char *problem = m_media_downloader ?
681                         "media has not been requested" :
682                         "all media has been received already";
683                 errorstream << "Client: Received media but "
684                         << problem << "! "
685                         << " bunch " << bunch_i << "/" << num_bunches
686                         << " files=" << num_files
687                         << " size=" << pkt->getSize() << std::endl;
688                 return;
689         }
690
691         // Mesh update thread must be stopped while
692         // updating content definitions
693         sanity_check(!m_mesh_update_thread.isRunning());
694
695         for (u32 i=0; i < num_files; i++) {
696                 std::string name;
697
698                 *pkt >> name;
699
700                 std::string data = pkt->readLongString();
701
702                 m_media_downloader->conventionalTransferDone(
703                                 name, data, this);
704         }
705 }
706
707 void Client::handleCommand_NodeDef(NetworkPacket* pkt)
708 {
709         infostream << "Client: Received node definitions: packet size: "
710                         << pkt->getSize() << std::endl;
711
712         // Mesh update thread must be stopped while
713         // updating content definitions
714         sanity_check(!m_mesh_update_thread.isRunning());
715
716         // Decompress node definitions
717         std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);
718         std::ostringstream tmp_os;
719         decompressZlib(tmp_is, tmp_os);
720
721         // Deserialize node definitions
722         std::istringstream tmp_is2(tmp_os.str());
723         m_nodedef->deSerialize(tmp_is2);
724         m_nodedef_received = true;
725 }
726
727 void Client::handleCommand_ItemDef(NetworkPacket* pkt)
728 {
729         infostream << "Client: Received item definitions: packet size: "
730                         << pkt->getSize() << std::endl;
731
732         // Mesh update thread must be stopped while
733         // updating content definitions
734         sanity_check(!m_mesh_update_thread.isRunning());
735
736         // Decompress item definitions
737         std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);
738         std::ostringstream tmp_os;
739         decompressZlib(tmp_is, tmp_os);
740
741         // Deserialize node definitions
742         std::istringstream tmp_is2(tmp_os.str());
743         m_itemdef->deSerialize(tmp_is2);
744         m_itemdef_received = true;
745 }
746
747 void Client::handleCommand_PlaySound(NetworkPacket* pkt)
748 {
749         /*
750                 [0] u32 server_id
751                 [4] u16 name length
752                 [6] char name[len]
753                 [ 6 + len] f32 gain
754                 [10 + len] u8 type
755                 [11 + len] (f32 * 3) pos
756                 [23 + len] u16 object_id
757                 [25 + len] bool loop
758                 [26 + len] f32 fade
759                 [30 + len] f32 pitch
760         */
761
762         s32 server_id;
763         std::string name;
764
765         float gain;
766         u8 type; // 0=local, 1=positional, 2=object
767         v3f pos;
768         u16 object_id;
769         bool loop;
770         float fade = 0.0f;
771         float pitch = 1.0f;
772
773         *pkt >> server_id >> name >> gain >> type >> pos >> object_id >> loop;
774
775         try {
776                 *pkt >> fade;
777                 *pkt >> pitch;
778         } catch (PacketError &e) {};
779
780         // Start playing
781         int client_id = -1;
782         switch(type) {
783                 case 0: // local
784                         client_id = m_sound->playSound(name, loop, gain, fade, pitch);
785                         break;
786                 case 1: // positional
787                         client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch);
788                         break;
789                 case 2:
790                 { // object
791                         ClientActiveObject *cao = m_env.getActiveObject(object_id);
792                         if (cao)
793                                 pos = cao->getPosition();
794                         client_id = m_sound->playSoundAt(name, loop, gain, pos, pitch);
795                         // TODO: Set up sound to move with object
796                         break;
797                 }
798                 default:
799                         break;
800         }
801
802         if (client_id != -1) {
803                 m_sounds_server_to_client[server_id] = client_id;
804                 m_sounds_client_to_server[client_id] = server_id;
805                 if (object_id != 0)
806                         m_sounds_to_objects[client_id] = object_id;
807         }
808 }
809
810 void Client::handleCommand_StopSound(NetworkPacket* pkt)
811 {
812         s32 server_id;
813
814         *pkt >> server_id;
815
816         std::unordered_map<s32, int>::iterator i = m_sounds_server_to_client.find(server_id);
817         if (i != m_sounds_server_to_client.end()) {
818                 int client_id = i->second;
819                 m_sound->stopSound(client_id);
820         }
821 }
822
823 void Client::handleCommand_FadeSound(NetworkPacket *pkt)
824 {
825         s32 sound_id;
826         float step;
827         float gain;
828
829         *pkt >> sound_id >> step >> gain;
830
831         std::unordered_map<s32, int>::const_iterator i =
832                         m_sounds_server_to_client.find(sound_id);
833
834         if (i != m_sounds_server_to_client.end())
835                 m_sound->fadeSound(i->second, step, gain);
836 }
837
838 void Client::handleCommand_Privileges(NetworkPacket* pkt)
839 {
840         m_privileges.clear();
841         infostream << "Client: Privileges updated: ";
842         u16 num_privileges;
843
844         *pkt >> num_privileges;
845
846         for (u16 i = 0; i < num_privileges; i++) {
847                 std::string priv;
848
849                 *pkt >> priv;
850
851                 m_privileges.insert(priv);
852                 infostream << priv << " ";
853         }
854         infostream << std::endl;
855 }
856
857 void Client::handleCommand_InventoryFormSpec(NetworkPacket* pkt)
858 {
859         LocalPlayer *player = m_env.getLocalPlayer();
860         assert(player != NULL);
861
862         // Store formspec in LocalPlayer
863         player->inventory_formspec = pkt->readLongString();
864 }
865
866 void Client::handleCommand_DetachedInventory(NetworkPacket* pkt)
867 {
868         std::string datastring(pkt->getString(0), pkt->getSize());
869         std::istringstream is(datastring, std::ios_base::binary);
870
871         std::string name = deSerializeString(is);
872
873         infostream << "Client: Detached inventory update: \"" << name
874                         << "\"" << std::endl;
875
876         Inventory *inv = NULL;
877         if (m_detached_inventories.count(name) > 0)
878                 inv = m_detached_inventories[name];
879         else {
880                 inv = new Inventory(m_itemdef);
881                 m_detached_inventories[name] = inv;
882         }
883         inv->deSerialize(is);
884 }
885
886 void Client::handleCommand_ShowFormSpec(NetworkPacket* pkt)
887 {
888         std::string formspec = pkt->readLongString();
889         std::string formname;
890
891         *pkt >> formname;
892
893         ClientEvent *event = new ClientEvent();
894         event->type = CE_SHOW_FORMSPEC;
895         // pointer is required as event is a struct only!
896         // adding a std:string to a struct isn't possible
897         event->show_formspec.formspec = new std::string(formspec);
898         event->show_formspec.formname = new std::string(formname);
899         m_client_event_queue.push(event);
900 }
901
902 void Client::handleCommand_SpawnParticle(NetworkPacket* pkt)
903 {
904         std::string datastring(pkt->getString(0), pkt->getSize());
905         std::istringstream is(datastring, std::ios_base::binary);
906
907         v3f pos                 = readV3F1000(is);
908         v3f vel                 = readV3F1000(is);
909         v3f acc                 = readV3F1000(is);
910         float expirationtime    = readF1000(is);
911         float size              = readF1000(is);
912         bool collisiondetection = readU8(is);
913         std::string texture     = deSerializeLongString(is);
914         bool vertical           = false;
915         bool collision_removal  = false;
916         TileAnimationParams animation;
917         animation.type = TAT_NONE;
918         u8 glow = 0;
919         try {
920                 vertical = readU8(is);
921                 collision_removal = readU8(is);
922                 animation.deSerialize(is, m_proto_ver);
923                 glow = readU8(is);
924         } catch (...) {}
925
926         ClientEvent *event = new ClientEvent();
927         event->type                              = CE_SPAWN_PARTICLE;
928         event->spawn_particle.pos                = new v3f (pos);
929         event->spawn_particle.vel                = new v3f (vel);
930         event->spawn_particle.acc                = new v3f (acc);
931         event->spawn_particle.expirationtime     = expirationtime;
932         event->spawn_particle.size               = size;
933         event->spawn_particle.collisiondetection = collisiondetection;
934         event->spawn_particle.collision_removal  = collision_removal;
935         event->spawn_particle.vertical           = vertical;
936         event->spawn_particle.texture            = new std::string(texture);
937         event->spawn_particle.animation          = animation;
938         event->spawn_particle.glow               = glow;
939
940         m_client_event_queue.push(event);
941 }
942
943 void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
944 {
945         u16 amount;
946         float spawntime;
947         v3f minpos;
948         v3f maxpos;
949         v3f minvel;
950         v3f maxvel;
951         v3f minacc;
952         v3f maxacc;
953         float minexptime;
954         float maxexptime;
955         float minsize;
956         float maxsize;
957         bool collisiondetection;
958         u32 id;
959
960         *pkt >> amount >> spawntime >> minpos >> maxpos >> minvel >> maxvel
961                 >> minacc >> maxacc >> minexptime >> maxexptime >> minsize
962                 >> maxsize >> collisiondetection;
963
964         std::string texture = pkt->readLongString();
965
966         *pkt >> id;
967
968         bool vertical = false;
969         bool collision_removal = false;
970         TileAnimationParams animation;
971         animation.type = TAT_NONE;
972         u8 glow = 0;
973         u16 attached_id = 0;
974         try {
975                 *pkt >> vertical;
976                 *pkt >> collision_removal;
977                 *pkt >> attached_id;
978
979                 // This is horrible but required (why are there two ways to deserialize pkts?)
980                 std::string datastring(pkt->getRemainingString(), pkt->getRemainingBytes());
981                 std::istringstream is(datastring, std::ios_base::binary);
982                 animation.deSerialize(is, m_proto_ver);
983                 glow = readU8(is);
984         } catch (...) {}
985
986         ClientEvent *event = new ClientEvent();
987         event->type                                   = CE_ADD_PARTICLESPAWNER;
988         event->add_particlespawner.amount             = amount;
989         event->add_particlespawner.spawntime          = spawntime;
990         event->add_particlespawner.minpos             = new v3f (minpos);
991         event->add_particlespawner.maxpos             = new v3f (maxpos);
992         event->add_particlespawner.minvel             = new v3f (minvel);
993         event->add_particlespawner.maxvel             = new v3f (maxvel);
994         event->add_particlespawner.minacc             = new v3f (minacc);
995         event->add_particlespawner.maxacc             = new v3f (maxacc);
996         event->add_particlespawner.minexptime         = minexptime;
997         event->add_particlespawner.maxexptime         = maxexptime;
998         event->add_particlespawner.minsize            = minsize;
999         event->add_particlespawner.maxsize            = maxsize;
1000         event->add_particlespawner.collisiondetection = collisiondetection;
1001         event->add_particlespawner.collision_removal  = collision_removal;
1002         event->add_particlespawner.attached_id        = attached_id;
1003         event->add_particlespawner.vertical           = vertical;
1004         event->add_particlespawner.texture            = new std::string(texture);
1005         event->add_particlespawner.id                 = id;
1006         event->add_particlespawner.animation          = animation;
1007         event->add_particlespawner.glow               = glow;
1008
1009         m_client_event_queue.push(event);
1010 }
1011
1012
1013 void Client::handleCommand_DeleteParticleSpawner(NetworkPacket* pkt)
1014 {
1015         u32 id;
1016         *pkt >> id;
1017
1018         ClientEvent *event = new ClientEvent();
1019         event->type = CE_DELETE_PARTICLESPAWNER;
1020         event->delete_particlespawner.id = id;
1021
1022         m_client_event_queue.push(event);
1023 }
1024
1025 void Client::handleCommand_HudAdd(NetworkPacket* pkt)
1026 {
1027         std::string datastring(pkt->getString(0), pkt->getSize());
1028         std::istringstream is(datastring, std::ios_base::binary);
1029
1030         u32 id;
1031         u8 type;
1032         v2f pos;
1033         std::string name;
1034         v2f scale;
1035         std::string text;
1036         u32 number;
1037         u32 item;
1038         u32 dir;
1039         v2f align;
1040         v2f offset;
1041         v3f world_pos;
1042         v2s32 size;
1043
1044         *pkt >> id >> type >> pos >> name >> scale >> text >> number >> item
1045                 >> dir >> align >> offset;
1046         try {
1047                 *pkt >> world_pos;
1048         }
1049         catch(SerializationError &e) {};
1050
1051         try {
1052                 *pkt >> size;
1053         } catch(SerializationError &e) {};
1054
1055         ClientEvent *event = new ClientEvent();
1056         event->type             = CE_HUDADD;
1057         event->hudadd.id        = id;
1058         event->hudadd.type      = type;
1059         event->hudadd.pos       = new v2f(pos);
1060         event->hudadd.name      = new std::string(name);
1061         event->hudadd.scale     = new v2f(scale);
1062         event->hudadd.text      = new std::string(text);
1063         event->hudadd.number    = number;
1064         event->hudadd.item      = item;
1065         event->hudadd.dir       = dir;
1066         event->hudadd.align     = new v2f(align);
1067         event->hudadd.offset    = new v2f(offset);
1068         event->hudadd.world_pos = new v3f(world_pos);
1069         event->hudadd.size      = new v2s32(size);
1070         m_client_event_queue.push(event);
1071 }
1072
1073 void Client::handleCommand_HudRemove(NetworkPacket* pkt)
1074 {
1075         u32 id;
1076
1077         *pkt >> id;
1078
1079         ClientEvent *event = new ClientEvent();
1080         event->type     = CE_HUDRM;
1081         event->hudrm.id = id;
1082         m_client_event_queue.push(event);
1083 }
1084
1085 void Client::handleCommand_HudChange(NetworkPacket* pkt)
1086 {
1087         std::string sdata;
1088         v2f v2fdata;
1089         v3f v3fdata;
1090         u32 intdata = 0;
1091         v2s32 v2s32data;
1092         u32 id;
1093         u8 stat;
1094
1095         *pkt >> id >> stat;
1096
1097         if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE ||
1098                 stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET)
1099                 *pkt >> v2fdata;
1100         else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT)
1101                 *pkt >> sdata;
1102         else if (stat == HUD_STAT_WORLD_POS)
1103                 *pkt >> v3fdata;
1104         else if (stat == HUD_STAT_SIZE )
1105                 *pkt >> v2s32data;
1106         else
1107                 *pkt >> intdata;
1108
1109         ClientEvent *event = new ClientEvent();
1110         event->type              = CE_HUDCHANGE;
1111         event->hudchange.id      = id;
1112         event->hudchange.stat    = (HudElementStat)stat;
1113         event->hudchange.v2fdata = new v2f(v2fdata);
1114         event->hudchange.v3fdata = new v3f(v3fdata);
1115         event->hudchange.sdata   = new std::string(sdata);
1116         event->hudchange.data    = intdata;
1117         event->hudchange.v2s32data = new v2s32(v2s32data);
1118         m_client_event_queue.push(event);
1119 }
1120
1121 void Client::handleCommand_HudSetFlags(NetworkPacket* pkt)
1122 {
1123         u32 flags, mask;
1124
1125         *pkt >> flags >> mask;
1126
1127         LocalPlayer *player = m_env.getLocalPlayer();
1128         assert(player != NULL);
1129
1130         bool was_minimap_visible = player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE;
1131         bool was_minimap_radar_visible = player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE;
1132
1133         player->hud_flags &= ~mask;
1134         player->hud_flags |= flags;
1135
1136         m_minimap_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE);
1137         bool m_minimap_radar_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE);
1138
1139         // Hide minimap if it has been disabled by the server
1140         if (m_minimap && m_minimap_disabled_by_server && was_minimap_visible)
1141                 // defers a minimap update, therefore only call it if really
1142                 // needed, by checking that minimap was visible before
1143                 m_minimap->setMinimapMode(MINIMAP_MODE_OFF);
1144
1145         // Switch to surface mode if radar disabled by server
1146         if (m_minimap && m_minimap_radar_disabled_by_server && was_minimap_radar_visible)
1147                 m_minimap->setMinimapMode(MINIMAP_MODE_SURFACEx1);
1148 }
1149
1150 void Client::handleCommand_HudSetParam(NetworkPacket* pkt)
1151 {
1152         u16 param; std::string value;
1153
1154         *pkt >> param >> value;
1155
1156         LocalPlayer *player = m_env.getLocalPlayer();
1157         assert(player != NULL);
1158
1159         if (param == HUD_PARAM_HOTBAR_ITEMCOUNT && value.size() == 4) {
1160                 s32 hotbar_itemcount = readS32((u8*) value.c_str());
1161                 if (hotbar_itemcount > 0 && hotbar_itemcount <= HUD_HOTBAR_ITEMCOUNT_MAX)
1162                         player->hud_hotbar_itemcount = hotbar_itemcount;
1163         }
1164         else if (param == HUD_PARAM_HOTBAR_IMAGE) {
1165                 // If value not empty verify image exists in texture source
1166                 if (!value.empty() && !getTextureSource()->isKnownSourceImage(value)) {
1167                         errorstream << "Server sent wrong Hud hotbar image (sent value: '"
1168                                 << value << "')" << std::endl;
1169                         return;
1170                 }
1171                 player->hotbar_image = value;
1172         }
1173         else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) {
1174                 // If value not empty verify image exists in texture source
1175                 if (!value.empty() && !getTextureSource()->isKnownSourceImage(value)) {
1176                         errorstream << "Server sent wrong Hud hotbar selected image (sent value: '"
1177                                         << value << "')" << std::endl;
1178                         return;
1179                 }
1180                 player->hotbar_selected_image = value;
1181         }
1182 }
1183
1184 void Client::handleCommand_HudSetSky(NetworkPacket* pkt)
1185 {
1186         std::string datastring(pkt->getString(0), pkt->getSize());
1187         std::istringstream is(datastring, std::ios_base::binary);
1188
1189         video::SColor *bgcolor           = new video::SColor(readARGB8(is));
1190         std::string *type                = new std::string(deSerializeString(is));
1191         u16 count                        = readU16(is);
1192         std::vector<std::string> *params = new std::vector<std::string>;
1193
1194         for (size_t i = 0; i < count; i++)
1195                 params->push_back(deSerializeString(is));
1196
1197         bool clouds = true;
1198         try {
1199                 clouds = readU8(is);
1200         } catch (...) {}
1201
1202         ClientEvent *event = new ClientEvent();
1203         event->type            = CE_SET_SKY;
1204         event->set_sky.bgcolor = bgcolor;
1205         event->set_sky.type    = type;
1206         event->set_sky.params  = params;
1207         event->set_sky.clouds  = clouds;
1208         m_client_event_queue.push(event);
1209 }
1210
1211 void Client::handleCommand_CloudParams(NetworkPacket* pkt)
1212 {
1213         f32 density;
1214         video::SColor color_bright;
1215         video::SColor color_ambient;
1216         f32 height;
1217         f32 thickness;
1218         v2f speed;
1219
1220         *pkt >> density >> color_bright >> color_ambient
1221                         >> height >> thickness >> speed;
1222
1223         ClientEvent *event = new ClientEvent();
1224         event->type                       = CE_CLOUD_PARAMS;
1225         event->cloud_params.density       = density;
1226         // use the underlying u32 representation, because we can't
1227         // use struct members with constructors here, and this way
1228         // we avoid using new() and delete() for no good reason
1229         event->cloud_params.color_bright  = color_bright.color;
1230         event->cloud_params.color_ambient = color_ambient.color;
1231         event->cloud_params.height        = height;
1232         event->cloud_params.thickness     = thickness;
1233         // same here: deconstruct to skip constructor
1234         event->cloud_params.speed_x       = speed.X;
1235         event->cloud_params.speed_y       = speed.Y;
1236         m_client_event_queue.push(event);
1237 }
1238
1239 void Client::handleCommand_OverrideDayNightRatio(NetworkPacket* pkt)
1240 {
1241         bool do_override;
1242         u16 day_night_ratio_u;
1243
1244         *pkt >> do_override >> day_night_ratio_u;
1245
1246         float day_night_ratio_f = (float)day_night_ratio_u / 65536;
1247
1248         ClientEvent *event = new ClientEvent();
1249         event->type                                 = CE_OVERRIDE_DAY_NIGHT_RATIO;
1250         event->override_day_night_ratio.do_override = do_override;
1251         event->override_day_night_ratio.ratio_f     = day_night_ratio_f;
1252         m_client_event_queue.push(event);
1253 }
1254
1255 void Client::handleCommand_LocalPlayerAnimations(NetworkPacket* pkt)
1256 {
1257         LocalPlayer *player = m_env.getLocalPlayer();
1258         assert(player != NULL);
1259
1260         *pkt >> player->local_animations[0];
1261         *pkt >> player->local_animations[1];
1262         *pkt >> player->local_animations[2];
1263         *pkt >> player->local_animations[3];
1264         *pkt >> player->local_animation_speed;
1265 }
1266
1267 void Client::handleCommand_EyeOffset(NetworkPacket* pkt)
1268 {
1269         LocalPlayer *player = m_env.getLocalPlayer();
1270         assert(player != NULL);
1271
1272         *pkt >> player->eye_offset_first >> player->eye_offset_third;
1273 }
1274
1275 void Client::handleCommand_UpdatePlayerList(NetworkPacket* pkt)
1276 {
1277         u8 type;
1278         u16 num_players;
1279         *pkt >> type >> num_players;
1280         PlayerListModifer notice_type = (PlayerListModifer) type;
1281
1282         for (u16 i = 0; i < num_players; i++) {
1283                 std::string name;
1284                 *pkt >> name;
1285                 switch (notice_type) {
1286                 case PLAYER_LIST_INIT:
1287                 case PLAYER_LIST_ADD:
1288                         m_env.addPlayerName(name);
1289                         continue;
1290                 case PLAYER_LIST_REMOVE:
1291                         m_env.removePlayerName(name);
1292                         continue;
1293                 }
1294         }
1295 }
1296
1297 void Client::handleCommand_SrpBytesSandB(NetworkPacket* pkt)
1298 {
1299         if (m_chosen_auth_mech != AUTH_MECHANISM_SRP &&
1300                         m_chosen_auth_mech != AUTH_MECHANISM_LEGACY_PASSWORD) {
1301                 errorstream << "Client: Received SRP S_B login message,"
1302                         << " but wasn't supposed to (chosen_mech="
1303                         << m_chosen_auth_mech << ")." << std::endl;
1304                 return;
1305         }
1306
1307         char *bytes_M = 0;
1308         size_t len_M = 0;
1309         SRPUser *usr = (SRPUser *) m_auth_data;
1310         std::string s;
1311         std::string B;
1312         *pkt >> s >> B;
1313
1314         infostream << "Client: Received TOCLIENT_SRP_BYTES_S_B." << std::endl;
1315
1316         srp_user_process_challenge(usr, (const unsigned char *) s.c_str(), s.size(),
1317                 (const unsigned char *) B.c_str(), B.size(),
1318                 (unsigned char **) &bytes_M, &len_M);
1319
1320         if ( !bytes_M ) {
1321                 errorstream << "Client: SRP-6a S_B safety check violation!" << std::endl;
1322                 return;
1323         }
1324
1325         NetworkPacket resp_pkt(TOSERVER_SRP_BYTES_M, 0);
1326         resp_pkt << std::string(bytes_M, len_M);
1327         Send(&resp_pkt);
1328 }
1329
1330 void Client::handleCommand_CSMFlavourLimits(NetworkPacket *pkt)
1331 {
1332         *pkt >> m_csm_flavour_limits >> m_csm_noderange_limit;
1333 }
1334
1335 /*
1336  * Mod channels
1337  */
1338
1339 void Client::handleCommand_ModChannelMsg(NetworkPacket *pkt)
1340 {
1341         std::string channel_name, sender, channel_msg;
1342         *pkt >> channel_name >> sender >> channel_msg;
1343
1344         verbosestream << "Mod channel message received from server " << pkt->getPeerId()
1345                 << " on channel " << channel_name << ". sender: `" << sender << "`, message: "
1346                 << channel_msg << std::endl;
1347
1348         if (!m_modchannel_mgr->channelRegistered(channel_name)) {
1349                 verbosestream << "Server sent us messages on unregistered channel "
1350                         << channel_name << ", ignoring." << std::endl;
1351                 return;
1352         }
1353
1354         m_script->on_modchannel_message(channel_name, sender, channel_msg);
1355 }
1356
1357 void Client::handleCommand_ModChannelSignal(NetworkPacket *pkt)
1358 {
1359         u8 signal_tmp;
1360         ModChannelSignal signal;
1361         std::string channel;
1362
1363         *pkt >> signal_tmp >> channel;
1364
1365         signal = (ModChannelSignal)signal_tmp;
1366
1367         bool valid_signal = true;
1368         // @TODO: send Signal to Lua API
1369         switch (signal) {
1370                 case MODCHANNEL_SIGNAL_JOIN_OK:
1371                         m_modchannel_mgr->setChannelState(channel, MODCHANNEL_STATE_READ_WRITE);
1372                         infostream << "Server ack our mod channel join on channel `" << channel
1373                                 << "`, joining." << std::endl;
1374                         break;
1375                 case MODCHANNEL_SIGNAL_JOIN_FAILURE:
1376                         // Unable to join, remove channel
1377                         m_modchannel_mgr->leaveChannel(channel, 0);
1378                         infostream << "Server refused our mod channel join on channel `" << channel
1379                                 << "`" << std::endl;
1380                         break;
1381                 case MODCHANNEL_SIGNAL_LEAVE_OK:
1382 #ifndef NDEBUG
1383                         infostream << "Server ack our mod channel leave on channel " << channel
1384                                 << "`, leaving." << std::endl;
1385 #endif
1386                         break;
1387                 case MODCHANNEL_SIGNAL_LEAVE_FAILURE:
1388                         infostream << "Server refused our mod channel leave on channel `" << channel
1389                                 << "`" << std::endl;
1390                         break;
1391                 case MODCHANNEL_SIGNAL_CHANNEL_NOT_REGISTERED:
1392 #ifndef NDEBUG
1393                         // Generally unused, but ensure we don't do an implementation error
1394                         infostream << "Server tells us we sent a message on channel `" << channel
1395                                 << "` but we are not registered. Message was dropped." << std::endl;
1396 #endif
1397                         break;
1398                 case MODCHANNEL_SIGNAL_SET_STATE: {
1399                         u8 state;
1400                         *pkt >> state;
1401
1402                         if (state == MODCHANNEL_STATE_INIT || state >= MODCHANNEL_STATE_MAX) {
1403                                 infostream << "Received wrong channel state " << state
1404                                                 << ", ignoring." << std::endl;
1405                                 return;
1406                         }
1407
1408                         m_modchannel_mgr->setChannelState(channel, (ModChannelState) state);
1409                         infostream << "Server sets mod channel `" << channel
1410                                         << "` in read-only mode." << std::endl;
1411                         break;
1412                 }
1413                 default:
1414 #ifndef NDEBUG
1415                         warningstream << "Received unhandled mod channel signal ID "
1416                                 << signal << ", ignoring." << std::endl;
1417 #endif
1418                         valid_signal = false;
1419                         break;
1420         }
1421
1422         // If signal is valid, forward it to client side mods
1423         if (valid_signal)
1424                 m_script->on_modchannel_signal(channel, signal);
1425 }