]> git.lizzy.rs Git - dragonfireclient.git/blob - src/clientiface.cpp
Fix flipped textures for drawtype "glasslike"
[dragonfireclient.git] / src / clientiface.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2014 celeron55, Perttu Ahola <celeron55@gmail.com>
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 <sstream>
21
22 #include "clientiface.h"
23 #include "player.h"
24 #include "settings.h"
25 #include "mapblock.h"
26 #include "connection.h"
27 #include "environment.h"
28 #include "map.h"
29 #include "emerge.h"
30 #include "serverobject.h"              // TODO this is used for cleanup of only
31
32 #include "util/numeric.h"
33 #include "util/mathconstants.h"
34
35 #include "main.h"                      // for g_settings
36
37 const char *ClientInterface::statenames[] = {
38         "Invalid",
39         "Disconnecting",
40         "Denied",
41         "Created",
42         "InitSent",
43         "InitDone",
44         "DefinitionsSent",
45         "Active"
46 };
47
48
49
50 std::string ClientInterface::state2Name(ClientState state)
51 {
52         return statenames[state];
53 }
54
55
56 void RemoteClient::GetNextBlocks(
57                 ServerEnvironment *env,
58                 EmergeManager * emerge,
59                 float dtime,
60                 std::vector<PrioritySortedBlockTransfer> &dest)
61 {
62         DSTACK(__FUNCTION_NAME);
63
64
65         // Increment timers
66         m_nothing_to_send_pause_timer -= dtime;
67         m_nearest_unsent_reset_timer += dtime;
68
69         if(m_nothing_to_send_pause_timer >= 0)
70                 return;
71
72         Player *player = env->getPlayer(peer_id);
73         // This can happen sometimes; clients and players are not in perfect sync.
74         if(player == NULL)
75                 return;
76
77         // Won't send anything if already sending
78         if(m_blocks_sending.size() >= g_settings->getU16
79                         ("max_simultaneous_block_sends_per_client"))
80         {
81                 //infostream<<"Not sending any blocks, Queue full."<<std::endl;
82                 return;
83         }
84
85         v3f playerpos = player->getPosition();
86         v3f playerspeed = player->getSpeed();
87         v3f playerspeeddir(0,0,0);
88         if(playerspeed.getLength() > 1.0*BS)
89                 playerspeeddir = playerspeed / playerspeed.getLength();
90         // Predict to next block
91         v3f playerpos_predicted = playerpos + playerspeeddir*MAP_BLOCKSIZE*BS;
92
93         v3s16 center_nodepos = floatToInt(playerpos_predicted, BS);
94
95         v3s16 center = getNodeBlockPos(center_nodepos);
96
97         // Camera position and direction
98         v3f camera_pos = player->getEyePosition();
99         v3f camera_dir = v3f(0,0,1);
100         camera_dir.rotateYZBy(player->getPitch());
101         camera_dir.rotateXZBy(player->getYaw());
102
103         /*infostream<<"camera_dir=("<<camera_dir.X<<","<<camera_dir.Y<<","
104                         <<camera_dir.Z<<")"<<std::endl;*/
105
106         /*
107                 Get the starting value of the block finder radius.
108         */
109
110         if(m_last_center != center)
111         {
112                 m_nearest_unsent_d = 0;
113                 m_last_center = center;
114         }
115
116         /*infostream<<"m_nearest_unsent_reset_timer="
117                         <<m_nearest_unsent_reset_timer<<std::endl;*/
118
119         // Reset periodically to workaround for some bugs or stuff
120         if(m_nearest_unsent_reset_timer > 20.0)
121         {
122                 m_nearest_unsent_reset_timer = 0;
123                 m_nearest_unsent_d = 0;
124                 //infostream<<"Resetting m_nearest_unsent_d for "
125                 //              <<server->getPlayerName(peer_id)<<std::endl;
126         }
127
128         //s16 last_nearest_unsent_d = m_nearest_unsent_d;
129         s16 d_start = m_nearest_unsent_d;
130
131         //infostream<<"d_start="<<d_start<<std::endl;
132
133         u16 max_simul_sends_setting = g_settings->getU16
134                         ("max_simultaneous_block_sends_per_client");
135         u16 max_simul_sends_usually = max_simul_sends_setting;
136
137         /*
138                 Check the time from last addNode/removeNode.
139
140                 Decrease send rate if player is building stuff.
141         */
142         m_time_from_building += dtime;
143         if(m_time_from_building < g_settings->getFloat(
144                                 "full_block_send_enable_min_time_from_building"))
145         {
146                 max_simul_sends_usually
147                         = LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
148         }
149
150         /*
151                 Number of blocks sending + number of blocks selected for sending
152         */
153         u32 num_blocks_selected = m_blocks_sending.size();
154
155         /*
156                 next time d will be continued from the d from which the nearest
157                 unsent block was found this time.
158
159                 This is because not necessarily any of the blocks found this
160                 time are actually sent.
161         */
162         s32 new_nearest_unsent_d = -1;
163
164         const s16 full_d_max = g_settings->getS16("max_block_send_distance");
165         s16 d_max = full_d_max;
166         s16 d_max_gen = g_settings->getS16("max_block_generate_distance");
167
168         // Don't loop very much at a time
169         s16 max_d_increment_at_time = 2;
170         if(d_max > d_start + max_d_increment_at_time)
171                 d_max = d_start + max_d_increment_at_time;
172
173         s32 nearest_emerged_d = -1;
174         s32 nearest_emergefull_d = -1;
175         s32 nearest_sent_d = -1;
176         bool queue_is_full = false;
177
178         s16 d;
179         for(d = d_start; d <= d_max; d++)
180         {
181                 /*
182                         Get the border/face dot coordinates of a "d-radiused"
183                         box
184                 */
185                 std::list<v3s16> list;
186                 getFacePositions(list, d);
187
188                 std::list<v3s16>::iterator li;
189                 for(li=list.begin(); li!=list.end(); ++li)
190                 {
191                         v3s16 p = *li + center;
192
193                         /*
194                                 Send throttling
195                                 - Don't allow too many simultaneous transfers
196                                 - EXCEPT when the blocks are very close
197
198                                 Also, don't send blocks that are already flying.
199                         */
200
201                         // Start with the usual maximum
202                         u16 max_simul_dynamic = max_simul_sends_usually;
203
204                         // If block is very close, allow full maximum
205                         if(d <= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
206                                 max_simul_dynamic = max_simul_sends_setting;
207
208                         // Don't select too many blocks for sending
209                         if(num_blocks_selected >= max_simul_dynamic)
210                         {
211                                 queue_is_full = true;
212                                 goto queue_full_break;
213                         }
214
215                         // Don't send blocks that are currently being transferred
216                         if(m_blocks_sending.find(p) != m_blocks_sending.end())
217                                 continue;
218
219                         /*
220                                 Do not go over-limit
221                         */
222                         if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
223                         || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
224                         || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
225                         || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
226                         || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
227                         || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
228                                 continue;
229
230                         // If this is true, inexistent block will be made from scratch
231                         bool generate = d <= d_max_gen;
232
233                         {
234                                 /*// Limit the generating area vertically to 2/3
235                                 if(abs(p.Y - center.Y) > d_max_gen - d_max_gen / 3)
236                                         generate = false;*/
237
238                                 // Limit the send area vertically to 1/2
239                                 if(abs(p.Y - center.Y) > full_d_max / 2)
240                                         continue;
241                         }
242
243                         /*
244                                 Don't generate or send if not in sight
245                                 FIXME This only works if the client uses a small enough
246                                 FOV setting. The default of 72 degrees is fine.
247                         */
248
249                         float camera_fov = (72.0*M_PI/180) * 4./3.;
250                         if(isBlockInSight(p, camera_pos, camera_dir, camera_fov, 10000*BS) == false)
251                         {
252                                 continue;
253                         }
254
255                         /*
256                                 Don't send already sent blocks
257                         */
258                         {
259                                 if(m_blocks_sent.find(p) != m_blocks_sent.end())
260                                 {
261                                         continue;
262                                 }
263                         }
264
265                         /*
266                                 Check if map has this block
267                         */
268                         MapBlock *block = env->getMap().getBlockNoCreateNoEx(p);
269
270                         bool surely_not_found_on_disk = false;
271                         bool block_is_invalid = false;
272                         if(block != NULL)
273                         {
274                                 // Reset usage timer, this block will be of use in the future.
275                                 block->resetUsageTimer();
276
277                                 // Block is dummy if data doesn't exist.
278                                 // It means it has been not found from disk and not generated
279                                 if(block->isDummy())
280                                 {
281                                         surely_not_found_on_disk = true;
282                                 }
283
284                                 // Block is valid if lighting is up-to-date and data exists
285                                 if(block->isValid() == false)
286                                 {
287                                         block_is_invalid = true;
288                                 }
289
290                                 if(block->isGenerated() == false)
291                                         block_is_invalid = true;
292
293                                 /*
294                                         If block is not close, don't send it unless it is near
295                                         ground level.
296
297                                         Block is near ground level if night-time mesh
298                                         differs from day-time mesh.
299                                 */
300                                 if(d >= 4)
301                                 {
302                                         if(block->getDayNightDiff() == false)
303                                                 continue;
304                                 }
305                         }
306
307                         /*
308                                 If block has been marked to not exist on disk (dummy)
309                                 and generating new ones is not wanted, skip block.
310                         */
311                         if(generate == false && surely_not_found_on_disk == true)
312                         {
313                                 // get next one.
314                                 continue;
315                         }
316
317                         /*
318                                 Add inexistent block to emerge queue.
319                         */
320                         if(block == NULL || surely_not_found_on_disk || block_is_invalid)
321                         {
322                                 if (emerge->enqueueBlockEmerge(peer_id, p, generate)) {
323                                         if (nearest_emerged_d == -1)
324                                                 nearest_emerged_d = d;
325                                 } else {
326                                         if (nearest_emergefull_d == -1)
327                                                 nearest_emergefull_d = d;
328                                         goto queue_full_break;
329                                 }
330
331                                 // get next one.
332                                 continue;
333                         }
334
335                         if(nearest_sent_d == -1)
336                                 nearest_sent_d = d;
337
338                         /*
339                                 Add block to send queue
340                         */
341                         PrioritySortedBlockTransfer q((float)d, p, peer_id);
342
343                         dest.push_back(q);
344
345                         num_blocks_selected += 1;
346                 }
347         }
348 queue_full_break:
349
350         // If nothing was found for sending and nothing was queued for
351         // emerging, continue next time browsing from here
352         if(nearest_emerged_d != -1){
353                 new_nearest_unsent_d = nearest_emerged_d;
354         } else if(nearest_emergefull_d != -1){
355                 new_nearest_unsent_d = nearest_emergefull_d;
356         } else {
357                 if(d > g_settings->getS16("max_block_send_distance")){
358                         new_nearest_unsent_d = 0;
359                         m_nothing_to_send_pause_timer = 2.0;
360                 } else {
361                         if(nearest_sent_d != -1)
362                                 new_nearest_unsent_d = nearest_sent_d;
363                         else
364                                 new_nearest_unsent_d = d;
365                 }
366         }
367
368         if(new_nearest_unsent_d != -1)
369                 m_nearest_unsent_d = new_nearest_unsent_d;
370 }
371
372 void RemoteClient::GotBlock(v3s16 p)
373 {
374         if(m_blocks_sending.find(p) != m_blocks_sending.end())
375                 m_blocks_sending.erase(p);
376         else
377         {
378                 m_excess_gotblocks++;
379         }
380         m_blocks_sent.insert(p);
381 }
382
383 void RemoteClient::SentBlock(v3s16 p)
384 {
385         if(m_blocks_sending.find(p) == m_blocks_sending.end())
386                 m_blocks_sending[p] = 0.0;
387         else
388                 infostream<<"RemoteClient::SentBlock(): Sent block"
389                                 " already in m_blocks_sending"<<std::endl;
390 }
391
392 void RemoteClient::SetBlockNotSent(v3s16 p)
393 {
394         m_nearest_unsent_d = 0;
395
396         if(m_blocks_sending.find(p) != m_blocks_sending.end())
397                 m_blocks_sending.erase(p);
398         if(m_blocks_sent.find(p) != m_blocks_sent.end())
399                 m_blocks_sent.erase(p);
400 }
401
402 void RemoteClient::SetBlocksNotSent(std::map<v3s16, MapBlock*> &blocks)
403 {
404         m_nearest_unsent_d = 0;
405
406         for(std::map<v3s16, MapBlock*>::iterator
407                         i = blocks.begin();
408                         i != blocks.end(); ++i)
409         {
410                 v3s16 p = i->first;
411
412                 if(m_blocks_sending.find(p) != m_blocks_sending.end())
413                         m_blocks_sending.erase(p);
414                 if(m_blocks_sent.find(p) != m_blocks_sent.end())
415                         m_blocks_sent.erase(p);
416         }
417 }
418
419 void RemoteClient::notifyEvent(ClientStateEvent event)
420 {
421         std::ostringstream myerror;
422         switch (m_state)
423         {
424         case CS_Invalid:
425                 //intentionally do nothing
426                 break;
427         case CS_Created:
428                 switch(event)
429                 {
430                 case CSE_Init:
431                         m_state = CS_InitSent;
432                         break;
433                 case CSE_Disconnect:
434                         m_state = CS_Disconnecting;
435                         break;
436                 case CSE_SetDenied:
437                         m_state = CS_Denied;
438                         break;
439                 /* GotInit2 SetDefinitionsSent SetMediaSent */
440                 default:
441                         myerror << "Created: Invalid client state transition! " << event;
442                         throw ClientStateError(myerror.str());
443                 }
444                 break;
445         case CS_Denied:
446                 /* don't do anything if in denied state */
447                 break;
448         case CS_InitSent:
449                 switch(event)
450                 {
451                 case CSE_GotInit2:
452                         confirmSerializationVersion();
453                         m_state = CS_InitDone;
454                         break;
455                 case CSE_Disconnect:
456                         m_state = CS_Disconnecting;
457                         break;
458                 case CSE_SetDenied:
459                         m_state = CS_Denied;
460                         break;
461
462                 /* Init SetDefinitionsSent SetMediaSent */
463                 default:
464                         myerror << "InitSent: Invalid client state transition! " << event;
465                         throw ClientStateError(myerror.str());
466                 }
467                 break;
468
469         case CS_InitDone:
470                 switch(event)
471                 {
472                 case CSE_SetDefinitionsSent:
473                         m_state = CS_DefinitionsSent;
474                         break;
475                 case CSE_Disconnect:
476                         m_state = CS_Disconnecting;
477                         break;
478                 case CSE_SetDenied:
479                         m_state = CS_Denied;
480                         break;
481
482                 /* Init GotInit2 SetMediaSent */
483                 default:
484                         myerror << "InitDone: Invalid client state transition! " << event;
485                         throw ClientStateError(myerror.str());
486                 }
487                 break;
488         case CS_DefinitionsSent:
489                 switch(event)
490                 {
491                 case CSE_SetClientReady:
492                         m_state = CS_Active;
493                         break;
494                 case CSE_Disconnect:
495                         m_state = CS_Disconnecting;
496                         break;
497                 case CSE_SetDenied:
498                         m_state = CS_Denied;
499                         break;
500                 /* Init GotInit2 SetDefinitionsSent */
501                 default:
502                         myerror << "DefinitionsSent: Invalid client state transition! " << event;
503                         throw ClientStateError(myerror.str());
504                 }
505                 break;
506         case CS_Active:
507                 switch(event)
508                 {
509                 case CSE_SetDenied:
510                         m_state = CS_Denied;
511                         break;
512                 case CSE_Disconnect:
513                         m_state = CS_Disconnecting;
514                         break;
515                 /* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */
516                 default:
517                         myerror << "Active: Invalid client state transition! " << event;
518                         throw ClientStateError(myerror.str());
519                         break;
520                 }
521                 break;
522         case CS_Disconnecting:
523                 /* we are already disconnecting */
524                 break;
525         }
526 }
527
528 u32 RemoteClient::uptime()
529 {
530         return getTime(PRECISION_SECONDS) - m_connection_time;
531 }
532
533 ClientInterface::ClientInterface(con::Connection* con)
534 :
535         m_con(con),
536         m_env(NULL),
537         m_print_info_timer(0.0)
538 {
539
540 }
541 ClientInterface::~ClientInterface()
542 {
543         /*
544                 Delete clients
545         */
546         {
547                 JMutexAutoLock clientslock(m_clients_mutex);
548
549                 for(std::map<u16, RemoteClient*>::iterator
550                         i = m_clients.begin();
551                         i != m_clients.end(); ++i)
552                 {
553
554                         // Delete client
555                         delete i->second;
556                 }
557         }
558 }
559
560 std::list<u16> ClientInterface::getClientIDs(ClientState min_state)
561 {
562         std::list<u16> reply;
563         JMutexAutoLock clientslock(m_clients_mutex);
564
565         for(std::map<u16, RemoteClient*>::iterator
566                 i = m_clients.begin();
567                 i != m_clients.end(); ++i)
568         {
569                 if (i->second->getState() >= min_state)
570                         reply.push_back(i->second->peer_id);
571         }
572
573         return reply;
574 }
575
576 std::vector<std::string> ClientInterface::getPlayerNames()
577 {
578         return m_clients_names;
579 }
580
581
582 void ClientInterface::step(float dtime)
583 {
584         m_print_info_timer += dtime;
585         if(m_print_info_timer >= 30.0)
586         {
587                 m_print_info_timer = 0.0;
588                 UpdatePlayerList();
589         }
590 }
591
592 void ClientInterface::UpdatePlayerList()
593 {
594         if (m_env != NULL)
595                 {
596                 std::list<u16> clients = getClientIDs();
597                 m_clients_names.clear();
598
599
600                 if(clients.size() != 0)
601                         infostream<<"Players:"<<std::endl;
602                 for(std::list<u16>::iterator
603                         i = clients.begin();
604                         i != clients.end(); ++i)
605                 {
606                         Player *player = m_env->getPlayer(*i);
607                         if(player==NULL)
608                                 continue;
609                         infostream<<"* "<<player->getName()<<"\t";
610
611                         {
612                                 JMutexAutoLock clientslock(m_clients_mutex);
613                                 RemoteClient* client = lockedGetClientNoEx(*i);
614                                 if(client != NULL)
615                                         client->PrintInfo(infostream);
616                         }
617                         m_clients_names.push_back(player->getName());
618                 }
619         }
620 }
621
622 void ClientInterface::send(u16 peer_id,u8 channelnum,
623                 SharedBuffer<u8> data, bool reliable)
624 {
625         m_con->Send(peer_id, channelnum, data, reliable);
626 }
627
628 void ClientInterface::sendToAll(u16 channelnum,
629                 SharedBuffer<u8> data, bool reliable)
630 {
631         JMutexAutoLock clientslock(m_clients_mutex);
632         for(std::map<u16, RemoteClient*>::iterator
633                 i = m_clients.begin();
634                 i != m_clients.end(); ++i)
635         {
636                 RemoteClient *client = i->second;
637
638                 if (client->net_proto_version != 0)
639                 {
640                         m_con->Send(client->peer_id, channelnum, data, reliable);
641                 }
642         }
643 }
644
645 RemoteClient* ClientInterface::getClientNoEx(u16 peer_id, ClientState state_min)
646 {
647         JMutexAutoLock clientslock(m_clients_mutex);
648         std::map<u16, RemoteClient*>::iterator n;
649         n = m_clients.find(peer_id);
650         // The client may not exist; clients are immediately removed if their
651         // access is denied, and this event occurs later then.
652         if(n == m_clients.end())
653                 return NULL;
654
655         if (n->second->getState() >= state_min)
656                 return n->second;
657         else
658                 return NULL;
659 }
660
661 RemoteClient* ClientInterface::lockedGetClientNoEx(u16 peer_id, ClientState state_min)
662 {
663         std::map<u16, RemoteClient*>::iterator n;
664         n = m_clients.find(peer_id);
665         // The client may not exist; clients are immediately removed if their
666         // access is denied, and this event occurs later then.
667         if(n == m_clients.end())
668                 return NULL;
669
670         if (n->second->getState() >= state_min)
671                 return n->second;
672         else
673                 return NULL;
674 }
675
676 ClientState ClientInterface::getClientState(u16 peer_id)
677 {
678         JMutexAutoLock clientslock(m_clients_mutex);
679         std::map<u16, RemoteClient*>::iterator n;
680         n = m_clients.find(peer_id);
681         // The client may not exist; clients are immediately removed if their
682         // access is denied, and this event occurs later then.
683         if(n == m_clients.end())
684                 return CS_Invalid;
685
686         return n->second->getState();
687 }
688
689 void ClientInterface::setPlayerName(u16 peer_id,std::string name)
690 {
691         JMutexAutoLock clientslock(m_clients_mutex);
692         std::map<u16, RemoteClient*>::iterator n;
693         n = m_clients.find(peer_id);
694         // The client may not exist; clients are immediately removed if their
695         // access is denied, and this event occurs later then.
696         if(n != m_clients.end())
697                 n->second->setName(name);
698 }
699
700 void ClientInterface::DeleteClient(u16 peer_id)
701 {
702         JMutexAutoLock conlock(m_clients_mutex);
703
704         // Error check
705         std::map<u16, RemoteClient*>::iterator n;
706         n = m_clients.find(peer_id);
707         // The client may not exist; clients are immediately removed if their
708         // access is denied, and this event occurs later then.
709         if(n == m_clients.end())
710                 return;
711
712         /*
713                 Mark objects to be not known by the client
714         */
715         //TODO this should be done by client destructor!!!
716         RemoteClient *client = n->second;
717         // Handle objects
718         for(std::set<u16>::iterator
719                         i = client->m_known_objects.begin();
720                         i != client->m_known_objects.end(); ++i)
721         {
722                 // Get object
723                 u16 id = *i;
724                 ServerActiveObject* obj = m_env->getActiveObject(id);
725
726                 if(obj && obj->m_known_by_count > 0)
727                         obj->m_known_by_count--;
728         }
729
730         // Delete client
731         delete m_clients[peer_id];
732         m_clients.erase(peer_id);
733 }
734
735 void ClientInterface::CreateClient(u16 peer_id)
736 {
737         JMutexAutoLock conlock(m_clients_mutex);
738
739         // Error check
740         std::map<u16, RemoteClient*>::iterator n;
741         n = m_clients.find(peer_id);
742         // The client shouldn't already exist
743         if(n != m_clients.end()) return;
744
745         // Create client
746         RemoteClient *client = new RemoteClient();
747         client->peer_id = peer_id;
748         m_clients[client->peer_id] = client;
749 }
750
751 void ClientInterface::event(u16 peer_id, ClientStateEvent event)
752 {
753         {
754                 JMutexAutoLock clientlock(m_clients_mutex);
755
756                 // Error check
757                 std::map<u16, RemoteClient*>::iterator n;
758                 n = m_clients.find(peer_id);
759
760                 // No client to deliver event
761                 if (n == m_clients.end())
762                         return;
763                 n->second->notifyEvent(event);
764         }
765
766         if ((event == CSE_SetClientReady) ||
767                 (event == CSE_Disconnect)     ||
768                 (event == CSE_SetDenied))
769         {
770                 UpdatePlayerList();
771         }
772 }
773
774 u16 ClientInterface::getProtocolVersion(u16 peer_id)
775 {
776         JMutexAutoLock conlock(m_clients_mutex);
777
778         // Error check
779         std::map<u16, RemoteClient*>::iterator n;
780         n = m_clients.find(peer_id);
781
782         // No client to get version
783         if (n == m_clients.end())
784                 return 0;
785
786         return n->second->net_proto_version;
787 }
788
789 void ClientInterface::setClientVersion(u16 peer_id, u8 major, u8 minor, u8 patch, std::string full)
790 {
791         JMutexAutoLock conlock(m_clients_mutex);
792
793         // Error check
794         std::map<u16, RemoteClient*>::iterator n;
795         n = m_clients.find(peer_id);
796
797         // No client to set versions
798         if (n == m_clients.end())
799                 return;
800
801         n->second->setVersionInfo(major,minor,patch,full);
802 }