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