]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - net_main.c
now uses more smoke particles for gunshots, looks better
[xonotic/darkplaces.git] / net_main.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20 // net_main.c
21
22 #include "quakedef.h"
23 #include "net_master.h"
24
25 qsocket_t *net_activeSockets = NULL;
26 mempool_t *net_mempool;
27
28 qboolean        ipxAvailable = false;
29 qboolean        tcpipAvailable = false;
30
31 int                     net_hostport;
32 int                     DEFAULTnet_hostport = 26000;
33
34 char            my_ipx_address[NET_NAMELEN];
35 char            my_tcpip_address[NET_NAMELEN];
36
37 static qboolean listening = false;
38
39 qboolean        slistInProgress = false;
40 qboolean        slistSilent = false;
41 qboolean        slistLocal = true;
42 static double   slistStartTime;
43 static int              slistLastShown;
44
45 static void Slist_Send(void);
46 static void Slist_Poll(void);
47 PollProcedure   slistSendProcedure = {NULL, 0.0, Slist_Send};
48 PollProcedure   slistPollProcedure = {NULL, 0.0, Slist_Poll};
49
50 static void InetSlist_Send(void);
51 static void InetSlist_Poll(void);
52 PollProcedure   inetSlistSendProcedure = {NULL, 0.0, InetSlist_Send};
53 PollProcedure   inetSlistPollProcedure = {NULL, 0.0, InetSlist_Poll};
54
55
56 sizebuf_t               net_message;
57 int                             net_activeconnections = 0;
58
59 int messagesSent = 0;
60 int messagesReceived = 0;
61 int unreliableMessagesSent = 0;
62 int unreliableMessagesReceived = 0;
63
64 cvar_t  net_messagetimeout = {0, "net_messagetimeout","300"};
65 cvar_t  hostname = {CVAR_SAVE, "hostname", "UNNAMED"};
66
67 qboolean        configRestored = false;
68
69 // these two macros are to make the code more readable
70 #define sfunc   net_drivers[sock->driver]
71 #define dfunc   net_drivers[net_driverlevel]
72
73 int     net_driverlevel;
74
75
76 double                  net_time;
77
78 double SetNetTime(void)
79 {
80         net_time = Sys_DoubleTime();
81         return net_time;
82 }
83
84
85 /*
86 ===================
87 NET_NewQSocket
88
89 Called by drivers when a new communications endpoint is required
90 The sequence and buffer fields will be filled in properly
91 ===================
92 */
93 qsocket_t *NET_NewQSocket (void)
94 {
95         qsocket_t       *sock;
96
97         if (net_activeconnections >= svs.maxclients)
98                 return NULL;
99
100         sock = Mem_Alloc(net_mempool, sizeof(qsocket_t));
101
102         // add it to active list
103         sock->next = net_activeSockets;
104         net_activeSockets = sock;
105
106         sock->disconnected = false;
107         sock->connecttime = net_time;
108         strcpy (sock->address,"UNSET ADDRESS");
109         sock->driver = net_driverlevel;
110         sock->socket = 0;
111         sock->driverdata = NULL;
112         sock->canSend = true;
113         sock->sendNext = false;
114         sock->lastMessageTime = net_time;
115         sock->ackSequence = 0;
116         sock->sendSequence = 0;
117         sock->unreliableSendSequence = 0;
118         sock->sendMessageLength = 0;
119         sock->receiveSequence = 0;
120         sock->unreliableReceiveSequence = 0;
121         sock->receiveMessageLength = 0;
122
123         return sock;
124 }
125
126
127 void NET_FreeQSocket(qsocket_t *sock)
128 {
129         qsocket_t       *s;
130
131         // remove it from active list
132         if (sock == net_activeSockets)
133                 net_activeSockets = net_activeSockets->next;
134         else
135         {
136                 for (s = net_activeSockets; s; s = s->next)
137                         if (s->next == sock)
138                         {
139                                 s->next = sock->next;
140                                 break;
141                         }
142                 if (!s)
143                         Sys_Error ("NET_FreeQSocket: not active\n");
144         }
145
146         Mem_Free(sock);
147 }
148
149
150 static void NET_Listen_f (void)
151 {
152         if (Cmd_Argc () != 2)
153         {
154                 Con_Printf ("\"listen\" is \"%u\"\n", listening ? 1 : 0);
155                 return;
156         }
157
158         listening = atoi(Cmd_Argv(1)) ? true : false;
159
160         for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
161         {
162                 if (net_drivers[net_driverlevel].initialized == false)
163                         continue;
164                 dfunc.Listen (listening);
165         }
166 }
167
168
169 static void MaxPlayers_f (void)
170 {
171         int n;
172
173         if (Cmd_Argc () != 2)
174         {
175                 Con_Printf ("\"maxplayers\" is \"%u\"\n", svs.maxclients);
176                 return;
177         }
178
179         if (sv.active)
180         {
181                 Con_Printf ("maxplayers can not be changed while a server is running.\n");
182                 return;
183         }
184
185         n = atoi(Cmd_Argv(1));
186         n = bound(1, n, MAX_SCOREBOARD);
187         if (svs.maxclients != n)
188                 Con_Printf ("\"maxplayers\" set to \"%u\"\n", n);
189
190         if ((n == 1) && listening)
191                 Cbuf_AddText ("listen 0\n");
192
193         if ((n > 1) && (!listening))
194                 Cbuf_AddText ("listen 1\n");
195
196         SV_SetMaxClients(n);
197 }
198
199
200 static void NET_Port_f (void)
201 {
202         int     n;
203
204         if (Cmd_Argc () != 2)
205         {
206                 Con_Printf ("\"port\" is \"%u\"\n", net_hostport);
207                 return;
208         }
209
210         n = atoi(Cmd_Argv(1));
211         if (n < 1 || n > 65534)
212         {
213                 Con_Printf ("Bad value, must be between 1 and 65534\n");
214                 return;
215         }
216
217         DEFAULTnet_hostport = n;
218         net_hostport = n;
219
220         if (listening)
221         {
222                 // force a change to the new port
223                 Cbuf_AddText ("listen 0\n");
224                 Cbuf_AddText ("listen 1\n");
225         }
226 }
227
228
229 static void NET_Heartbeat_f (void)
230 {
231         NET_Heartbeat ();
232 }
233
234
235 static void PrintSlistHeader(void)
236 {
237         Con_Printf("Server          Map             Users\n");
238         Con_Printf("--------------- --------------- -----\n");
239         slistLastShown = 0;
240 }
241
242
243 static void PrintSlist(void)
244 {
245         int n;
246
247         for (n = slistLastShown; n < hostCacheCount; n++)
248         {
249                 if (hostcache[n].maxusers)
250                         Con_Printf("%-15.15s %-15.15s %2u/%2u\n", hostcache[n].name, hostcache[n].map, hostcache[n].users, hostcache[n].maxusers);
251                 else
252                         Con_Printf("%-15.15s %-15.15s\n", hostcache[n].name, hostcache[n].map);
253         }
254         slistLastShown = n;
255 }
256
257
258 static void PrintSlistTrailer(void)
259 {
260         if (hostCacheCount)
261                 Con_Printf("== end list ==\n\n");
262         else
263                 Con_Printf("No Quake servers found.\n\n");
264 }
265
266
267 void NET_Slist_f (void)
268 {
269         if (slistInProgress)
270                 return;
271
272         if (! slistSilent)
273         {
274                 Con_Printf("Looking for Quake servers...\n");
275                 PrintSlistHeader();
276         }
277
278         slistInProgress = true;
279         slistStartTime = Sys_DoubleTime();
280
281         SchedulePollProcedure(&slistSendProcedure, 0.0);
282         SchedulePollProcedure(&slistPollProcedure, 0.1);
283
284         hostCacheCount = 0;
285 }
286
287
288 void NET_InetSlist_f (void)
289 {
290         if (slistInProgress)
291                 return;
292
293         if (! slistSilent)
294         {
295                 Con_Printf("Looking for Quake servers...\n");
296                 PrintSlistHeader();
297         }
298
299         slistInProgress = true;
300         slistStartTime = Sys_DoubleTime();
301
302         SchedulePollProcedure(&inetSlistSendProcedure, 0.0);
303         SchedulePollProcedure(&inetSlistPollProcedure, 0.1);
304
305         hostCacheCount = 0;
306 }
307
308
309 static void Slist_Send(void)
310 {
311         for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
312         {
313                 if (!slistLocal && net_driverlevel == 0)
314                         continue;
315                 if (net_drivers[net_driverlevel].initialized == false)
316                         continue;
317                 dfunc.SearchForHosts (true);
318         }
319
320         if ((Sys_DoubleTime() - slistStartTime) < 0.5)
321                 SchedulePollProcedure(&slistSendProcedure, 0.75);
322 }
323
324
325 static void Slist_Poll(void)
326 {
327         for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
328         {
329                 if (!slistLocal && net_driverlevel == 0)
330                         continue;
331                 if (net_drivers[net_driverlevel].initialized == false)
332                         continue;
333                 dfunc.SearchForHosts (false);
334         }
335
336         if (! slistSilent)
337                 PrintSlist();
338
339         if ((Sys_DoubleTime() - slistStartTime) < 1.5)
340         {
341                 SchedulePollProcedure(&slistPollProcedure, 0.1);
342                 return;
343         }
344
345         if (! slistSilent)
346                 PrintSlistTrailer();
347         slistInProgress = false;
348         slistSilent = false;
349         slistLocal = true;
350 }
351
352
353 static void InetSlist_Send(void)
354 {
355         char* host;
356
357         if (!slistInProgress)
358                 return;
359
360         while ((host = Master_BuildGetServers ()) != NULL)
361         {
362                 for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
363                 {
364                         if (!slistLocal && net_driverlevel == 0)
365                                 continue;
366                         if (net_drivers[net_driverlevel].initialized == false)
367                                 continue;
368                         dfunc.SearchForInetHosts (host);
369                 }
370         }
371
372         if ((Sys_DoubleTime() - slistStartTime) < 3.5)
373                 SchedulePollProcedure(&inetSlistSendProcedure, 1.0);
374 }
375
376
377 static void InetSlist_Poll(void)
378 {
379         for (net_driverlevel=0; net_driverlevel < net_numdrivers; net_driverlevel++)
380         {
381                 if (!slistLocal && net_driverlevel == 0)
382                         continue;
383                 if (net_drivers[net_driverlevel].initialized == false)
384                         continue;
385                 // We stop as soon as we have one answer (FIXME: bad...)
386                 if (dfunc.SearchForInetHosts (NULL))
387                         slistInProgress = false;
388         }
389
390         if (! slistSilent)
391                 PrintSlist();
392
393         if (slistInProgress && (Sys_DoubleTime() - slistStartTime) < 4.0)
394         {
395                 SchedulePollProcedure(&inetSlistPollProcedure, 0.1);
396                 return;
397         }
398
399         if (! slistSilent)
400                 PrintSlistTrailer();
401         slistInProgress = false;
402         slistSilent = false;
403         slistLocal = true;
404 }
405
406
407 /*
408 ===================
409 NET_Connect
410 ===================
411 */
412
413 int hostCacheCount = 0;
414 hostcache_t hostcache[HOSTCACHESIZE];
415
416 qsocket_t *NET_Connect (char *host)
417 {
418         qsocket_t               *ret;
419         int                             n;
420         int                             numdrivers = net_numdrivers;
421
422         SetNetTime();
423
424         if (host && *host == 0)
425                 host = NULL;
426
427         if (host)
428         {
429                 if (Q_strcasecmp (host, "local") == 0)
430                 {
431                         numdrivers = 1;
432                         goto JustDoIt;
433                 }
434
435                 if (hostCacheCount)
436                 {
437                         for (n = 0; n < hostCacheCount; n++)
438                                 if (Q_strcasecmp (host, hostcache[n].name) == 0)
439                                 {
440                                         host = hostcache[n].cname;
441                                         break;
442                                 }
443                         if (n < hostCacheCount)
444                                 goto JustDoIt;
445                 }
446         }
447
448         slistSilent = host ? true : false;
449         NET_Slist_f ();
450
451         while(slistInProgress)
452                 NET_Poll();
453
454         if (host == NULL)
455         {
456                 if (hostCacheCount != 1)
457                         return NULL;
458                 host = hostcache[0].cname;
459                 Con_Printf("Connecting to...\n%s @ %s\n\n", hostcache[0].name, host);
460         }
461
462         if (hostCacheCount)
463                 for (n = 0; n < hostCacheCount; n++)
464                         if (Q_strcasecmp (host, hostcache[n].name) == 0)
465                         {
466                                 host = hostcache[n].cname;
467                                 break;
468                         }
469
470 JustDoIt:
471         for (net_driverlevel=0 ; net_driverlevel<numdrivers; net_driverlevel++)
472         {
473                 if (net_drivers[net_driverlevel].initialized == false)
474                         continue;
475                 ret = dfunc.Connect (host);
476                 if (ret)
477                         return ret;
478         }
479
480         if (host)
481         {
482                 Con_Printf("\n");
483                 PrintSlistHeader();
484                 PrintSlist();
485                 PrintSlistTrailer();
486         }
487         
488         return NULL;
489 }
490
491
492 /*
493 ===================
494 NET_CheckNewConnections
495 ===================
496 */
497
498 qsocket_t *NET_CheckNewConnections (void)
499 {
500         qsocket_t       *ret;
501
502         SetNetTime();
503
504         for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
505         {
506                 if (net_drivers[net_driverlevel].initialized == false)
507                         continue;
508                 if (net_driverlevel && listening == false)
509                         continue;
510                 ret = dfunc.CheckNewConnections ();
511                 if (ret)
512                         return ret;
513         }
514
515         return NULL;
516 }
517
518 /*
519 ===================
520 NET_Close
521 ===================
522 */
523 void NET_Close (qsocket_t *sock)
524 {
525         if (!sock)
526                 return;
527
528         if (sock->disconnected)
529                 return;
530
531         SetNetTime();
532
533         // call the driver_Close function
534         sfunc.Close (sock);
535
536         NET_FreeQSocket(sock);
537 }
538
539
540 /*
541 =================
542 NET_GetMessage
543
544 If there is a complete message, return it in net_message
545
546 returns 0 if no data is waiting
547 returns 1 if a message was received
548 returns -1 if connection is invalid
549 =================
550 */
551
552 extern void PrintStats(qsocket_t *s);
553
554 int     NET_GetMessage (qsocket_t *sock)
555 {
556         int ret;
557
558         if (!sock)
559                 return -1;
560
561         if (sock->disconnected)
562         {
563                 Con_Printf("NET_GetMessage: disconnected socket\n");
564                 return -1;
565         }
566
567         SetNetTime();
568
569         ret = sfunc.QGetMessage(sock);
570
571         // see if this connection has timed out
572         if (ret == 0 && sock->driver)
573         {
574                 if (net_time - sock->lastMessageTime > net_messagetimeout.value)
575                 {
576                         NET_Close(sock);
577                         return -1;
578                 }
579         }
580
581
582         if (ret > 0)
583         {
584                 if (sock->driver)
585                 {
586                         sock->lastMessageTime = net_time;
587                         if (ret == 1)
588                                 messagesReceived++;
589                         else if (ret == 2)
590                                 unreliableMessagesReceived++;
591                 }
592         }
593
594         return ret;
595 }
596
597
598 /*
599 ==================
600 NET_SendMessage
601
602 Try to send a complete length+message unit over the reliable stream.
603 returns 0 if the message cannot be delivered reliably, but the connection
604                 is still considered valid
605 returns 1 if the message was sent properly
606 returns -1 if the connection died
607 ==================
608 */
609 int NET_SendMessage (qsocket_t *sock, sizebuf_t *data)
610 {
611         int             r;
612         
613         if (!sock)
614                 return -1;
615
616         if (sock->disconnected)
617         {
618                 Con_Printf("NET_SendMessage: disconnected socket\n");
619                 return -1;
620         }
621
622         SetNetTime();
623         r = sfunc.QSendMessage(sock, data);
624         if (r == 1 && sock->driver)
625                 messagesSent++;
626
627         return r;
628 }
629
630
631 int NET_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data)
632 {
633         int             r;
634         
635         if (!sock)
636                 return -1;
637
638         if (sock->disconnected)
639         {
640                 Con_Printf("NET_SendMessage: disconnected socket\n");
641                 return -1;
642         }
643
644         SetNetTime();
645         r = sfunc.SendUnreliableMessage(sock, data);
646         if (r == 1 && sock->driver)
647                 unreliableMessagesSent++;
648
649         return r;
650 }
651
652
653 /*
654 ==================
655 NET_CanSendMessage
656
657 Returns true or false if the given qsocket can currently accept a
658 message to be transmitted.
659 ==================
660 */
661 qboolean NET_CanSendMessage (qsocket_t *sock)
662 {
663         int             r;
664         
665         if (!sock)
666                 return false;
667
668         if (sock->disconnected)
669                 return false;
670
671         SetNetTime();
672
673         r = sfunc.CanSendMessage(sock);
674         
675         return r;
676 }
677
678
679 /*
680 ====================
681 NET_Heartbeat
682
683 Send an heartbeat to the master server(s)
684 ====================
685 */
686 void NET_Heartbeat (void)
687 {
688         char* host;
689         while ((host = Master_BuildHeartbeat ()) != NULL)
690         {
691                 for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
692                 {
693                         if (net_drivers[net_driverlevel].initialized == false)
694                                 continue;
695                         if (net_driverlevel && listening == false)
696                                 continue;
697                         dfunc.Heartbeat (host);
698                 }
699         }
700 }
701
702
703 int NET_SendToAll(sizebuf_t *data, int blocktime)
704 {
705         double          start;
706         int                     i;
707         int                     count = 0;
708         qboolean        state1 [MAX_SCOREBOARD];
709         qboolean        state2 [MAX_SCOREBOARD];
710
711         for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
712         {
713                 if (!host_client->netconnection)
714                         continue;
715                 if (host_client->active)
716                 {
717                         if (host_client->netconnection->driver == 0)
718                         {
719                                 NET_SendMessage(host_client->netconnection, data);
720                                 state1[i] = true;
721                                 state2[i] = true;
722                                 continue;
723                         }
724                         count++;
725                         state1[i] = false;
726                         state2[i] = false;
727                 }
728                 else
729                 {
730                         state1[i] = true;
731                         state2[i] = true;
732                 }
733         }
734
735         start = Sys_DoubleTime();
736         while (count)
737         {
738                 count = 0;
739                 for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
740                 {
741                         if (! state1[i])
742                         {
743                                 if (NET_CanSendMessage (host_client->netconnection))
744                                 {
745                                         state1[i] = true;
746                                         NET_SendMessage(host_client->netconnection, data);
747                                 }
748                                 else
749                                 {
750                                         NET_GetMessage (host_client->netconnection);
751                                 }
752                                 count++;
753                                 continue;
754                         }
755
756                         if (! state2[i])
757                         {
758                                 if (NET_CanSendMessage (host_client->netconnection))
759                                 {
760                                         state2[i] = true;
761                                 }
762                                 else
763                                 {
764                                         NET_GetMessage (host_client->netconnection);
765                                 }
766                                 count++;
767                                 continue;
768                         }
769                 }
770                 if ((Sys_DoubleTime() - start) > blocktime)
771                         break;
772         }
773         return count;
774 }
775
776
777 //=============================================================================
778
779 /*
780 ====================
781 NET_Init
782 ====================
783 */
784
785 void NET_Init (void)
786 {
787         int                     i;
788         int                     controlSocket;
789
790         i = COM_CheckParm ("-port");
791         if (!i)
792                 i = COM_CheckParm ("-udpport");
793         if (!i)
794                 i = COM_CheckParm ("-ipxport");
795
796         if (i)
797         {
798                 if (i < com_argc-1)
799                         DEFAULTnet_hostport = atoi (com_argv[i+1]);
800                 else
801                         Sys_Error ("NET_Init: you must specify a number after -port");
802         }
803         net_hostport = DEFAULTnet_hostport;
804
805         if (COM_CheckParm("-listen") || cls.state == ca_dedicated || gamemode == GAME_TRANSFUSION)
806                 listening = true;
807
808         SetNetTime();
809
810         net_mempool = Mem_AllocPool("qsocket");
811
812         // allocate space for network message buffer
813         SZ_Alloc (&net_message, NET_MAXMESSAGE, "net_message");
814
815         Cvar_RegisterVariable (&net_messagetimeout);
816         Cvar_RegisterVariable (&hostname);
817
818         Cmd_AddCommand ("net_slist", NET_Slist_f);
819         Cmd_AddCommand ("net_inetslist", NET_InetSlist_f);
820         Cmd_AddCommand ("listen", NET_Listen_f);
821         Cmd_AddCommand ("maxplayers", MaxPlayers_f);
822         Cmd_AddCommand ("port", NET_Port_f);
823         Cmd_AddCommand ("heartbeat", NET_Heartbeat_f);
824
825         // initialize all the drivers
826         for (net_driverlevel=0 ; net_driverlevel<net_numdrivers ; net_driverlevel++)
827                 {
828                 controlSocket = net_drivers[net_driverlevel].Init();
829                 if (controlSocket == -1)
830                         continue;
831                 net_drivers[net_driverlevel].initialized = true;
832                 net_drivers[net_driverlevel].controlSock = controlSocket;
833                 if (listening)
834                         net_drivers[net_driverlevel].Listen (true);
835                 }
836
837         if (*my_ipx_address)
838                 Con_DPrintf("IPX address %s\n", my_ipx_address);
839         if (*my_tcpip_address)
840                 Con_DPrintf("TCP/IP address %s\n", my_tcpip_address);
841
842         Master_Init ();
843 }
844
845 /*
846 ====================
847 NET_Shutdown
848 ====================
849 */
850
851 void NET_Shutdown (void)
852 {
853         SetNetTime();
854
855         while (net_activeSockets)
856                 NET_Close(net_activeSockets);
857
858 //
859 // shutdown the drivers
860 //
861         for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++)
862         {
863                 if (net_drivers[net_driverlevel].initialized == true)
864                 {
865                         net_drivers[net_driverlevel].Shutdown ();
866                         net_drivers[net_driverlevel].initialized = false;
867                 }
868         }
869
870         Mem_FreePool(&net_mempool);
871 }
872
873
874 static PollProcedure *pollProcedureList = NULL;
875
876 void NET_Poll(void)
877 {
878         PollProcedure *pp;
879
880         if (!configRestored)
881                 configRestored = true;
882
883         SetNetTime();
884
885         for (pp = pollProcedureList; pp; pp = pp->next)
886         {
887                 if (pp->nextTime > net_time)
888                         break;
889                 pollProcedureList = pp->next;
890                 pp->procedure(pp->arg);
891         }
892 }
893
894
895 void SchedulePollProcedure(PollProcedure *proc, double timeOffset)
896 {
897         PollProcedure *pp, *prev;
898
899         proc->nextTime = Sys_DoubleTime() + timeOffset;
900         for (pp = pollProcedureList, prev = NULL; pp; pp = pp->next)
901         {
902                 if (pp->nextTime >= proc->nextTime)
903                         break;
904                 prev = pp;
905         }
906
907         if (prev == NULL)
908         {
909                 proc->next = pollProcedureList;
910                 pollProcedureList = proc;
911                 return;
912         }
913
914         proc->next = pp;
915         prev->next = proc;
916 }
917