DEADSOFTWARE

12d926f937c309fc94ff6c7b186cbe4be50491e9
[d2df-sdl.git] / src / game / g_netmaster.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_netmaster;
18 interface
20 uses
21 ENet, SysUtils, e_msg;
23 const
24 NET_MCHANS = 2;
26 NET_MCHAN_MAIN = 0;
27 NET_MCHAN_UPD = 1;
29 NET_MMSG_UPD = 200;
30 NET_MMSG_DEL = 201;
31 NET_MMSG_GET = 202;
33 const
34 // all timeouts in seconds
35 NMASTER_TIMEOUT_CONNECT = 3; // 3 seconds
36 NMASTER_TIMEOUT_RECONNECT = 5*60; // 5 minutes
37 //NMASTER_TIMEOUT_RECONNECT = 30; // 5 minutes
38 //NMASTER_FORCE_UPDATE_TIMEOUT = 20;
39 //NMASTER_FORCE_UPDATE_TIMEOUT = 0;
41 type
42 TNetServer = record
43 Number: Byte;
44 Protocol: Byte;
45 Name: AnsiString;
46 IP: AnsiString;
47 Port: Word;
48 Map: AnsiString;
49 Players, MaxPlayers, LocalPl, Bots: Byte;
50 Ping: Int64;
51 GameMode: Byte;
52 Password: Boolean;
53 PingAddr: ENetAddress;
54 end;
55 pTNetServer = ^TNetServer;
56 TNetServerRow = record
57 Indices: Array of Integer;
58 Current: Integer;
59 end;
61 TNetServerList = array of TNetServer;
62 pTNetServerList = ^TNetServerList;
63 TNetServerTable = array of TNetServerRow;
65 type
66 TMasterHost = record
67 public
68 hostName: AnsiString;
70 public
71 peer: pENetPeer;
72 enetAddr: ENetAddress;
73 // inside the game, calling `connect()` is disasterous, as it is blocking.
74 // so we'll use this variable to indicate if "connected" event is received.
75 NetHostConnected: Boolean;
76 NetHostConReqTime: Int64; // to timeout `connect`; -1 means "waiting for shutdown"
77 NetUpdatePending: Boolean; // should we send an update after connection completes?
78 lastDisconnectTime: Int64; // last real disconnect time; <0: do not reconnect
79 updateSent: Boolean; // was at least one update sent? (used to decide if we should call `remove()`)
80 lastUpdateTime: Int64;
81 // server list request working flags
82 srvAnswered: Integer;
83 srvAnswer: array of TNetServer;
84 slMOTD: AnsiString;
85 slUrgent: AnsiString;
86 slReadUrgent: Boolean;
87 // temporary mark
88 justAdded: Boolean;
89 connectCount: Integer;
91 private
92 netmsg: TMsg;
94 public
95 constructor Create (var ea: ENetAddress);
97 procedure clear ();
99 function setAddress (var ea: ENetAddress; hostStr: AnsiString): Boolean;
101 function isValid (): Boolean;
102 function isAlive (): Boolean; // not disconnected
103 function isConnecting (): Boolean; // is connection in progress?
104 function isConnected (): Boolean;
106 // call as often as you want, the object will do the rest
107 // but try to call this at least once in 100 msecs
108 procedure pulse ();
110 procedure disconnect (forced: Boolean);
111 function connect (): Boolean;
113 procedure update ();
114 procedure remove ();
116 class procedure writeInfo (var msg: TMsg); static;
118 procedure connectedEvent ();
119 procedure disconnectedEvent ();
120 procedure receivedEvent (pkt: pENetPacket); // `pkt` is never `nil`
121 end;
124 var
125 slCurrent: TNetServerList = nil;
126 slTable: TNetServerTable = nil;
127 slWaitStr: AnsiString = '';
128 slReturnPressed: Boolean = True;
130 slMOTD: AnsiString = '';
131 slUrgent: AnsiString = '';
133 NMASTER_FORCE_UPDATE_TIMEOUT: Integer = 0; // fuck you, fpc, and your idiotic "diagnostics"
136 procedure g_Net_Slist_Set (list: AnsiString);
137 function g_Net_Slist_Fetch (var SL: TNetServerList): Boolean;
139 // make this server private
140 procedure g_Net_Slist_Private ();
141 // make this server public
142 procedure g_Net_Slist_Public ();
144 // called while the server is running
145 procedure g_Net_Slist_ServerUpdate ();
146 // called when the server is started
147 procedure g_Net_Slist_ServerStarted ();
148 // called when the server is stopped
149 procedure g_Net_Slist_ServerClosed ();
151 // called when new netword player comes
152 procedure g_Net_Slist_ServerPlayerComes ();
153 // called when new netword player comes
154 procedure g_Net_Slist_ServerPlayerLeaves ();
155 // started new map
156 procedure g_Net_Slist_ServerMapStarted ();
157 // this server renamed (or password mode changed, or other params changed)
158 procedure g_Net_Slist_ServerRenamed ();
160 // non-zero timeout ignores current status (used to fetch server list)
161 procedure g_Net_Slist_Pulse (timeout: Integer=0);
163 procedure g_Net_Slist_ShutdownAll ();
165 procedure g_Serverlist_GenerateTable (SL: TNetServerList; var ST: TNetServerTable);
166 procedure g_Serverlist_Control (var SL: TNetServerList; var ST: TNetServerTable);
168 function GetServerFromTable (Index: Integer; SL: TNetServerList; ST: TNetServerTable): TNetServer;
170 function GetTimerMS (): Int64;
172 var (* private state *)
173 slSelection: Byte = 0;
174 slReadUrgent: Boolean = False;
176 implementation
178 uses
179 {$IFDEF ENABLE_MENU}
180 g_gui, g_menu,
181 {$ENDIF}
182 {$IFNDEF HEADLESS}
183 r_render, g_system,
184 {$ENDIF}
185 e_input, e_log, g_net, g_console,
186 g_map, g_game, g_sound, g_options, g_language, g_basic,
187 wadreader, utils, hashtable;
190 // ////////////////////////////////////////////////////////////////////////// //
191 var
192 NetMHost: pENetHost = nil;
193 NetMEvent: ENetEvent;
194 mlist: array of TMasterHost = nil;
196 slFetched: Boolean = False;
197 slDirPressed: Boolean = False;
199 reportsEnabled: Boolean = true;
202 //==========================================================================
203 //
204 // GetTimerMS
205 //
206 //==========================================================================
207 function GetTimerMS (): Int64;
208 begin
209 Result := GetTickCount64() {div 1000};
210 end;
213 //==========================================================================
214 //
215 // findByPeer
216 //
217 //==========================================================================
218 function findByPeer (peer: pENetPeer): Integer;
219 var
220 f: Integer;
221 begin
222 for f := 0 to High(mlist) do if (mlist[f].peer = peer) then begin result := f; exit; end;
223 result := -1;
224 end;
227 //==========================================================================
228 //
229 // ShutdownAll
230 //
231 //==========================================================================
232 procedure g_Net_Slist_ShutdownAll ();
233 var
234 f, sres, idx: Integer;
235 stt, ct: Int64;
236 activeCount: Integer = 0;
237 begin
238 if (NetMHost = nil) then exit;
239 for f := 0 to High(mlist) do
240 begin
241 if (mlist[f].isAlive()) then
242 begin
243 Inc(activeCount);
244 if (mlist[f].isConnected() and mlist[f].updateSent) then
245 begin
246 writeln('unregistering from [', mlist[f].hostName, ']');
247 mlist[f].remove();
248 end;
249 //mlist[f].disconnect(false);
250 enet_peer_disconnect_later(mlist[f].peer, 0);
251 end;
252 end;
253 if (activeCount = 0) then exit;
254 stt := GetTimerMS();
255 while (activeCount > 0) do
256 begin
257 ct := GetTimerMS();
258 if (ct < stt) or (ct-stt >= 1500) then break;
260 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
261 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
262 // thank you, enet. let's ignore failures altogether then.
263 sres := enet_host_service(NetMHost, @NetMEvent, 100);
264 // if (sres < 0) then break;
265 if (sres <= 0) then continue;
267 idx := findByPeer(NetMEvent.peer);
268 if (idx < 0) then
269 begin
270 if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then enet_packet_destroy(NetMEvent.packet);
271 continue;
272 end;
274 if (NetMEvent.kind = ENET_EVENT_TYPE_CONNECT) then
275 begin
276 mlist[idx].connectedEvent();
277 //mlist[idx].disconnect(false);
278 enet_peer_disconnect(mlist[f].peer, 0);
279 end
280 else if (NetMEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
281 begin
282 mlist[idx].disconnectedEvent();
283 Dec(activeCount);
284 end
285 else if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
286 begin
287 mlist[idx].receivedEvent(NetMEvent.packet);
288 enet_packet_destroy(NetMEvent.packet);
289 end;
290 end;
291 enet_host_destroy(NetMHost);
292 NetMHost := nil;
293 end;
296 //==========================================================================
297 //
298 // DisconnectAll
299 //
300 //==========================================================================
301 procedure DisconnectAll (forced: Boolean=false);
302 var
303 f: Integer;
304 begin
305 for f := 0 to High(mlist) do
306 begin
307 if (mlist[f].isAlive()) then mlist[f].disconnect(forced);
308 end;
309 end;
312 //==========================================================================
313 //
314 // ConnectAll
315 //
316 //==========================================================================
317 procedure ConnectAll (sendUpdate: Boolean);
318 var
319 f: Integer;
320 begin
321 // set flags; pulse will take care of the rest
322 for f := 0 to High(mlist) do
323 begin
324 // force reconnect
325 mlist[f].lastDisconnectTime := 0;
326 // force updating
327 if (sendUpdate) then
328 begin
329 mlist[f].NetUpdatePending := true;
330 mlist[f].lastUpdateTime := 0;
331 end;
332 end;
333 end;
336 //==========================================================================
337 //
338 // UpdateAll
339 //
340 //==========================================================================
341 procedure UpdateAll (force: Boolean);
342 var
343 f: Integer;
344 begin
345 // set flags; pulse will take care of the rest
346 for f := 0 to High(mlist) do
347 begin
348 if (not mlist[f].isAlive()) then continue;
349 mlist[f].NetUpdatePending := true;
350 if (force) then mlist[f].lastUpdateTime := 0;
351 end;
352 end;
355 //**************************************************************************
356 //
357 // public api
358 //
359 //**************************************************************************
361 //==========================================================================
362 //
363 // g_Net_Slist_Private
364 //
365 // make this server private
366 //
367 //==========================================================================
368 procedure g_Net_Slist_Private ();
369 begin
370 DisconnectAll();
371 reportsEnabled := false;
372 end;
375 //==========================================================================
376 //
377 // g_Net_Slist_Public
378 //
379 // make this server public
380 //
381 //==========================================================================
382 procedure g_Net_Slist_Public ();
383 begin
384 if (not reportsEnabled) then
385 begin
386 reportsEnabled := true;
387 ConnectAll(true);
388 end;
389 end;
392 //==========================================================================
393 //
394 // g_Net_Slist_ServerUpdate
395 //
396 // called while the server is running
397 //
398 //==========================================================================
399 procedure g_Net_Slist_ServerUpdate ();
400 begin
401 UpdateAll(false);
402 end;
405 // called when the server is started
406 procedure g_Net_Slist_ServerStarted ();
407 begin
408 reportsEnabled := NetUseMaster;
409 if reportsEnabled and g_Game_IsServer() and g_Game_IsNet() then
410 begin
411 writeln('*** server started; reporting to master...');
412 ConnectAll(true);
413 end;
414 end;
417 //==========================================================================
418 //
419 // g_Net_Slist_ServerClosed
420 //
421 // called when the server is stopped
422 //
423 //==========================================================================
424 procedure g_Net_Slist_ServerClosed ();
425 var
426 f: Integer;
427 begin
428 if reportsEnabled then
429 begin
430 reportsEnabled := false;
431 for f := 0 to High(mlist) do
432 begin
433 if (mlist[f].isConnected()) then mlist[f].remove();
434 end;
435 end;
436 DisconnectAll();
437 end;
440 //==========================================================================
441 //
442 // g_Net_Slist_ServerPlayerComes
443 //
444 // called when new netword player comes
445 //
446 //==========================================================================
447 procedure g_Net_Slist_ServerPlayerComes ();
448 begin
449 UpdateAll(true);
450 end;
453 //==========================================================================
454 //
455 // g_Net_Slist_ServerPlayerLeaves
456 //
457 // called when new netword player comes
458 //
459 //==========================================================================
460 procedure g_Net_Slist_ServerPlayerLeaves ();
461 begin
462 UpdateAll(true);
463 end;
466 //==========================================================================
467 //
468 // g_Net_Slist_ServerMapStarted
469 //
470 // started new map
471 //
472 //==========================================================================
473 procedure g_Net_Slist_ServerMapStarted ();
474 begin
475 UpdateAll(true);
476 end;
479 //==========================================================================
480 //
481 // g_Net_Slist_ServerRenamed
482 //
483 // this server renamed (or password mode changed, or other params changed)
484 //
485 //==========================================================================
486 procedure g_Net_Slist_ServerRenamed ();
487 begin
488 UpdateAll(true);
489 end;
492 //**************************************************************************
493 //
494 // TMasterHost
495 //
496 //**************************************************************************
498 //==========================================================================
499 //
500 // TMasterHost.Create
501 //
502 //==========================================================================
503 constructor TMasterHost.Create (var ea: ENetAddress);
504 begin
505 peer := nil;
506 NetHostConnected := false;
507 NetHostConReqTime := 0;
508 NetUpdatePending := false;
509 lastDisconnectTime := 0;
510 updateSent := false;
511 lastUpdateTime := 0;
512 hostName := '';
513 ZeroMemory(@enetAddr, sizeof(enetAddr));
514 SetLength(srvAnswer, 0);
515 srvAnswered := 0;
516 slMOTD := '';
517 slUrgent := '';
518 slReadUrgent := true;
519 justAdded := false;
520 connectCount := 0;
521 netmsg.Alloc(NET_BUFSIZE);
522 setAddress(ea, '');
523 end;
526 //==========================================================================
527 //
528 // TMasterHost.clear
529 //
530 //==========================================================================
531 procedure TMasterHost.clear ();
532 begin
533 updateSent := false; // do not send 'remove'
534 disconnect(true);
535 hostName := '';
536 netmsg.Free();
537 SetLength(srvAnswer, 0);
538 srvAnswered := 0;
539 slMOTD := '';
540 slUrgent := '';
541 slReadUrgent := true;
542 ZeroMemory(@enetAddr, sizeof(enetAddr));
543 end;
546 //==========================================================================
547 //
548 // TMasterHost.setAddress
549 //
550 //==========================================================================
551 function TMasterHost.setAddress (var ea: ENetAddress; hostStr: AnsiString): Boolean;
552 begin
553 result := false;
554 SetLength(srvAnswer, 0);
555 srvAnswered := 0;
556 slMOTD := '';
557 slUrgent := '';
558 slReadUrgent := true;
559 updateSent := false; // do not send 'remove'
560 disconnect(true);
561 hostName := '';
563 if (not g_Net_IsNetworkAvailable()) then exit;
565 enetAddr := ea;
566 if (enetAddr.host = 0) or (enetAddr.port = 0) then exit;
568 if (length(hostStr) > 0) then hostName := hostStr else hostName := IntToStr(enetAddr.host)+':'+IntToStr(ea.port);
570 result := isValid();
571 end;
574 //==========================================================================
575 //
576 // TMasterHost.isValid
577 //
578 //==========================================================================
579 function TMasterHost.isValid (): Boolean;
580 begin
581 result := (enetAddr.host <> 0) and (enetAddr.port <> 0);
582 end;
585 //==========================================================================
586 //
587 // TMasterHost.isAlive
588 //
589 // not disconnected
590 //
591 //==========================================================================
592 function TMasterHost.isAlive (): Boolean;
593 begin
594 result := (NetMHost <> nil) and (peer <> nil);
595 end;
598 //==========================================================================
599 //
600 // TMasterHost.isConnecting
601 //
602 // is connection in progress?
603 //
604 //==========================================================================
605 function TMasterHost.isConnecting (): Boolean;
606 begin
607 result := isAlive() and (not NetHostConnected) and (NetHostConReqTime <> -1);
608 end;
611 //==========================================================================
612 //
613 // TMasterHost.isConnected
614 //
615 //==========================================================================
616 function TMasterHost.isConnected (): Boolean;
617 begin
618 result := isAlive() and (NetHostConnected) and (NetHostConReqTime <> -1);
619 end;
622 //==========================================================================
623 //
624 // TMasterHost.connectedEvent
625 //
626 //==========================================================================
627 procedure TMasterHost.connectedEvent ();
628 begin
629 if not isAlive() then exit;
630 if NetHostConnected then exit;
631 NetHostConnected := true;
632 NetHostConReqTime := 0; // just in case
633 e_LogWritefln('connected to master at [%s]', [hostName], TMsgType.Notify);
634 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_CONN], [mlist[f].hostName]));
635 end;
638 //==========================================================================
639 //
640 // TMasterHost.disconnectedEvent
641 //
642 //==========================================================================
643 procedure TMasterHost.disconnectedEvent ();
644 begin
645 if not isAlive() then exit;
646 e_LogWritefln('disconnected from master at [%s]', [hostName], TMsgType.Notify);
647 disconnect(true);
648 //if (spamConsole) then g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_DISC], [hostName]));
649 end;
652 //==========================================================================
653 //
654 // TMasterHost.receivedEvent
655 //
656 // `pkt` is never `nil`
657 //
658 //==========================================================================
659 procedure TMasterHost.receivedEvent (pkt: pENetPacket);
660 var
661 msg: TMsg;
662 MID: Byte;
663 Cnt: Byte;
664 f: Integer;
665 s: AnsiString;
666 begin
667 e_LogWritefln('received packed from master at [%s]', [hostName], TMsgType.Notify);
668 if not msg.Init(pkt^.data, pkt^.dataLength, True) then exit;
669 // packet type
670 MID := msg.ReadByte();
671 if (MID <> NET_MMSG_GET) then exit;
672 e_LogWritefln('received list packet from master at [%s]', [hostName], TMsgType.Notify);
673 SetLength(srvAnswer, 0);
674 if (srvAnswered > 0) then Inc(srvAnswered);
675 slMOTD := '';
676 //slUrgent := '';
677 slReadUrgent := true;
678 // number of items
679 Cnt := msg.ReadByte();
680 //g_Console_Add(_lc[I_NET_MSG]+Format(_lc[I_NET_SLIST_RETRIEVED], [Cnt, hostName]), True);
681 e_LogWritefln('got %u server(s) from master at [%s]', [Cnt, hostName], TMsgType.Notify);
682 if (Cnt > 0) then
683 begin
684 SetLength(srvAnswer, Cnt);
685 for f := 0 to Cnt-1 do
686 begin
687 srvAnswer[f].Number := f;
688 srvAnswer[f].IP := msg.ReadString();
689 srvAnswer[f].Port := msg.ReadWord();
690 srvAnswer[f].Name := msg.ReadString();
691 srvAnswer[f].Map := msg.ReadString();
692 srvAnswer[f].GameMode := msg.ReadByte();
693 srvAnswer[f].Players := msg.ReadByte();
694 srvAnswer[f].MaxPlayers := msg.ReadByte();
695 srvAnswer[f].Protocol := msg.ReadByte();
696 srvAnswer[f].Password := msg.ReadByte() = 1;
697 enet_address_set_host(Addr(srvAnswer[f].PingAddr), PChar(Addr(srvAnswer[f].IP[1])));
698 srvAnswer[f].Ping := -1;
699 srvAnswer[f].PingAddr.port := NET_PING_PORT;
700 end;
701 end;
703 if (msg.ReadCount < msg.CurSize) then
704 begin
705 // new master, supports version reports
706 s := msg.ReadString();
707 if (s <> {MyVer}GAME_VERSION) then
708 begin
709 { TODO }
710 g_Console_Add('!!! UpdVer = `'+s+'`');
711 end;
712 // even newer master, supports extra info
713 if (msg.ReadCount < msg.CurSize) then
714 begin
715 slMOTD := b_Text_Format(msg.ReadString());
716 if (slMOTD <> '') then e_LogWritefln('got MOTD from master at [%s]: %s', [hostName, slMOTD], TMsgType.Notify);
717 s := b_Text_Format(msg.ReadString());
718 // check if the message has updated and the user has to read it again
719 if (slUrgent <> s) then slReadUrgent := false;
720 slUrgent := s;
721 if (s <> '') then e_LogWritefln('got urgent from master at [%s]: %s', [hostName, s], TMsgType.Notify);
722 end;
723 end;
724 end;
727 //==========================================================================
728 //
729 // TMasterHost.disconnect
730 //
731 //==========================================================================
732 procedure TMasterHost.disconnect (forced: Boolean);
733 begin
734 if isAlive() then
735 begin
736 lastDisconnectTime := GetTimerMS();
737 if forced or (not NetHostConnected) or (NetHostConReqTime = -1) then
738 begin
739 enet_peer_reset(peer);
740 peer := nil;
741 NetHostConReqTime := 0;
742 updateSent := false;
743 end
744 else
745 begin
746 enet_peer_disconnect_later(peer, 0);
747 // main pulse will take care of the rest
748 NetHostConReqTime := -1;
749 end;
750 end
751 else
752 begin
753 // just in case
754 NetHostConReqTime := 0;
755 updateSent := false;
756 end;
758 NetHostConnected := false;
759 NetUpdatePending := false;
760 lastUpdateTime := 0;
761 //if (spamConsole) then g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_DISC], [hostName]));
762 end;
765 //==========================================================================
766 //
767 // TMasterHost.connect
768 //
769 //==========================================================================
770 function TMasterHost.connect (): Boolean;
771 begin
772 result := false;
773 if not isValid() then exit;
774 if (NetHostConReqTime = -1) then
775 begin
776 disconnect(true);
777 if (NetHostConReqTime = -1) then e_LogWritefln('ketmar broke master [%s] logic! (000)', [hostName], TMsgType.Notify);
778 if (isAlive()) then e_LogWritefln('ketmar broke master [%s] logic! (001)', [hostName], TMsgType.Notify);
779 end
780 else
781 begin
782 if isAlive() then begin result := true; exit; end;
783 end;
785 lastDisconnectTime := GetTimerMS(); // why not?
786 SetLength(srvAnswer, 0);
787 srvAnswered := 0;
788 NetHostConnected := false;
789 NetHostConReqTime := 0;
790 NetUpdatePending := false;
791 updateSent := false;
792 lastUpdateTime := 0;
793 Inc(connectCount);
795 peer := enet_host_connect(NetMHost, @enetAddr, NET_MCHANS, 0);
796 if (peer = nil) then
797 begin
798 g_Console_Add(_lc[I_NET_MSG_ERROR]+_lc[I_NET_ERR_CLIENT], true);
799 exit;
800 end;
802 NetHostConReqTime := lastDisconnectTime;
803 e_LogWritefln('connecting to master at [%s]', [hostName], TMsgType.Notify);
804 end;
807 //==========================================================================
808 //
809 // TMasterHost.writeInfo
810 //
811 //==========================================================================
812 class procedure TMasterHost.writeInfo (var msg: TMsg);
813 var
814 wad, map: AnsiString;
815 begin
816 wad := g_ExtractWadNameNoPath(gMapInfo.Map);
817 map := g_ExtractFileName(gMapInfo.Map);
819 msg.Write(NetServerName);
821 msg.Write(wad+':/'+map);
822 msg.Write(gGameSettings.GameMode);
824 msg.Write(Byte(NetClientCount));
826 msg.Write(NetMaxClients);
828 msg.Write(Byte(NET_PROTOCOL_VER));
829 msg.Write(Byte(NetPassword <> ''));
830 end;
833 //==========================================================================
834 //
835 // TMasterHost.update
836 //
837 //==========================================================================
838 procedure TMasterHost.update ();
839 var
840 pkt: pENetPacket;
841 begin
842 if not isAlive() then exit;
843 if not isConnected() then
844 begin
845 NetUpdatePending := isConnecting();
846 exit;
847 end;
849 netmsg.Clear();
851 if reportsEnabled and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster then
852 begin
853 try
854 netmsg.Write(Byte(NET_MMSG_UPD));
855 netmsg.Write(NetAddr.port);
856 //writeln(formatstrf('%08x', [NetAddr.host]), ' : ', NetAddr.host);
858 writeInfo(netmsg);
860 pkt := enet_packet_create(netmsg.Data, netmsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
861 if assigned(pkt) then
862 begin
863 if (enet_peer_send(peer, NET_MCHAN_UPD, pkt) = 0) then
864 begin
865 e_LogWritefln('sent update to master at [%s]', [hostName], TMsgType.Notify);
866 NetUpdatePending := false;
867 updateSent := true;
868 end;
869 end;
870 finally
871 netmsg.Clear();
872 end;
873 end
874 else
875 begin
876 NetUpdatePending := false;
877 end;
878 end;
881 //==========================================================================
882 //
883 // TMasterHost.remove
884 //
885 //==========================================================================
886 procedure TMasterHost.remove ();
887 var
888 pkt: pENetPacket;
889 begin
890 NetUpdatePending := false;
891 lastUpdateTime := 0;
892 updateSent := false;
893 if not isAlive() then exit;
894 if not isConnected() then exit;
896 netmsg.Clear();
897 try
898 netmsg.Write(Byte(NET_MMSG_DEL));
899 netmsg.Write(NetAddr.port);
901 pkt := enet_packet_create(netmsg.Data, netmsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
902 if assigned(pkt) then
903 begin
904 enet_peer_send(peer, NET_MCHAN_MAIN, pkt);
905 end;
906 finally
907 netmsg.Clear();
908 end;
909 end;
912 //==========================================================================
913 //
914 // TMasterHost.pulse
915 //
916 // this performs various scheduled tasks, if necessary
917 //
918 //==========================================================================
919 procedure TMasterHost.pulse ();
920 var
921 ct: Int64;
922 mrate: Cardinal;
923 begin
924 if not isAlive() then exit;
925 if (NetHostConReqTime = -1) then exit; // waiting for shutdown (disconnect in progress)
926 ct := GetTimerMS();
927 // process pending connection timeout
928 if (not NetHostConnected) then
929 begin
930 if (ct < NetHostConReqTime) or (ct-NetHostConReqTime >= 1000*NMASTER_TIMEOUT_CONNECT) then
931 begin
932 e_LogWritefln('failed to connect to master at [%s]', [hostName], TMsgType.Notify);
933 // do not spam with error messages, it looks like the master is down
934 //g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR], True);
935 disconnect(true);
936 end;
937 exit;
938 end;
939 // send update, if necessary
940 if (NetUpdatePending) then
941 begin
942 mrate := NetMasterRate;
943 if (mrate < 10000) then mrate := 10000
944 else if (mrate > 1000*60*10) then mrate := 1000*60*10;
945 if (NMASTER_FORCE_UPDATE_TIMEOUT > 0) then mrate := NMASTER_FORCE_UPDATE_TIMEOUT*1000;
946 if (lastUpdateTime = 0) or (ct < lastUpdateTime) or (ct-lastUpdateTime >= mrate) then
947 begin
948 //e_LogWritefln('update timeout: %d', [Integer(mrate)], TMsgType.Notify);
949 lastUpdateTime := ct;
950 update();
951 end;
952 end;
953 end;
956 //**************************************************************************
957 //
958 // other functions
959 //
960 //**************************************************************************
961 type
962 THashStrDWord = specialize THashBase<AnsiString, LongWord, THashKeyStrAnsiCI>;
964 var
965 knownHosts: THashStrDWord = nil;
968 //==========================================================================
969 //
970 // parseAddressPort
971 //
972 //==========================================================================
973 function parseAddressPort (var ea: ENetAddress; hostandport: AnsiString): Boolean;
974 var
975 cp, port: Integer;
976 hostName: AnsiString;
977 ip: LongWord;
978 begin
979 result := false;
980 if (not g_Net_IsNetworkAvailable()) then exit;
982 hostandport := Trim(hostandport);
983 if (length(hostandport) = 0) then exit;
985 hostName := hostandport;
986 port := 25665;
988 cp := Pos(':', hostandport);
989 if (cp > 0) then
990 begin
991 hostName := Trim(Copy(hostandport, 1, cp-1));
992 Delete(hostandport, 1, cp);
993 hostandport := Trim(hostandport);
994 if (length(hostandport) > 0) then
995 begin
996 try
997 port := StrToInt(hostandport);
998 except
999 port := -1;
1000 end;
1001 end;
1002 end;
1004 if (length(hostName) = 0) then exit;
1005 if (port < 1) or (port > 65535) then exit;
1007 if not assigned(knownHosts) then knownHosts := THashStrDWord.Create();
1009 if knownHosts.get(hostName, ip) then
1010 begin
1011 ea.host := ip;
1012 end
1013 else
1014 begin
1015 if (enet_address_set_host(@ea, PChar(Addr(hostName[1]))) <> 0) then
1016 begin
1017 knownHosts.put(hostName, 0);
1018 exit;
1019 end;
1020 knownHosts.put(hostName, ea.host);
1021 end;
1022 ea.Port := port;
1023 result := true;
1024 end;
1027 //==========================================================================
1028 //
1029 // addMasterRecord
1030 //
1031 //==========================================================================
1032 procedure addMasterRecord (var ea: ENetAddress; sa: AnsiString);
1033 var
1034 f: Integer;
1035 freeIdx: Integer;
1036 begin
1037 freeIdx := -1;
1038 for f := 0 to High(mlist) do
1039 begin
1040 if (mlist[f].enetAddr.host = ea.host) and (mlist[f].enetAddr.port = ea.port) then
1041 begin
1042 mlist[f].justAdded := true;
1043 exit;
1044 end;
1045 if (freeIdx < 0) and (not mlist[f].isValid()) then freeIdx := f;
1046 end;
1047 if (freeIdx < 0) then
1048 begin
1049 freeIdx := length(mlist);
1050 SetLength(mlist, freeIdx+1);
1051 mlist[freeIdx].Create(ea);
1052 end;
1053 mlist[freeIdx].justAdded := true;
1054 mlist[freeIdx].setAddress(ea, sa);
1055 e_LogWritefln('added masterserver with address [%s]', [sa], TMsgType.Notify);
1056 end;
1059 //==========================================================================
1060 //
1061 // g_Net_Slist_Set
1062 //
1063 //==========================================================================
1064 procedure g_Net_Slist_Set (list: AnsiString);
1065 var
1066 f, dest: Integer;
1067 sa: AnsiString;
1068 ea: ENetAddress;
1069 pp: Integer;
1070 begin
1071 if (not g_Net_IsNetworkAvailable()) then exit;
1073 for f := 0 to High(mlist) do mlist[f].justAdded := false;
1075 list := Trim(list);
1076 //writeln('list=[', list, ']');
1077 while (length(list) > 0) do
1078 begin
1079 pp := Pos(',', list);
1080 if (pp < 1) then pp := length(list)+1;
1081 sa := Trim(Copy(list, 1, pp-1));
1082 Delete(list, 1, pp);
1083 //writeln(' sa=[', sa, ']');
1084 if (length(sa) > 0) and parseAddressPort(ea, sa) then addMasterRecord(ea, sa);
1085 end;
1087 // remove unknown master servers
1088 dest := 0;
1089 for f := 0 to High(mlist) do
1090 begin
1091 if (not mlist[f].justAdded) then mlist[f].clear();
1092 if (mlist[f].isValid()) then
1093 begin
1094 if (dest <> f) then mlist[dest] := mlist[f];
1095 Inc(dest);
1096 end;
1097 end;
1098 if (dest <> length(mlist)) then SetLength(mlist, dest);
1099 end;
1102 //**************************************************************************
1103 //
1104 // main pulse
1105 //
1106 //**************************************************************************
1108 //==========================================================================
1109 //
1110 // isMasterReportsEnabled
1111 //
1112 //==========================================================================
1113 function isMasterReportsEnabled (): Boolean;
1114 begin
1115 result := (reportsEnabled and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster);
1116 end;
1119 //==========================================================================
1120 //
1121 // g_Net_Slist_Pulse
1122 //
1123 // non-zero timeout ignores current status (used to fetch server list)
1124 //
1125 //==========================================================================
1126 procedure g_Net_Slist_Pulse (timeout: Integer=0);
1127 var
1128 f: Integer;
1129 sres: Integer;
1130 idx: Integer;
1131 ct: Int64;
1132 isListQuery: Boolean;
1133 count: Integer;
1134 begin
1135 if (not g_Net_IsNetworkAvailable()) then exit;
1137 if (length(mlist) = 0) then
1138 begin
1139 if (NetMHost <> nil) then
1140 begin
1141 enet_host_destroy(NetMHost);
1142 NetMHost := nil;
1143 exit;
1144 end;
1145 end;
1147 if (NetMHost = nil) then
1148 begin
1149 NetMHost := enet_host_create(nil, 64, NET_MCHANS, 1024*1024, 1024*1024);
1150 if (NetMHost = nil) then
1151 begin
1152 e_LogWriteln(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT] + ' (host_create)', TMsgType.Notify);
1153 for f := 0 to High(mlist) do mlist[f].clear();
1154 SetLength(mlist, 0);
1155 Exit;
1156 end;
1157 end;
1159 isListQuery := (timeout > 0);
1160 ct := GetTimerMS();
1161 // reconnect/disconnect/pulse for each master
1162 for f := 0 to High(mlist) do
1163 begin
1164 if (not mlist[f].isValid()) then continue;
1165 if (not mlist[f].isAlive()) then
1166 begin
1167 // not connected; try to reconnect if we're asking for a host list, or we are in netgame, and we are the host
1168 if (not isListQuery) and isMasterReportsEnabled() then
1169 begin
1170 if (mlist[f].lastDisconnectTime = 0) or (ct < mlist[f].lastDisconnectTime) or (ct-mlist[f].lastDisconnectTime >= 1000*NMASTER_TIMEOUT_RECONNECT) then
1171 begin
1172 e_LogWritefln('reconnecting to master [%s]', [mlist[f].hostName], TMsgType.Notify);
1173 mlist[f].connect();
1174 end
1175 else
1176 begin
1177 //e_LogWritefln('DEAD master [%s]: ct=%d; ldt=%d; diff=%d', [mlist[f].hostName, Integer(ct), Integer(mlist[f].lastDisconnectTime), Integer(ct-mlist[f].lastDisconnectTime)], TMsgType.Notify);
1178 end;
1179 end;
1180 end
1181 else
1182 begin
1183 // if we're not in slist query, and not in netgame (or not a host), disconnect
1184 if (not isListQuery) and (not isMasterReportsEnabled()) then
1185 begin
1186 if (mlist[f].isConnected()) and (mlist[f].updateSent) then
1187 begin
1188 e_LogWritefln('removing from master [%s]', [mlist[f].hostName], TMsgType.Notify);
1189 mlist[f].remove();
1190 end;
1191 e_LogWritefln('disconnecting from master [%s]', [mlist[f].hostName], TMsgType.Notify);
1192 mlist[f].disconnect(false);
1193 end;
1194 end;
1195 mlist[f].pulse();
1196 end;
1198 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
1199 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
1200 // thank you, enet. let's ignore failures altogether then.
1201 count := 10; // no more than ten events in a row
1202 sres := enet_host_service(NetMHost, @NetMEvent, timeout);
1203 while (sres > 0) do
1204 begin
1206 if (sres < 0) then
1207 begin
1208 e_LogWriteln(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT] + ' (host_service)', TMsgType.Notify);
1209 for f := 0 to High(mlist) do mlist[f].clear();
1210 SetLength(mlist, 0);
1211 enet_host_destroy(NetMHost);
1212 NetMHost := nil;
1213 exit;
1214 end;
1217 idx := findByPeer(NetMEvent.peer);
1218 if (idx < 0) then
1219 begin
1220 e_LogWriteln('network event from unknown master host. ignored.', TMsgType.Warning);
1221 if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then enet_packet_destroy(NetMEvent.packet);
1222 end
1223 else
1224 begin
1225 if (NetMEvent.kind = ENET_EVENT_TYPE_CONNECT) then
1226 begin
1227 mlist[idx].connectedEvent();
1228 end
1229 else if (NetMEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
1230 begin
1231 mlist[idx].disconnectedEvent();
1232 end
1233 else if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
1234 begin
1235 mlist[idx].receivedEvent(NetMEvent.packet);
1236 enet_packet_destroy(NetMEvent.packet);
1237 end;
1238 end;
1240 Dec(count);
1241 if (count = 0) then break;
1242 sres := enet_host_service(NetMHost, @NetMEvent, 0);
1243 end;
1244 end;
1247 //**************************************************************************
1248 //
1249 // gui and server list
1250 //
1251 //**************************************************************************
1253 //==========================================================================
1254 //
1255 // PingServer
1256 //
1257 //==========================================================================
1258 procedure PingServer (var S: TNetServer; Sock: ENetSocket);
1259 var
1260 Buf: ENetBuffer;
1261 Ping: array [0..9] of Byte;
1262 ClTime: Int64;
1263 begin
1264 ClTime := GetTimerMS();
1266 Buf.data := Addr(Ping[0]);
1267 Buf.dataLength := 2+8;
1269 Ping[0] := Ord('D');
1270 Ping[1] := Ord('F');
1271 Int64(Addr(Ping[2])^) := ClTime;
1273 enet_socket_send(Sock, Addr(S.PingAddr), @Buf, 1);
1274 end;
1277 //==========================================================================
1278 //
1279 // PingBcast
1280 //
1281 //==========================================================================
1282 procedure PingBcast (Sock: ENetSocket);
1283 var
1284 S: TNetServer;
1285 begin
1286 S.IP := '255.255.255.255';
1287 S.Port := NET_PING_PORT;
1288 enet_address_set_host(Addr(S.PingAddr), PChar(Addr(S.IP[1])));
1289 S.Ping := -1;
1290 S.PingAddr.port := S.Port;
1291 PingServer(S, Sock);
1292 end;
1295 //==========================================================================
1296 //
1297 // g_Net_Slist_Fetch
1298 //
1299 //==========================================================================
1300 function g_Net_Slist_Fetch (var SL: TNetServerList): Boolean;
1301 var
1302 Cnt: Byte;
1303 pkt: pENetPacket;
1304 I, RX: Integer;
1305 T: Int64;
1306 Sock: ENetSocket;
1307 Buf: ENetBuffer;
1308 InMsg: TMsg;
1309 SvAddr: ENetAddress;
1310 FromSL: Boolean;
1311 MyVer: AnsiString;
1313 procedure ProcessLocal ();
1314 begin
1315 I := Length(SL);
1316 SetLength(SL, I + 1);
1317 with SL[I] do
1318 begin
1319 IP := DecodeIPV4(SvAddr.host);
1320 Port := InMsg.ReadWord();
1321 Ping := InMsg.ReadInt64();
1322 Ping := GetTimerMS() - Ping;
1323 Name := InMsg.ReadString();
1324 Map := InMsg.ReadString();
1325 GameMode := InMsg.ReadByte();
1326 Players := InMsg.ReadByte();
1327 MaxPlayers := InMsg.ReadByte();
1328 Protocol := InMsg.ReadByte();
1329 Password := InMsg.ReadByte() = 1;
1330 LocalPl := InMsg.ReadByte();
1331 Bots := InMsg.ReadWord();
1332 end;
1333 end;
1335 procedure CheckLocalServers ();
1336 begin
1337 SetLength(SL, 0);
1339 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1340 if Sock = ENET_SOCKET_NULL then Exit;
1341 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
1342 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
1343 PingBcast(Sock);
1345 T := GetTimerMS();
1347 InMsg.Alloc(NET_BUFSIZE);
1348 Buf.data := InMsg.Data;
1349 Buf.dataLength := InMsg.MaxSize;
1350 while GetTimerMS() - T <= 500 do
1351 begin
1352 InMsg.Clear();
1354 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
1355 if RX <= 0 then continue;
1356 InMsg.CurSize := RX;
1358 InMsg.BeginReading();
1360 if InMsg.ReadChar() <> 'D' then continue;
1361 if InMsg.ReadChar() <> 'F' then continue;
1363 ProcessLocal();
1364 end;
1366 InMsg.Free();
1367 enet_socket_destroy(Sock);
1369 if Length(SL) = 0 then SL := nil;
1370 end;
1372 var
1373 f, c, n, pos: Integer;
1374 aliveCount: Integer;
1375 hasUnanswered: Boolean;
1376 stt, ct: Int64;
1377 tmpsv: TNetServer;
1378 begin
1379 result := false;
1380 SL := nil;
1382 if (not g_Net_IsNetworkAvailable()) then
1383 begin
1384 SetLength(SL, 0);
1385 exit;
1386 end;
1388 g_Net_Slist_Pulse(); // this will create mhost
1390 DisconnectAll(true); // forced disconnect
1392 for f := 0 to High(mlist) do
1393 begin
1394 mlist[f].connectCount := 0;
1395 mlist[f].srvAnswered := 0;
1396 end;
1398 NetOut.Clear();
1399 NetOut.Write(Byte(NET_MMSG_GET));
1401 // TODO: what should we identify the build with?
1402 MyVer := GAME_VERSION;
1403 NetOut.Write(MyVer);
1405 try
1406 e_WriteLog('Fetching serverlist...', TMsgType.Notify);
1407 g_Console_Add(_lc[I_NET_MSG]+_lc[I_NET_SLIST_FETCH]);
1409 // wait until all servers connected and answered
1410 stt := GetTimerMS();
1411 while true do
1412 begin
1413 aliveCount := 0;
1414 hasUnanswered := false;
1415 for f := 0 to High(mlist) do
1416 begin
1418 e_LogWritefln(' master #%d: [%s] valid=%d; alive=%d; connected=%d; connecting=%d',
1419 [f, mlist[f].hostName, Integer(mlist[f].isValid()), Integer(mlist[f].isAlive()),
1420 Integer(mlist[f].isConnected()), Integer(mlist[f].isConnecting())], TMsgType.Notify);
1422 if (not mlist[f].isValid()) then continue;
1423 if (not mlist[f].isAlive()) then
1424 begin
1425 if (mlist[f].connectCount = 0) then
1426 begin
1427 mlist[f].connect();
1428 if (mlist[f].isAlive()) then
1429 begin
1430 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_WCONN], [mlist[f].hostName]));
1431 hasUnanswered := true;
1432 stt := GetTimerMS();
1433 end;
1434 end
1435 else if (mlist[f].srvAnswered > 1) then
1436 begin
1437 Inc(aliveCount);
1438 end;
1439 end
1440 else if (mlist[f].isConnected()) then
1441 begin
1442 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_CONN], [mlist[f].hostName]));
1443 if (mlist[f].srvAnswered = 0) then
1444 begin
1445 pkt := enet_packet_create(NetOut.Data, NetOut.CurSize, Cardinal(ENET_PACKET_FLAG_RELIABLE));
1446 if assigned(pkt) then
1447 begin
1448 if (enet_peer_send(mlist[f].peer, NET_MCHAN_MAIN, pkt) = 0) then
1449 begin
1450 hasUnanswered := true;
1451 mlist[f].srvAnswered := 1;
1452 stt := GetTimerMS();
1453 end;
1454 end;
1455 end
1456 else if (mlist[f].srvAnswered = 1) then
1457 begin
1458 hasUnanswered := true;
1459 end
1460 else if (mlist[f].srvAnswered > 1) then
1461 begin
1462 Inc(aliveCount);
1463 mlist[f].disconnect(false); // not forced
1464 end;
1465 end
1466 else if (mlist[f].isConnecting()) then
1467 begin
1468 hasUnanswered := true;
1469 end;
1470 end;
1471 if (not hasUnanswered) then break;
1472 // check for timeout
1473 ct := GetTimerMS();
1474 if (ct < stt) or (ct-stt > 4000) then break;
1475 g_Net_Slist_Pulse(300);
1476 end;
1478 if (aliveCount = 0) then
1479 begin
1480 DisconnectAll();
1481 CheckLocalServers();
1482 exit;
1483 end;
1485 slMOTD := '';
1487 slUrgent := '';
1488 slReadUrgent := true;
1491 SetLength(SL, 0);
1492 for f := 0 to High(mlist) do
1493 begin
1494 if (mlist[f].srvAnswered < 2) then continue;
1495 for n := 0 to High(mlist[f].srvAnswer) do
1496 begin
1497 pos := -1;
1498 for c := 0 to High(SL) do
1499 begin
1500 if (SL[c].IP = mlist[f].srvAnswer[n].IP) and (SL[c].Port = mlist[f].srvAnswer[n].Port) then
1501 begin
1502 pos := c;
1503 break;
1504 end;
1505 end;
1506 if (pos < 0) then
1507 begin
1508 pos := length(SL);
1509 SetLength(SL, pos+1);
1510 SL[pos] := mlist[f].srvAnswer[n];
1511 SL[pos].Number := pos;
1512 end;
1513 end;
1514 if (not mlist[f].slReadUrgent) and (mlist[f].slUrgent <> '') then
1515 begin
1516 if (mlist[f].slUrgent <> slUrgent) then
1517 begin
1518 slUrgent := mlist[f].slUrgent;
1519 slReadUrgent := false;
1520 end;
1521 end;
1522 if (slMOTD <> '') and (mlist[f].slMOTD <> '') then
1523 begin
1524 slMOTD := mlist[f].slMOTD;
1525 end;
1526 end;
1528 DisconnectAll();
1530 if (length(SL) = 0) then
1531 begin
1532 CheckLocalServers();
1533 exit;
1534 end;
1536 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1537 if Sock = ENET_SOCKET_NULL then Exit;
1538 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
1540 for I := Low(SL) to High(SL) do PingServer(SL[I], Sock);
1542 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
1543 PingBcast(Sock);
1545 T := GetTimerMS();
1547 InMsg.Alloc(NET_BUFSIZE);
1548 Buf.data := InMsg.Data;
1549 Buf.dataLength := InMsg.MaxSize;
1550 Cnt := 0;
1551 while GetTimerMS() - T <= 500 do
1552 begin
1553 InMsg.Clear();
1555 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
1556 if RX <= 0 then continue;
1557 InMsg.CurSize := RX;
1559 InMsg.BeginReading();
1561 if InMsg.ReadChar() <> 'D' then continue;
1562 if InMsg.ReadChar() <> 'F' then continue;
1564 with tmpsv do
1565 begin
1566 Port := InMsg.ReadWord();
1567 Ping := InMsg.ReadInt64();
1568 Ping := GetTimerMS() - Ping;
1569 Name := InMsg.ReadString();
1570 Map := InMsg.ReadString();
1571 GameMode := InMsg.ReadByte();
1572 Players := InMsg.ReadByte();
1573 MaxPlayers := InMsg.ReadByte();
1574 Protocol := InMsg.ReadByte();
1575 Password := InMsg.ReadByte() = 1;
1576 LocalPl := InMsg.ReadByte();
1577 Bots := InMsg.ReadWord();
1578 PingAddr := SvAddr;
1579 end;
1581 FromSL := False;
1582 for I := Low(SL) to High(SL) do
1583 if (SL[I].PingAddr.host = SvAddr.host) and
1584 (SL[I].PingAddr.port = SvAddr.port) and
1585 (SL[I].Port = tmpsv.Port) and
1586 (SL[I].Name = tmpsv.Name) then
1587 begin
1588 tmpsv.IP := SL[I].IP;
1589 SL[I] := tmpsv;
1590 FromSL := True;
1591 Inc(Cnt);
1592 break;
1593 end;
1595 if not FromSL then
1596 begin
1597 I := Length(SL);
1598 SetLength(SL, I + 1);
1599 tmpsv.IP := DecodeIPV4(SvAddr.host);
1600 SL[I] := tmpsv;
1601 end;
1602 end;
1604 InMsg.Free();
1605 enet_socket_destroy(Sock);
1606 finally
1607 NetOut.Clear();
1608 end;
1609 end;
1612 //==========================================================================
1613 //
1614 // GetServerFromTable
1615 //
1616 //==========================================================================
1617 function GetServerFromTable (Index: Integer; SL: TNetServerList; ST: TNetServerTable): TNetServer;
1618 begin
1619 Result.Number := 0;
1620 Result.Protocol := 0;
1621 Result.Name := '';
1622 Result.IP := '';
1623 Result.Port := 0;
1624 Result.Map := '';
1625 Result.Players := 0;
1626 Result.MaxPlayers := 0;
1627 Result.LocalPl := 0;
1628 Result.Bots := 0;
1629 Result.Ping := 0;
1630 Result.GameMode := 0;
1631 Result.Password := false;
1632 FillChar(Result.PingAddr, SizeOf(ENetAddress), 0);
1633 if ST = nil then
1634 Exit;
1635 if (Index < 0) or (Index >= Length(ST)) then
1636 Exit;
1637 Result := SL[ST[Index].Indices[ST[Index].Current]];
1638 end;
1641 //==========================================================================
1642 //
1643 // g_Serverlist_GenerateTable
1644 //
1645 //==========================================================================
1646 procedure g_Serverlist_GenerateTable (SL: TNetServerList; var ST: TNetServerTable);
1647 var
1648 i, j: Integer;
1650 function FindServerInTable(Name: AnsiString; Port: Word): Integer;
1651 var
1652 i: Integer;
1653 begin
1654 Result := -1;
1655 if ST = nil then
1656 Exit;
1657 for i := Low(ST) to High(ST) do
1658 begin
1659 if Length(ST[i].Indices) = 0 then
1660 continue;
1661 if (SL[ST[i].Indices[0]].Name = Name) and (SL[ST[i].Indices[0]].Port = Port) then
1662 begin
1663 Result := i;
1664 Exit;
1665 end;
1666 end;
1667 end;
1668 function ComparePing(i1, i2: Integer): Boolean;
1669 var
1670 p1, p2: Int64;
1671 begin
1672 p1 := SL[i1].Ping;
1673 p2 := SL[i2].Ping;
1674 if (p1 < 0) then p1 := 999;
1675 if (p2 < 0) then p2 := 999;
1676 Result := p1 > p2;
1677 end;
1678 procedure SortIndices(var ind: Array of Integer);
1679 var
1680 I, J: Integer;
1681 T: Integer;
1682 begin
1683 for I := High(ind) downto Low(ind) do
1684 for J := Low(ind) to High(ind) - 1 do
1685 if ComparePing(ind[j], ind[j+1]) then
1686 begin
1687 T := ind[j];
1688 ind[j] := ind[j+1];
1689 ind[j+1] := T;
1690 end;
1691 end;
1692 procedure SortRows();
1693 var
1694 I, J: Integer;
1695 T: TNetServerRow;
1696 begin
1697 for I := High(ST) downto Low(ST) do
1698 for J := Low(ST) to High(ST) - 1 do
1699 if ComparePing(ST[j].Indices[0], ST[j+1].Indices[0]) then
1700 begin
1701 T := ST[j];
1702 ST[j] := ST[j+1];
1703 ST[j+1] := T;
1704 end;
1705 end;
1706 begin
1707 ST := nil;
1708 if SL = nil then
1709 Exit;
1711 for i := Low(SL) to High(SL) do
1712 begin
1713 j := FindServerInTable(SL[i].Name, SL[i].Port);
1714 if j = -1 then
1715 begin
1716 j := Length(ST);
1717 SetLength(ST, j + 1);
1718 ST[j].Current := 0;
1719 SetLength(ST[j].Indices, 1);
1720 ST[j].Indices[0] := i;
1721 end
1722 else
1723 begin
1724 SetLength(ST[j].Indices, Length(ST[j].Indices) + 1);
1725 ST[j].Indices[High(ST[j].Indices)] := i;
1726 end;
1727 end;
1729 for i := Low(ST) to High(ST) do
1730 SortIndices(ST[i].Indices);
1732 SortRows();
1733 end;
1736 //==========================================================================
1737 //
1738 // g_Serverlist_Control
1739 //
1740 //==========================================================================
1741 procedure g_Serverlist_Control (var SL: TNetServerList; var ST: TNetServerTable);
1742 var
1743 qm: Boolean;
1744 Srv: TNetServer;
1745 begin
1746 g_Net_Slist_Pulse();
1748 if gConsoleShow or gChatShow then
1749 Exit;
1751 {$IFDEF HEADLESS}
1752 qm := True;
1753 {$ELSE}
1754 qm := sys_HandleInput(); // this updates kbd
1755 {$ENDIF}
1757 if qm or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
1758 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or
1759 e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
1760 begin
1761 SL := nil;
1762 ST := nil;
1763 gState := STATE_MENU;
1764 {$IFDEF ENABLE_MENU}
1765 g_GUI_ShowWindow('MainMenu');
1766 g_GUI_ShowWindow('NetGameMenu');
1767 g_GUI_ShowWindow('NetClientMenu');
1768 g_Sound_PlayEx(WINDOW_CLOSESOUND);
1769 {$ENDIF}
1770 Exit;
1771 end;
1773 // if there's a message on the screen,
1774 if not slReadUrgent and (slUrgent <> '') then
1775 begin
1776 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1777 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
1778 slReadUrgent := True;
1779 Exit;
1780 end;
1782 if e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_JUMP) or
1783 e_KeyPressed(JOY0_ACTIVATE) or e_KeyPressed(JOY1_ACTIVATE) or e_KeyPressed(JOY2_ACTIVATE) or e_KeyPressed(JOY3_ACTIVATE) then
1784 begin
1785 if not slFetched then
1786 begin
1787 slWaitStr := _lc[I_NET_SLIST_WAIT];
1789 {$IFNDEF HEADLESS}
1790 r_Render_Draw;
1791 sys_Repaint;
1792 {$ENDIF}
1794 if g_Net_Slist_Fetch(SL) then
1795 begin
1796 if SL = nil then
1797 slWaitStr := _lc[I_NET_SLIST_NOSERVERS];
1798 end
1799 else
1800 if SL = nil then
1801 slWaitStr := _lc[I_NET_SLIST_ERROR];
1802 slFetched := True;
1803 slSelection := 0;
1804 g_Serverlist_GenerateTable(SL, ST);
1805 end;
1806 end
1807 else
1808 slFetched := False;
1810 if SL = nil then Exit;
1812 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1813 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
1814 begin
1815 if not slReturnPressed then
1816 begin
1817 Srv := GetServerFromTable(slSelection, SL, ST);
1818 if Srv.Password then
1819 begin
1820 {$IFDEF ENABLE_MENU}
1821 PromptIP := Srv.IP;
1822 PromptPort := Srv.Port;
1823 {$ENDIF}
1824 gState := STATE_MENU;
1825 {$IFDEF ENABLE_MENU}
1826 g_GUI_ShowWindow('ClientPasswordMenu');
1827 {$ENDIF}
1828 SL := nil;
1829 ST := nil;
1830 slReturnPressed := True;
1831 Exit;
1832 end
1833 else
1834 g_Game_StartClient(Srv.IP, Srv.Port, '');
1835 SL := nil;
1836 ST := nil;
1837 slReturnPressed := True;
1838 Exit;
1839 end;
1840 end
1841 else
1842 slReturnPressed := False;
1844 if e_KeyPressed(IK_DOWN) or e_KeyPressed(IK_KPDOWN) or e_KeyPressed(VK_DOWN) or
1845 e_KeyPressed(JOY0_DOWN) or e_KeyPressed(JOY1_DOWN) or e_KeyPressed(JOY2_DOWN) or e_KeyPressed(JOY3_DOWN) then
1846 begin
1847 if not slDirPressed then
1848 begin
1849 Inc(slSelection);
1850 if slSelection > High(ST) then slSelection := 0;
1851 slDirPressed := True;
1852 end;
1853 end;
1855 if e_KeyPressed(IK_UP) or e_KeyPressed(IK_KPUP) or e_KeyPressed(VK_UP) or
1856 e_KeyPressed(JOY0_UP) or e_KeyPressed(JOY1_UP) or e_KeyPressed(JOY2_UP) or e_KeyPressed(JOY3_UP) then
1857 begin
1858 if not slDirPressed then
1859 begin
1860 if slSelection = 0 then slSelection := Length(ST);
1861 Dec(slSelection);
1863 slDirPressed := True;
1864 end;
1865 end;
1867 if e_KeyPressed(IK_RIGHT) or e_KeyPressed(IK_KPRIGHT) or e_KeyPressed(VK_RIGHT) or
1868 e_KeyPressed(JOY0_RIGHT) or e_KeyPressed(JOY1_RIGHT) or e_KeyPressed(JOY2_RIGHT) or e_KeyPressed(JOY3_RIGHT) then
1869 begin
1870 if not slDirPressed then
1871 begin
1872 Inc(ST[slSelection].Current);
1873 if ST[slSelection].Current > High(ST[slSelection].Indices) then ST[slSelection].Current := 0;
1874 slDirPressed := True;
1875 end;
1876 end;
1878 if e_KeyPressed(IK_LEFT) or e_KeyPressed(IK_KPLEFT) or e_KeyPressed(VK_LEFT) or
1879 e_KeyPressed(JOY0_LEFT) or e_KeyPressed(JOY1_LEFT) or e_KeyPressed(JOY2_LEFT) or e_KeyPressed(JOY3_LEFT) then
1880 begin
1881 if not slDirPressed then
1882 begin
1883 if ST[slSelection].Current = 0 then ST[slSelection].Current := Length(ST[slSelection].Indices);
1884 Dec(ST[slSelection].Current);
1886 slDirPressed := True;
1887 end;
1888 end;
1890 if (not e_KeyPressed(IK_DOWN)) and
1891 (not e_KeyPressed(IK_UP)) and
1892 (not e_KeyPressed(IK_RIGHT)) and
1893 (not e_KeyPressed(IK_LEFT)) and
1894 (not e_KeyPressed(IK_KPDOWN)) and
1895 (not e_KeyPressed(IK_KPUP)) and
1896 (not e_KeyPressed(IK_KPRIGHT)) and
1897 (not e_KeyPressed(IK_KPLEFT)) and
1898 (not e_KeyPressed(VK_DOWN)) and
1899 (not e_KeyPressed(VK_UP)) and
1900 (not e_KeyPressed(VK_RIGHT)) and
1901 (not e_KeyPressed(VK_LEFT)) and
1902 (not e_KeyPressed(JOY0_UP)) and (not e_KeyPressed(JOY1_UP)) and (not e_KeyPressed(JOY2_UP)) and (not e_KeyPressed(JOY3_UP)) and
1903 (not e_KeyPressed(JOY0_DOWN)) and (not e_KeyPressed(JOY1_DOWN)) and (not e_KeyPressed(JOY2_DOWN)) and (not e_KeyPressed(JOY3_DOWN)) and
1904 (not e_KeyPressed(JOY0_LEFT)) and (not e_KeyPressed(JOY1_LEFT)) and (not e_KeyPressed(JOY2_LEFT)) and (not e_KeyPressed(JOY3_LEFT)) and
1905 (not e_KeyPressed(JOY0_RIGHT)) and (not e_KeyPressed(JOY1_RIGHT)) and (not e_KeyPressed(JOY2_RIGHT)) and (not e_KeyPressed(JOY3_RIGHT))
1906 then
1907 slDirPressed := False;
1908 end;
1911 end.