DEADSOFTWARE

net: oops; reconnection to failed master was totally broken
[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;
90 private
91 netmsg: TMsg;
93 public
94 constructor Create (var ea: ENetAddress);
96 procedure clear ();
98 function setAddress (var ea: ENetAddress; hostStr: AnsiString): Boolean;
100 function isValid (): Boolean;
101 function isAlive (): Boolean; // not disconnected
102 function isConnecting (): Boolean; // is connection in progress?
103 function isConnected (): Boolean;
105 // call as often as you want, the object will do the rest
106 // but try to call this at least once in 100 msecs
107 procedure pulse ();
109 procedure disconnect (forced: Boolean);
110 function connect (): Boolean;
112 procedure update ();
113 procedure remove ();
115 class procedure writeInfo (var msg: TMsg); static;
117 procedure connectedEvent ();
118 procedure disconnectedEvent ();
119 procedure receivedEvent (pkt: pENetPacket); // `pkt` is never `nil`
120 end;
123 var
124 slCurrent: TNetServerList = nil;
125 slTable: TNetServerTable = nil;
126 slWaitStr: AnsiString = '';
127 slReturnPressed: Boolean = True;
129 slMOTD: AnsiString = '';
130 slUrgent: AnsiString = '';
132 NMASTER_FORCE_UPDATE_TIMEOUT: Integer = 0; // fuck you, fpc, and your idiotic "diagnostics"
135 procedure g_Net_Slist_Set (IP: AnsiString; Port: Word; list: AnsiString='');
136 function g_Net_Slist_Fetch (var SL: TNetServerList): Boolean;
138 // make this server private
139 procedure g_Net_Slist_Private ();
140 // make this server public
141 procedure g_Net_Slist_Public ();
143 // called while the server is running
144 procedure g_Net_Slist_ServerUpdate ();
145 // called when the server is started
146 procedure g_Net_Slist_ServerStarted ();
147 // called when the server is stopped
148 procedure g_Net_Slist_ServerClosed ();
150 // called when new netword player comes
151 procedure g_Net_Slist_ServerPlayerComes ();
152 // called when new netword player comes
153 procedure g_Net_Slist_ServerPlayerLeaves ();
154 // started new map
155 procedure g_Net_Slist_ServerMapStarted ();
156 // this server renamed (or password mode changed, or other params changed)
157 procedure g_Net_Slist_ServerRenamed ();
159 // non-zero timeout ignores current status (used to fetch server list)
160 procedure g_Net_Slist_Pulse (timeout: Integer=0);
162 procedure g_Net_Slist_ShutdownAll ();
164 procedure g_Serverlist_GenerateTable (SL: TNetServerList; var ST: TNetServerTable);
165 procedure g_Serverlist_Draw (var SL: TNetServerList; var ST: TNetServerTable);
166 procedure g_Serverlist_Control (var SL: TNetServerList; var ST: TNetServerTable);
168 function GetTimerMS (): Int64;
171 implementation
173 uses
174 e_input, e_graphics, e_log, g_window, g_net, g_console,
175 g_map, g_game, g_sound, g_gui, g_menu, g_options, g_language, g_basic,
176 wadreader, g_system, utils, hashtable;
179 // ////////////////////////////////////////////////////////////////////////// //
180 var
181 NetMHost: pENetHost = nil;
182 NetMEvent: ENetEvent;
183 mlist: array of TMasterHost = nil;
185 slSelection: Byte = 0;
186 slFetched: Boolean = False;
187 slDirPressed: Boolean = False;
188 slReadUrgent: Boolean = False;
190 reportsEnabled: Boolean = true;
193 //==========================================================================
194 //
195 // GetTimerMS
196 //
197 //==========================================================================
198 function GetTimerMS (): Int64;
199 begin
200 Result := sys_GetTicks() {div 1000};
201 end;
204 //==========================================================================
205 //
206 // findByPeer
207 //
208 //==========================================================================
209 function findByPeer (peer: pENetPeer): Integer;
210 var
211 f: Integer;
212 begin
213 for f := 0 to High(mlist) do if (mlist[f].peer = peer) then begin result := f; exit; end;
214 result := -1;
215 end;
218 //==========================================================================
219 //
220 // ShutdownAll
221 //
222 //==========================================================================
223 procedure g_Net_Slist_ShutdownAll ();
224 var
225 f, sres, idx: Integer;
226 stt, ct: Int64;
227 activeCount: Integer = 0;
228 begin
229 if (NetMHost = nil) then exit;
230 for f := 0 to High(mlist) do
231 begin
232 if (mlist[f].isAlive()) then
233 begin
234 Inc(activeCount);
235 if (mlist[f].isConnected() and mlist[f].updateSent) then
236 begin
237 writeln('unregistering from [', mlist[f].hostName, ']');
238 mlist[f].remove();
239 end;
240 //mlist[f].disconnect(false);
241 enet_peer_disconnect_later(mlist[f].peer, 0);
242 end;
243 end;
244 if (activeCount = 0) then exit;
245 stt := GetTimerMS();
246 while (activeCount > 0) do
247 begin
248 ct := GetTimerMS();
249 if (ct < stt) or (ct-stt >= 1500) then break;
251 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
252 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
253 // thank you, enet. let's ignore failures altogether then.
254 sres := enet_host_service(NetMHost, @NetMEvent, 100);
255 // if (sres < 0) then break;
256 if (sres <= 0) then continue;
258 idx := findByPeer(NetMEvent.peer);
259 if (idx < 0) then
260 begin
261 if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then enet_packet_destroy(NetMEvent.packet);
262 continue;
263 end;
265 if (NetMEvent.kind = ENET_EVENT_TYPE_CONNECT) then
266 begin
267 mlist[idx].connectedEvent();
268 //mlist[idx].disconnect(false);
269 enet_peer_disconnect(mlist[f].peer, 0);
270 end
271 else if (NetMEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
272 begin
273 mlist[idx].disconnectedEvent();
274 Dec(activeCount);
275 end
276 else if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
277 begin
278 mlist[idx].receivedEvent(NetMEvent.packet);
279 enet_packet_destroy(NetMEvent.packet);
280 end;
281 end;
282 enet_host_destroy(NetMHost);
283 NetMHost := nil;
284 end;
287 //==========================================================================
288 //
289 // DisconnectAll
290 //
291 //==========================================================================
292 procedure DisconnectAll (forced: Boolean=false);
293 var
294 f: Integer;
295 begin
296 for f := 0 to High(mlist) do
297 begin
298 if (mlist[f].isAlive()) then mlist[f].disconnect(forced);
299 end;
300 end;
303 //==========================================================================
304 //
305 // ConnectAll
306 //
307 //==========================================================================
308 procedure ConnectAll (sendUpdate: Boolean);
309 var
310 f: Integer;
311 begin
312 // set flags; pulse will take care of the rest
313 for f := 0 to High(mlist) do
314 begin
315 // force reconnect
316 mlist[f].lastDisconnectTime := 0;
317 // force updating
318 if (sendUpdate) then
319 begin
320 mlist[f].NetUpdatePending := true;
321 mlist[f].lastUpdateTime := 0;
322 end;
323 end;
324 end;
327 //==========================================================================
328 //
329 // UpdateAll
330 //
331 //==========================================================================
332 procedure UpdateAll (force: Boolean);
333 var
334 f: Integer;
335 begin
336 // set flags; pulse will take care of the rest
337 for f := 0 to High(mlist) do
338 begin
339 if (not mlist[f].isAlive()) then continue;
340 mlist[f].NetUpdatePending := true;
341 if (force) then mlist[f].lastUpdateTime := 0;
342 end;
343 end;
346 //**************************************************************************
347 //
348 // public api
349 //
350 //**************************************************************************
352 //==========================================================================
353 //
354 // g_Net_Slist_Private
355 //
356 // make this server private
357 //
358 //==========================================================================
359 procedure g_Net_Slist_Private ();
360 begin
361 DisconnectAll();
362 reportsEnabled := false;
363 end;
366 //==========================================================================
367 //
368 // g_Net_Slist_Public
369 //
370 // make this server public
371 //
372 //==========================================================================
373 procedure g_Net_Slist_Public ();
374 begin
375 if (not reportsEnabled) then
376 begin
377 reportsEnabled := true;
378 ConnectAll(true);
379 end;
380 end;
383 //==========================================================================
384 //
385 // g_Net_Slist_ServerUpdate
386 //
387 // called while the server is running
388 //
389 //==========================================================================
390 procedure g_Net_Slist_ServerUpdate ();
391 begin
392 UpdateAll(false);
393 end;
396 // called when the server is started
397 procedure g_Net_Slist_ServerStarted ();
398 begin
399 reportsEnabled := NetUseMaster;
400 if reportsEnabled and g_Game_IsServer() and g_Game_IsNet() then
401 begin
402 writeln('*** server started; reporting to master...');
403 ConnectAll(true);
404 end;
405 end;
408 //==========================================================================
409 //
410 // g_Net_Slist_ServerClosed
411 //
412 // called when the server is stopped
413 //
414 //==========================================================================
415 procedure g_Net_Slist_ServerClosed ();
416 var
417 f: Integer;
418 begin
419 if reportsEnabled then
420 begin
421 reportsEnabled := false;
422 for f := 0 to High(mlist) do
423 begin
424 if (mlist[f].isConnected()) then mlist[f].remove();
425 end;
426 end;
427 DisconnectAll();
428 end;
431 //==========================================================================
432 //
433 // g_Net_Slist_ServerPlayerComes
434 //
435 // called when new netword player comes
436 //
437 //==========================================================================
438 procedure g_Net_Slist_ServerPlayerComes ();
439 begin
440 UpdateAll(true);
441 end;
444 //==========================================================================
445 //
446 // g_Net_Slist_ServerPlayerLeaves
447 //
448 // called when new netword player comes
449 //
450 //==========================================================================
451 procedure g_Net_Slist_ServerPlayerLeaves ();
452 begin
453 UpdateAll(true);
454 end;
457 //==========================================================================
458 //
459 // g_Net_Slist_ServerMapStarted
460 //
461 // started new map
462 //
463 //==========================================================================
464 procedure g_Net_Slist_ServerMapStarted ();
465 begin
466 UpdateAll(true);
467 end;
470 //==========================================================================
471 //
472 // g_Net_Slist_ServerRenamed
473 //
474 // this server renamed (or password mode changed, or other params changed)
475 //
476 //==========================================================================
477 procedure g_Net_Slist_ServerRenamed ();
478 begin
479 UpdateAll(true);
480 end;
483 //**************************************************************************
484 //
485 // TMasterHost
486 //
487 //**************************************************************************
489 //==========================================================================
490 //
491 // TMasterHost.Create
492 //
493 //==========================================================================
494 constructor TMasterHost.Create (var ea: ENetAddress);
495 begin
496 peer := nil;
497 NetHostConnected := false;
498 NetHostConReqTime := 0;
499 NetUpdatePending := false;
500 lastDisconnectTime := 0;
501 updateSent := false;
502 lastUpdateTime := 0;
503 hostName := '';
504 ZeroMemory(@enetAddr, sizeof(enetAddr));
505 SetLength(srvAnswer, 0);
506 srvAnswered := 0;
507 slMOTD := '';
508 slUrgent := '';
509 slReadUrgent := true;
510 netmsg.Alloc(NET_BUFSIZE);
511 setAddress(ea, '');
512 end;
515 //==========================================================================
516 //
517 // TMasterHost.clear
518 //
519 //==========================================================================
520 procedure TMasterHost.clear ();
521 begin
522 updateSent := false; // do not send 'remove'
523 disconnect(true);
524 hostName := '';
525 netmsg.Free();
526 SetLength(srvAnswer, 0);
527 srvAnswered := 0;
528 slMOTD := '';
529 slUrgent := '';
530 slReadUrgent := true;
531 ZeroMemory(@enetAddr, sizeof(enetAddr));
532 end;
535 //==========================================================================
536 //
537 // TMasterHost.setAddress
538 //
539 //==========================================================================
540 function TMasterHost.setAddress (var ea: ENetAddress; hostStr: AnsiString): Boolean;
541 begin
542 result := false;
543 SetLength(srvAnswer, 0);
544 srvAnswered := 0;
545 slMOTD := '';
546 slUrgent := '';
547 slReadUrgent := true;
548 updateSent := false; // do not send 'remove'
549 disconnect(true);
550 hostName := '';
552 if (not g_Net_IsNetworkAvailable()) then exit;
554 enetAddr := ea;
555 if (enetAddr.host = 0) or (enetAddr.port = 0) then exit;
557 if (length(hostStr) > 0) then hostName := hostStr else hostName := IntToStr(enetAddr.host)+':'+IntToStr(ea.port);
559 result := isValid();
560 end;
563 //==========================================================================
564 //
565 // TMasterHost.isValid
566 //
567 //==========================================================================
568 function TMasterHost.isValid (): Boolean;
569 begin
570 result := (enetAddr.host <> 0) and (enetAddr.port <> 0);
571 end;
574 //==========================================================================
575 //
576 // TMasterHost.isAlive
577 //
578 // not disconnected
579 //
580 //==========================================================================
581 function TMasterHost.isAlive (): Boolean;
582 begin
583 result := (NetMHost <> nil) and (peer <> nil);
584 end;
587 //==========================================================================
588 //
589 // TMasterHost.isConnecting
590 //
591 // is connection in progress?
592 //
593 //==========================================================================
594 function TMasterHost.isConnecting (): Boolean;
595 begin
596 result := isAlive() and (not NetHostConnected) and (NetHostConReqTime <> -1);
597 end;
600 //==========================================================================
601 //
602 // TMasterHost.isConnected
603 //
604 //==========================================================================
605 function TMasterHost.isConnected (): Boolean;
606 begin
607 result := isAlive() and (NetHostConnected) and (NetHostConReqTime <> -1);
608 end;
611 //==========================================================================
612 //
613 // TMasterHost.connectedEvent
614 //
615 //==========================================================================
616 procedure TMasterHost.connectedEvent ();
617 begin
618 if not isAlive() then exit;
619 if NetHostConnected then exit;
620 NetHostConnected := true;
621 NetHostConReqTime := 0; // just in case
622 e_LogWritefln('connected to master at [%s]', [hostName], TMsgType.Notify);
623 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_CONN], [mlist[f].hostName]));
624 end;
627 //==========================================================================
628 //
629 // TMasterHost.disconnectedEvent
630 //
631 //==========================================================================
632 procedure TMasterHost.disconnectedEvent ();
633 begin
634 if not isAlive() then exit;
635 e_LogWritefln('disconnected from master at [%s]', [hostName], TMsgType.Notify);
636 disconnect(true);
637 //if (spamConsole) then g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_DISC], [hostName]));
638 end;
641 //==========================================================================
642 //
643 // TMasterHost.receivedEvent
644 //
645 // `pkt` is never `nil`
646 //
647 //==========================================================================
648 procedure TMasterHost.receivedEvent (pkt: pENetPacket);
649 var
650 msg: TMsg;
651 MID: Byte;
652 Cnt: Byte;
653 f: Integer;
654 s: AnsiString;
655 begin
656 e_LogWritefln('received packed from master at [%s]', [hostName], TMsgType.Notify);
657 if not msg.Init(pkt^.data, pkt^.dataLength, True) then exit;
658 // packet type
659 MID := msg.ReadByte();
660 if (MID <> NET_MMSG_GET) then exit;
661 e_LogWritefln('received list packet from master at [%s]', [hostName], TMsgType.Notify);
662 SetLength(srvAnswer, 0);
663 if (srvAnswered > 0) then Inc(srvAnswered);
664 slMOTD := '';
665 //slUrgent := '';
666 slReadUrgent := true;
667 // number of items
668 Cnt := msg.ReadByte();
669 //g_Console_Add(_lc[I_NET_MSG]+Format(_lc[I_NET_SLIST_RETRIEVED], [Cnt, hostName]), True);
670 e_LogWritefln('got %u server(s) from master at [%s]', [Cnt, hostName], TMsgType.Notify);
671 if (Cnt > 0) then
672 begin
673 SetLength(srvAnswer, Cnt);
674 for f := 0 to Cnt-1 do
675 begin
676 srvAnswer[f].Number := f;
677 srvAnswer[f].IP := msg.ReadString();
678 srvAnswer[f].Port := msg.ReadWord();
679 srvAnswer[f].Name := msg.ReadString();
680 srvAnswer[f].Map := msg.ReadString();
681 srvAnswer[f].GameMode := msg.ReadByte();
682 srvAnswer[f].Players := msg.ReadByte();
683 srvAnswer[f].MaxPlayers := msg.ReadByte();
684 srvAnswer[f].Protocol := msg.ReadByte();
685 srvAnswer[f].Password := msg.ReadByte() = 1;
686 enet_address_set_host(Addr(srvAnswer[f].PingAddr), PChar(Addr(srvAnswer[f].IP[1])));
687 srvAnswer[f].Ping := -1;
688 srvAnswer[f].PingAddr.port := NET_PING_PORT;
689 end;
690 end;
692 if (msg.ReadCount < msg.CurSize) then
693 begin
694 // new master, supports version reports
695 s := msg.ReadString();
696 if (s <> {MyVer}GAME_VERSION) then
697 begin
698 { TODO }
699 g_Console_Add('!!! UpdVer = `'+s+'`');
700 end;
701 // even newer master, supports extra info
702 if (msg.ReadCount < msg.CurSize) then
703 begin
704 slMOTD := b_Text_Format(msg.ReadString());
705 if (slMOTD <> '') then e_LogWritefln('got MOTD from master at [%s]: %s', [hostName, slMOTD], TMsgType.Notify);
706 s := b_Text_Format(msg.ReadString());
707 // check if the message has updated and the user has to read it again
708 if (slUrgent <> s) then slReadUrgent := false;
709 slUrgent := s;
710 if (s <> '') then e_LogWritefln('got urgent from master at [%s]: %s', [hostName, s], TMsgType.Notify);
711 end;
712 end;
713 end;
716 //==========================================================================
717 //
718 // TMasterHost.disconnect
719 //
720 //==========================================================================
721 procedure TMasterHost.disconnect (forced: Boolean);
722 begin
723 if isAlive() then
724 begin
725 lastDisconnectTime := GetTimerMS();
726 if forced or (not NetHostConnected) or (NetHostConReqTime = -1) then
727 begin
728 enet_peer_reset(peer);
729 peer := nil;
730 NetHostConReqTime := 0;
731 updateSent := false;
732 end
733 else
734 begin
735 enet_peer_disconnect_later(peer, 0);
736 // main pulse will take care of the rest
737 NetHostConReqTime := -1;
738 end;
739 end
740 else
741 begin
742 // just in case
743 NetHostConReqTime := 0;
744 updateSent := false;
745 end;
747 NetHostConnected := false;
748 NetUpdatePending := false;
749 lastUpdateTime := 0;
750 //if (spamConsole) then g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_DISC], [hostName]));
751 end;
754 //==========================================================================
755 //
756 // TMasterHost.connect
757 //
758 //==========================================================================
759 function TMasterHost.connect (): Boolean;
760 begin
761 result := false;
762 if not isValid() then exit;
763 if (NetHostConReqTime = -1) then
764 begin
765 disconnect(true);
766 if (NetHostConReqTime = -1) then e_LogWritefln('ketmar broke master [%s] logic! (000)', [hostName], TMsgType.Notify);
767 if (isAlive()) then e_LogWritefln('ketmar broke master [%s] logic! (001)', [hostName], TMsgType.Notify);
768 end
769 else
770 begin
771 if isAlive() then begin result := true; exit; end;
772 end;
774 lastDisconnectTime := GetTimerMS(); // why not?
775 SetLength(srvAnswer, 0);
776 srvAnswered := 0;
777 NetHostConnected := false;
778 NetHostConReqTime := 0;
779 NetUpdatePending := false;
780 updateSent := false;
781 lastUpdateTime := 0;
783 peer := enet_host_connect(NetMHost, @enetAddr, NET_MCHANS, 0);
784 if (peer = nil) then
785 begin
786 g_Console_Add(_lc[I_NET_MSG_ERROR]+_lc[I_NET_ERR_CLIENT], true);
787 exit;
788 end;
790 NetHostConReqTime := lastDisconnectTime;
791 e_LogWritefln('connecting to master at [%s]', [hostName], TMsgType.Notify);
792 end;
795 //==========================================================================
796 //
797 // TMasterHost.writeInfo
798 //
799 //==========================================================================
800 class procedure TMasterHost.writeInfo (var msg: TMsg);
801 var
802 wad, map: AnsiString;
803 begin
804 wad := g_ExtractWadNameNoPath(gMapInfo.Map);
805 map := g_ExtractFileName(gMapInfo.Map);
807 msg.Write(NetServerName);
809 msg.Write(wad+':/'+map);
810 msg.Write(gGameSettings.GameMode);
812 msg.Write(Byte(NetClientCount));
814 msg.Write(NetMaxClients);
816 msg.Write(Byte(NET_PROTOCOL_VER));
817 msg.Write(Byte(NetPassword <> ''));
818 end;
821 //==========================================================================
822 //
823 // TMasterHost.update
824 //
825 //==========================================================================
826 procedure TMasterHost.update ();
827 var
828 pkt: pENetPacket;
829 begin
830 if not isAlive() then exit;
831 if not isConnected() then
832 begin
833 NetUpdatePending := isConnecting();
834 exit;
835 end;
837 netmsg.Clear();
839 if reportsEnabled and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster then
840 begin
841 try
842 netmsg.Write(Byte(NET_MMSG_UPD));
843 netmsg.Write(NetAddr.port);
844 //writeln(formatstrf('%08x', [NetAddr.host]), ' : ', NetAddr.host);
846 writeInfo(netmsg);
848 pkt := enet_packet_create(netmsg.Data, netmsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
849 if assigned(pkt) then
850 begin
851 if (enet_peer_send(peer, NET_MCHAN_UPD, pkt) = 0) then
852 begin
853 e_LogWritefln('sent update to master at [%s]', [hostName], TMsgType.Notify);
854 NetUpdatePending := false;
855 updateSent := true;
856 end;
857 end;
858 finally
859 netmsg.Clear();
860 end;
861 end
862 else
863 begin
864 NetUpdatePending := false;
865 end;
866 end;
869 //==========================================================================
870 //
871 // TMasterHost.remove
872 //
873 //==========================================================================
874 procedure TMasterHost.remove ();
875 var
876 pkt: pENetPacket;
877 begin
878 NetUpdatePending := false;
879 lastUpdateTime := 0;
880 updateSent := false;
881 if not isAlive() then exit;
882 if not isConnected() then exit;
884 netmsg.Clear();
885 try
886 netmsg.Write(Byte(NET_MMSG_DEL));
887 netmsg.Write(NetAddr.port);
889 pkt := enet_packet_create(netmsg.Data, netmsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
890 if assigned(pkt) then
891 begin
892 enet_peer_send(peer, NET_MCHAN_MAIN, pkt);
893 end;
894 finally
895 netmsg.Clear();
896 end;
897 end;
900 //==========================================================================
901 //
902 // TMasterHost.pulse
903 //
904 // this performs various scheduled tasks, if necessary
905 //
906 //==========================================================================
907 procedure TMasterHost.pulse ();
908 var
909 ct: Int64;
910 mrate: Cardinal;
911 begin
912 if not isAlive() then exit;
913 if (NetHostConReqTime = -1) then exit; // waiting for shutdown (disconnect in progress)
914 ct := GetTimerMS();
915 // process pending connection timeout
916 if (not NetHostConnected) then
917 begin
918 if (ct < NetHostConReqTime) or (ct-NetHostConReqTime >= 1000*NMASTER_TIMEOUT_CONNECT) then
919 begin
920 e_LogWritefln('failed to connect to master at [%s]', [hostName], TMsgType.Notify);
921 // do not spam with error messages, it looks like the master is down
922 //g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR], True);
923 disconnect(true);
924 end;
925 exit;
926 end;
927 // send update, if necessary
928 if (NetUpdatePending) then
929 begin
930 mrate := NetMasterRate;
931 if (mrate < 10000) then mrate := 10000
932 else if (mrate > 1000*60*10) then mrate := 1000*60*10;
933 if (NMASTER_FORCE_UPDATE_TIMEOUT > 0) then mrate := NMASTER_FORCE_UPDATE_TIMEOUT*1000;
934 if (lastUpdateTime = 0) or (ct < lastUpdateTime) or (ct-lastUpdateTime >= mrate) then
935 begin
936 //e_LogWritefln('update timeout: %d', [Integer(mrate)], TMsgType.Notify);
937 lastUpdateTime := ct;
938 update();
939 end;
940 end;
941 end;
944 //**************************************************************************
945 //
946 // other functions
947 //
948 //**************************************************************************
949 type
950 THashStrDWord = specialize THashBase<AnsiString, LongWord, THashKeyStrAnsiCI>;
952 var
953 knownHosts: THashStrDWord = nil;
956 //==========================================================================
957 //
958 // parseAddressPort
959 //
960 //==========================================================================
961 function parseAddressPort (var ea: ENetAddress; hostandport: AnsiString): Boolean;
962 var
963 cp, port: Integer;
964 hostName: AnsiString;
965 ip: LongWord;
966 begin
967 result := false;
968 if (not g_Net_IsNetworkAvailable()) then exit;
970 hostandport := Trim(hostandport);
971 if (length(hostandport) = 0) then exit;
973 hostName := hostandport;
974 port := 25665;
976 cp := Pos(':', hostandport);
977 if (cp > 0) then
978 begin
979 hostName := Trim(Copy(hostandport, 1, cp-1));
980 Delete(hostandport, 1, cp);
981 hostandport := Trim(hostandport);
982 if (length(hostandport) > 0) then
983 begin
984 try
985 port := StrToInt(hostandport);
986 except
987 port := -1;
988 end;
989 end;
990 end;
992 if (length(hostName) = 0) then exit;
993 if (port < 1) or (port > 65535) then exit;
995 if not assigned(knownHosts) then knownHosts := THashStrDWord.Create();
997 if knownHosts.get(hostName, ip) then
998 begin
999 ea.host := ip;
1000 end
1001 else
1002 begin
1003 if (enet_address_set_host(@ea, PChar(Addr(hostName[1]))) <> 0) then
1004 begin
1005 knownHosts.put(hostName, 0);
1006 exit;
1007 end;
1008 knownHosts.put(hostName, ea.host);
1009 end;
1010 ea.Port := port;
1011 result := true;
1012 end;
1015 //==========================================================================
1016 //
1017 // addMasterRecord
1018 //
1019 //==========================================================================
1020 procedure addMasterRecord (var ea: ENetAddress; sa: AnsiString);
1021 var
1022 f: Integer;
1023 freeIdx: Integer;
1024 begin
1025 freeIdx := -1;
1026 for f := 0 to High(mlist) do
1027 begin
1028 if (mlist[f].enetAddr.host = ea.host) and (mlist[f].enetAddr.port = ea.port) then
1029 begin
1030 mlist[f].justAdded := true;
1031 exit;
1032 end;
1033 if (freeIdx < 0) and (not mlist[f].isValid()) then freeIdx := f;
1034 end;
1035 if (freeIdx < 0) then
1036 begin
1037 freeIdx := length(mlist);
1038 SetLength(mlist, freeIdx+1);
1039 mlist[freeIdx].Create(ea);
1040 end;
1041 mlist[freeIdx].justAdded := true;
1042 mlist[freeIdx].setAddress(ea, sa);
1043 e_LogWritefln('added masterserver with address [%s]', [sa], TMsgType.Notify);
1044 end;
1047 //==========================================================================
1048 //
1049 // g_Net_Slist_Set
1050 //
1051 //==========================================================================
1052 procedure g_Net_Slist_Set (IP: AnsiString; Port: Word; list: AnsiString='');
1053 var
1054 f, dest: Integer;
1055 sa: AnsiString;
1056 ea: ENetAddress;
1057 pp: Integer;
1058 begin
1059 if (not g_Net_IsNetworkAvailable()) then exit;
1061 for f := 0 to High(mlist) do mlist[f].justAdded := false;
1063 IP := Trim(IP);
1064 if (length(IP) > 0) and (Port > 0) then
1065 begin
1066 sa := IP+':'+IntToStr(Port);
1067 if parseAddressPort(ea, sa) then addMasterRecord(ea, sa);
1068 end;
1070 list := Trim(list);
1071 //writeln('list=[', list, ']');
1072 while (length(list) > 0) do
1073 begin
1074 pp := Pos(',', list);
1075 if (pp < 1) then pp := length(list)+1;
1076 sa := Trim(Copy(list, 1, pp-1));
1077 Delete(list, 1, pp);
1078 //writeln(' sa=[', sa, ']');
1079 if (length(sa) > 0) and parseAddressPort(ea, sa) then addMasterRecord(ea, sa);
1080 end;
1082 // remove unknown master servers
1083 dest := 0;
1084 for f := 0 to High(mlist) do
1085 begin
1086 if (not mlist[f].justAdded) then mlist[f].clear();
1087 if (mlist[f].isValid()) then
1088 begin
1089 if (dest <> f) then mlist[dest] := mlist[f];
1090 Inc(dest);
1091 end;
1092 end;
1093 if (dest <> length(mlist)) then SetLength(mlist, dest);
1094 end;
1097 //**************************************************************************
1098 //
1099 // main pulse
1100 //
1101 //**************************************************************************
1103 //==========================================================================
1104 //
1105 // isMasterReportsEnabled
1106 //
1107 //==========================================================================
1108 function isMasterReportsEnabled (): Boolean;
1109 begin
1110 result := (reportsEnabled and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster);
1111 end;
1114 //==========================================================================
1115 //
1116 // g_Net_Slist_Pulse
1117 //
1118 // non-zero timeout ignores current status (used to fetch server list)
1119 //
1120 //==========================================================================
1121 procedure g_Net_Slist_Pulse (timeout: Integer=0);
1122 var
1123 f: Integer;
1124 sres: Integer;
1125 idx: Integer;
1126 ct: Int64;
1127 isListQuery: Boolean;
1128 count: Integer;
1129 begin
1130 if (not g_Net_IsNetworkAvailable()) then exit;
1132 if (length(mlist) = 0) then
1133 begin
1134 if (NetMHost <> nil) then
1135 begin
1136 enet_host_destroy(NetMHost);
1137 NetMHost := nil;
1138 exit;
1139 end;
1140 end;
1142 if (NetMHost = nil) then
1143 begin
1144 NetMHost := enet_host_create(nil, 64, NET_MCHANS, 1024*1024, 1024*1024);
1145 if (NetMHost = nil) then
1146 begin
1147 e_LogWriteln(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT] + ' (host_create)', TMsgType.Notify);
1148 for f := 0 to High(mlist) do mlist[f].clear();
1149 SetLength(mlist, 0);
1150 Exit;
1151 end;
1152 end;
1154 isListQuery := (timeout > 0);
1155 ct := GetTimerMS();
1156 // reconnect/disconnect/pulse for each master
1157 for f := 0 to High(mlist) do
1158 begin
1159 if (not mlist[f].isValid()) then continue;
1160 if (not mlist[f].isAlive()) then
1161 begin
1162 // not connected; try to reconnect if we're asking for a host list, or we are in netgame, and we are the host
1163 if isListQuery or isMasterReportsEnabled() then
1164 begin
1165 if (mlist[f].lastDisconnectTime = 0) or (ct < mlist[f].lastDisconnectTime) or (ct-mlist[f].lastDisconnectTime >= 1000*NMASTER_TIMEOUT_RECONNECT) then
1166 begin
1167 e_LogWritefln('reconnecting to master [%s]', [mlist[f].hostName], TMsgType.Notify);
1168 mlist[f].connect();
1169 end
1170 else
1171 begin
1172 //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);
1173 end;
1174 end;
1175 end
1176 else
1177 begin
1178 // if we're not in slist query, and not in netgame (or not a host), disconnect
1179 if (not isListQuery) and (not isMasterReportsEnabled()) then
1180 begin
1181 if (mlist[f].isConnected()) and (mlist[f].updateSent) then
1182 begin
1183 e_LogWritefln('removing from master [%s]', [mlist[f].hostName], TMsgType.Notify);
1184 mlist[f].remove();
1185 end;
1186 e_LogWritefln('disconnecting from master [%s]', [mlist[f].hostName], TMsgType.Notify);
1187 mlist[f].disconnect(false);
1188 end;
1189 end;
1190 mlist[f].pulse();
1191 end;
1193 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
1194 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
1195 // thank you, enet. let's ignore failures altogether then.
1196 count := 10; // no more than ten events in a row
1197 sres := enet_host_service(NetMHost, @NetMEvent, timeout);
1198 while (sres > 0) do
1199 begin
1201 if (sres < 0) then
1202 begin
1203 e_LogWriteln(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT] + ' (host_service)', TMsgType.Notify);
1204 for f := 0 to High(mlist) do mlist[f].clear();
1205 SetLength(mlist, 0);
1206 enet_host_destroy(NetMHost);
1207 NetMHost := nil;
1208 exit;
1209 end;
1212 idx := findByPeer(NetMEvent.peer);
1213 if (idx < 0) then
1214 begin
1215 e_LogWriteln('network event from unknown master host. ignored.', TMsgType.Warning);
1216 if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then enet_packet_destroy(NetMEvent.packet);
1217 end
1218 else
1219 begin
1220 if (NetMEvent.kind = ENET_EVENT_TYPE_CONNECT) then
1221 begin
1222 mlist[idx].connectedEvent();
1223 end
1224 else if (NetMEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
1225 begin
1226 mlist[idx].disconnectedEvent();
1227 end
1228 else if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
1229 begin
1230 mlist[idx].receivedEvent(NetMEvent.packet);
1231 enet_packet_destroy(NetMEvent.packet);
1232 end;
1233 end;
1235 Dec(count);
1236 if (count = 0) then break;
1237 sres := enet_host_service(NetMHost, @NetMEvent, 0);
1238 end;
1239 end;
1242 //**************************************************************************
1243 //
1244 // gui and server list
1245 //
1246 //**************************************************************************
1248 //==========================================================================
1249 //
1250 // PingServer
1251 //
1252 //==========================================================================
1253 procedure PingServer (var S: TNetServer; Sock: ENetSocket);
1254 var
1255 Buf: ENetBuffer;
1256 Ping: array [0..9] of Byte;
1257 ClTime: Int64;
1258 begin
1259 ClTime := GetTimerMS();
1261 Buf.data := Addr(Ping[0]);
1262 Buf.dataLength := 2+8;
1264 Ping[0] := Ord('D');
1265 Ping[1] := Ord('F');
1266 Int64(Addr(Ping[2])^) := ClTime;
1268 enet_socket_send(Sock, Addr(S.PingAddr), @Buf, 1);
1269 end;
1272 //==========================================================================
1273 //
1274 // PingBcast
1275 //
1276 //==========================================================================
1277 procedure PingBcast (Sock: ENetSocket);
1278 var
1279 S: TNetServer;
1280 begin
1281 S.IP := '255.255.255.255';
1282 S.Port := NET_PING_PORT;
1283 enet_address_set_host(Addr(S.PingAddr), PChar(Addr(S.IP[1])));
1284 S.Ping := -1;
1285 S.PingAddr.port := S.Port;
1286 PingServer(S, Sock);
1287 end;
1290 //==========================================================================
1291 //
1292 // g_Net_Slist_Fetch
1293 //
1294 //==========================================================================
1295 function g_Net_Slist_Fetch (var SL: TNetServerList): Boolean;
1296 var
1297 Cnt: Byte;
1298 pkt: pENetPacket;
1299 I, RX: Integer;
1300 T: Int64;
1301 Sock: ENetSocket;
1302 Buf: ENetBuffer;
1303 InMsg: TMsg;
1304 SvAddr: ENetAddress;
1305 FromSL: Boolean;
1306 MyVer: AnsiString;
1308 procedure ProcessLocal ();
1309 begin
1310 I := Length(SL);
1311 SetLength(SL, I + 1);
1312 with SL[I] do
1313 begin
1314 IP := DecodeIPV4(SvAddr.host);
1315 Port := InMsg.ReadWord();
1316 Ping := InMsg.ReadInt64();
1317 Ping := GetTimerMS() - Ping;
1318 Name := InMsg.ReadString();
1319 Map := InMsg.ReadString();
1320 GameMode := InMsg.ReadByte();
1321 Players := InMsg.ReadByte();
1322 MaxPlayers := InMsg.ReadByte();
1323 Protocol := InMsg.ReadByte();
1324 Password := InMsg.ReadByte() = 1;
1325 LocalPl := InMsg.ReadByte();
1326 Bots := InMsg.ReadWord();
1327 end;
1328 end;
1330 procedure CheckLocalServers ();
1331 begin
1332 SetLength(SL, 0);
1334 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1335 if Sock = ENET_SOCKET_NULL then Exit;
1336 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
1337 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
1338 PingBcast(Sock);
1340 T := GetTimerMS();
1342 InMsg.Alloc(NET_BUFSIZE);
1343 Buf.data := InMsg.Data;
1344 Buf.dataLength := InMsg.MaxSize;
1345 while GetTimerMS() - T <= 500 do
1346 begin
1347 InMsg.Clear();
1349 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
1350 if RX <= 0 then continue;
1351 InMsg.CurSize := RX;
1353 InMsg.BeginReading();
1355 if InMsg.ReadChar() <> 'D' then continue;
1356 if InMsg.ReadChar() <> 'F' then continue;
1358 ProcessLocal();
1359 end;
1361 InMsg.Free();
1362 enet_socket_destroy(Sock);
1364 if Length(SL) = 0 then SL := nil;
1365 end;
1367 var
1368 f, c, n, pos: Integer;
1369 aliveCount: Integer;
1370 hasUnanswered: Boolean;
1371 stt, ct: Int64;
1372 begin
1373 result := false;
1374 SL := nil;
1376 if (not g_Net_IsNetworkAvailable()) then
1377 begin
1378 SetLength(SL, 0);
1379 exit;
1380 end;
1382 g_Net_Slist_Pulse(); // this will create mhost
1384 DisconnectAll(true); // forced disconnect
1386 NetOut.Clear();
1387 NetOut.Write(Byte(NET_MMSG_GET));
1389 // TODO: what should we identify the build with?
1390 MyVer := GAME_VERSION;
1391 NetOut.Write(MyVer);
1393 try
1394 e_WriteLog('Fetching serverlist...', TMsgType.Notify);
1395 g_Console_Add(_lc[I_NET_MSG]+_lc[I_NET_SLIST_FETCH]);
1397 // wait until all servers connected and answered
1398 stt := GetTimerMS();
1399 while true do
1400 begin
1401 aliveCount := 0;
1402 hasUnanswered := false;
1403 for f := 0 to High(mlist) do
1404 begin
1406 e_LogWritefln(' master #%d: [%s] valid=%d; alive=%d; connected=%d; connecting=%d',
1407 [f, mlist[f].hostName, Integer(mlist[f].isValid()), Integer(mlist[f].isAlive()),
1408 Integer(mlist[f].isConnected()), Integer(mlist[f].isConnecting())], TMsgType.Notify);
1410 if (not mlist[f].isValid()) then continue;
1411 if (not mlist[f].isAlive()) then
1412 begin
1413 mlist[f].connect();
1414 if (mlist[f].isAlive()) then
1415 begin
1416 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_WCONN], [mlist[f].hostName]));
1417 hasUnanswered := true;
1418 stt := GetTimerMS();
1419 end;
1420 end
1421 else if (mlist[f].isConnected()) then
1422 begin
1423 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_CONN], [mlist[f].hostName]));
1424 if (mlist[f].srvAnswered = 0) then
1425 begin
1426 pkt := enet_packet_create(NetOut.Data, NetOut.CurSize, Cardinal(ENET_PACKET_FLAG_RELIABLE));
1427 if assigned(pkt) then
1428 begin
1429 if (enet_peer_send(mlist[f].peer, NET_MCHAN_MAIN, pkt) = 0) then
1430 begin
1431 hasUnanswered := true;
1432 mlist[f].srvAnswered := 1;
1433 stt := GetTimerMS();
1434 end;
1435 end;
1436 end
1437 else if (mlist[f].srvAnswered = 1) then
1438 begin
1439 hasUnanswered := true;
1440 end
1441 else if (mlist[f].srvAnswered > 1) then
1442 begin
1443 Inc(aliveCount);
1444 end;
1445 end
1446 else if (mlist[f].isConnecting()) then
1447 begin
1448 hasUnanswered := true;
1449 end;
1450 end;
1451 if (not hasUnanswered) then break;
1452 // check for timeout
1453 ct := GetTimerMS();
1454 if (ct < stt) or (ct-stt > 4000) then break;
1455 g_Net_Slist_Pulse(300);
1456 end;
1458 if (aliveCount = 0) then
1459 begin
1460 DisconnectAll();
1461 CheckLocalServers();
1462 exit;
1463 end;
1465 slMOTD := '';
1467 slUrgent := '';
1468 slReadUrgent := true;
1471 SetLength(SL, 0);
1472 for f := 0 to High(mlist) do
1473 begin
1474 if (mlist[f].srvAnswered < 2) then continue;
1475 for n := 0 to High(mlist[f].srvAnswer) do
1476 begin
1477 pos := -1;
1478 for c := 0 to High(SL) do
1479 begin
1480 if (SL[c].IP = mlist[f].srvAnswer[n].IP) and (SL[c].Port = mlist[f].srvAnswer[n].Port) then
1481 begin
1482 pos := c;
1483 break;
1484 end;
1485 end;
1486 if (pos < 0) then
1487 begin
1488 pos := length(SL);
1489 SetLength(SL, pos+1);
1490 SL[pos] := mlist[f].srvAnswer[n];
1491 SL[pos].Number := pos;
1492 end;
1493 end;
1494 if (not mlist[f].slReadUrgent) and (mlist[f].slUrgent <> '') then
1495 begin
1496 if (mlist[f].slUrgent <> slUrgent) then
1497 begin
1498 slUrgent := mlist[f].slUrgent;
1499 slReadUrgent := false;
1500 end;
1501 end;
1502 if (slMOTD <> '') and (mlist[f].slMOTD <> '') then
1503 begin
1504 slMOTD := mlist[f].slMOTD;
1505 end;
1506 end;
1508 DisconnectAll();
1510 if (length(SL) = 0) then
1511 begin
1512 CheckLocalServers();
1513 exit;
1514 end;
1516 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1517 if Sock = ENET_SOCKET_NULL then Exit;
1518 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
1520 for I := Low(SL) to High(SL) do PingServer(SL[I], Sock);
1522 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
1523 PingBcast(Sock);
1525 T := GetTimerMS();
1527 InMsg.Alloc(NET_BUFSIZE);
1528 Buf.data := InMsg.Data;
1529 Buf.dataLength := InMsg.MaxSize;
1530 Cnt := 0;
1531 while GetTimerMS() - T <= 500 do
1532 begin
1533 InMsg.Clear();
1535 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
1536 if RX <= 0 then continue;
1537 InMsg.CurSize := RX;
1539 InMsg.BeginReading();
1541 if InMsg.ReadChar() <> 'D' then continue;
1542 if InMsg.ReadChar() <> 'F' then continue;
1544 FromSL := False;
1545 for I := Low(SL) to High(SL) do
1546 if (SL[I].PingAddr.host = SvAddr.host) and
1547 (SL[I].PingAddr.port = SvAddr.port) then
1548 begin
1549 with SL[I] do
1550 begin
1551 Port := InMsg.ReadWord();
1552 Ping := InMsg.ReadInt64();
1553 Ping := GetTimerMS() - Ping;
1554 Name := InMsg.ReadString();
1555 Map := InMsg.ReadString();
1556 GameMode := InMsg.ReadByte();
1557 Players := InMsg.ReadByte();
1558 MaxPlayers := InMsg.ReadByte();
1559 Protocol := InMsg.ReadByte();
1560 Password := InMsg.ReadByte() = 1;
1561 LocalPl := InMsg.ReadByte();
1562 Bots := InMsg.ReadWord();
1563 end;
1564 FromSL := True;
1565 Inc(Cnt);
1566 break;
1567 end;
1568 if not FromSL then
1569 ProcessLocal();
1570 end;
1572 InMsg.Free();
1573 enet_socket_destroy(Sock);
1574 finally
1575 NetOut.Clear();
1576 end;
1577 end;
1580 //==========================================================================
1581 //
1582 // GetServerFromTable
1583 //
1584 //==========================================================================
1585 function GetServerFromTable (Index: Integer; SL: TNetServerList; ST: TNetServerTable): TNetServer;
1586 begin
1587 Result.Number := 0;
1588 Result.Protocol := 0;
1589 Result.Name := '';
1590 Result.IP := '';
1591 Result.Port := 0;
1592 Result.Map := '';
1593 Result.Players := 0;
1594 Result.MaxPlayers := 0;
1595 Result.LocalPl := 0;
1596 Result.Bots := 0;
1597 Result.Ping := 0;
1598 Result.GameMode := 0;
1599 Result.Password := false;
1600 FillChar(Result.PingAddr, SizeOf(ENetAddress), 0);
1601 if ST = nil then
1602 Exit;
1603 if (Index < 0) or (Index >= Length(ST)) then
1604 Exit;
1605 Result := SL[ST[Index].Indices[ST[Index].Current]];
1606 end;
1609 //==========================================================================
1610 //
1611 // g_Serverlist_Draw
1612 //
1613 //==========================================================================
1614 procedure g_Serverlist_Draw (var SL: TNetServerList; var ST: TNetServerTable);
1615 var
1616 Srv: TNetServer;
1617 sy, i, y, mw, mx, l, motdh: Integer;
1618 cw: Byte = 0;
1619 ch: Byte = 0;
1620 ww: Word = 0;
1621 hh: Word = 0;
1622 ip: AnsiString;
1623 begin
1624 ip := '';
1625 sy := 0;
1627 e_CharFont_GetSize(gMenuFont, _lc[I_NET_SLIST], ww, hh);
1628 e_CharFont_Print(gMenuFont, (gScreenWidth div 2) - (ww div 2), 16, _lc[I_NET_SLIST]);
1630 e_TextureFontGetSize(gStdFont, cw, ch);
1632 ip := _lc[I_NET_SLIST_HELP];
1633 mw := (Length(ip) * cw) div 2;
1635 motdh := gScreenHeight - 49 - ch * b_Text_LineCount(slMOTD);
1637 e_DrawFillQuad(16, 64, gScreenWidth-16, motdh, 64, 64, 64, 110);
1638 e_DrawQuad(16, 64, gScreenWidth-16, motdh, 255, 127, 0);
1640 e_TextureFontPrintEx(gScreenWidth div 2 - mw, gScreenHeight-24, ip, gStdFont, 225, 225, 225, 1);
1642 // MOTD
1643 if slMOTD <> '' then
1644 begin
1645 e_DrawFillQuad(16, motdh, gScreenWidth-16, gScreenHeight-44, 64, 64, 64, 110);
1646 e_DrawQuad(16, motdh, gScreenWidth-16, gScreenHeight-44, 255, 127, 0);
1647 e_TextureFontPrintFmt(20, motdh + 3, slMOTD, gStdFont, False, True);
1648 end;
1650 // Urgent message
1651 if not slReadUrgent and (slUrgent <> '') then
1652 begin
1653 e_DrawFillQuad(17, 65, gScreenWidth-17, motdh-1, 64, 64, 64, 128);
1654 e_DrawFillQuad(gScreenWidth div 2 - 256, gScreenHeight div 2 - 60,
1655 gScreenWidth div 2 + 256, gScreenHeight div 2 + 60, 64, 64, 64, 128);
1656 e_DrawQuad(gScreenWidth div 2 - 256, gScreenHeight div 2 - 60,
1657 gScreenWidth div 2 + 256, gScreenHeight div 2 + 60, 255, 127, 0);
1658 e_DrawLine(1, gScreenWidth div 2 - 256, gScreenHeight div 2 - 40,
1659 gScreenWidth div 2 + 256, gScreenHeight div 2 - 40, 255, 127, 0);
1660 l := Length(_lc[I_NET_SLIST_URGENT]) div 2;
1661 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 - 58,
1662 _lc[I_NET_SLIST_URGENT], gStdFont);
1663 l := Length(slUrgent) div 2;
1664 e_TextureFontPrintFmt(gScreenWidth div 2 - 253, gScreenHeight div 2 - 38,
1665 slUrgent, gStdFont, False, True);
1666 l := Length(_lc[I_NET_SLIST_URGENT_CONT]) div 2;
1667 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 + 41,
1668 _lc[I_NET_SLIST_URGENT_CONT], gStdFont);
1669 e_DrawLine(1, gScreenWidth div 2 - 256, gScreenHeight div 2 + 40,
1670 gScreenWidth div 2 + 256, gScreenHeight div 2 + 40, 255, 127, 0);
1671 Exit;
1672 end;
1674 if SL = nil then
1675 begin
1676 l := Length(slWaitStr) div 2;
1677 e_DrawFillQuad(17, 65, gScreenWidth-17, motdh-1, 64, 64, 64, 128);
1678 e_DrawQuad(gScreenWidth div 2 - 192, gScreenHeight div 2 - 10,
1679 gScreenWidth div 2 + 192, gScreenHeight div 2 + 11, 255, 127, 0);
1680 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 - ch div 2,
1681 slWaitStr, gStdFont);
1682 Exit;
1683 end;
1685 y := 90;
1686 if (slSelection < Length(ST)) then
1687 begin
1688 I := slSelection;
1689 sy := y + 42 * I - 4;
1690 Srv := GetServerFromTable(I, SL, ST);
1691 ip := _lc[I_NET_ADDRESS] + ' ' + Srv.IP + ':' + IntToStr(Srv.Port);
1692 if Srv.Password then
1693 ip := ip + ' ' + _lc[I_NET_SERVER_PASSWORD] + ' ' + _lc[I_MENU_YES]
1694 else
1695 ip := ip + ' ' + _lc[I_NET_SERVER_PASSWORD] + ' ' + _lc[I_MENU_NO];
1696 end else
1697 if Length(ST) > 0 then
1698 slSelection := 0;
1700 mw := (gScreenWidth - 188);
1701 mx := 16 + mw;
1703 e_DrawFillQuad(16 + 1, sy, gScreenWidth - 16 - 1, sy + 40, 64, 64, 64, 0);
1704 e_DrawLine(1, 16 + 1, sy, gScreenWidth - 16 - 1, sy, 205, 205, 205);
1705 e_DrawLine(1, 16 + 1, sy + 41, gScreenWidth - 16 - 1, sy + 41, 255, 255, 255);
1707 e_DrawLine(1, 16, 85, gScreenWidth - 16, 85, 255, 127, 0);
1708 e_DrawLine(1, 16, motdh-20, gScreenWidth-16, motdh-20, 255, 127, 0);
1710 e_DrawLine(1, mx - 70, 64, mx - 70, motdh, 255, 127, 0);
1711 e_DrawLine(1, mx, 64, mx, motdh-20, 255, 127, 0);
1712 e_DrawLine(1, mx + 52, 64, mx + 52, motdh-20, 255, 127, 0);
1713 e_DrawLine(1, mx + 104, 64, mx + 104, motdh-20, 255, 127, 0);
1715 e_TextureFontPrintEx(18, 68, 'NAME/MAP', gStdFont, 255, 127, 0, 1);
1716 e_TextureFontPrintEx(mx - 68, 68, 'PING', gStdFont, 255, 127, 0, 1);
1717 e_TextureFontPrintEx(mx + 2, 68, 'MODE', gStdFont, 255, 127, 0, 1);
1718 e_TextureFontPrintEx(mx + 54, 68, 'PLRS', gStdFont, 255, 127, 0, 1);
1719 e_TextureFontPrintEx(mx + 106, 68, 'VER', gStdFont, 255, 127, 0, 1);
1721 y := 90;
1722 for I := 0 to High(ST) do
1723 begin
1724 Srv := GetServerFromTable(I, SL, ST);
1725 // Name and map
1726 e_TextureFontPrintEx(18, y, Srv.Name, gStdFont, 255, 255, 255, 1);
1727 e_TextureFontPrintEx(18, y + 16, Srv.Map, gStdFont, 210, 210, 210, 1);
1729 // Ping and similar count
1730 if (Srv.Ping < 0) or (Srv.Ping > 999) then
1731 e_TextureFontPrintEx(mx - 68, y, _lc[I_NET_SLIST_NO_ACCESS], gStdFont, 255, 0, 0, 1)
1732 else
1733 if Srv.Ping = 0 then
1734 e_TextureFontPrintEx(mx - 68, y, '<1' + _lc[I_NET_SLIST_PING_MS], gStdFont, 255, 255, 255, 1)
1735 else
1736 e_TextureFontPrintEx(mx - 68, y, IntToStr(Srv.Ping) + _lc[I_NET_SLIST_PING_MS], gStdFont, 255, 255, 255, 1);
1738 if Length(ST[I].Indices) > 1 then
1739 e_TextureFontPrintEx(mx - 68, y + 16, '< ' + IntToStr(Length(ST[I].Indices)) + ' >', gStdFont, 210, 210, 210, 1);
1741 // Game mode
1742 e_TextureFontPrintEx(mx + 2, y, g_Game_ModeToText(Srv.GameMode), gStdFont, 255, 255, 255, 1);
1744 // Players
1745 e_TextureFontPrintEx(mx + 54, y, IntToStr(Srv.Players) + '/' + IntToStr(Srv.MaxPlayers), gStdFont, 255, 255, 255, 1);
1746 e_TextureFontPrintEx(mx + 54, y + 16, IntToStr(Srv.LocalPl) + '+' + IntToStr(Srv.Bots), gStdFont, 210, 210, 210, 1);
1748 // Version
1749 e_TextureFontPrintEx(mx + 106, y, IntToStr(Srv.Protocol), gStdFont, 255, 255, 255, 1);
1751 y := y + 42;
1752 end;
1754 e_TextureFontPrintEx(20, motdh-20+3, ip, gStdFont, 205, 205, 205, 1);
1755 ip := IntToStr(Length(ST)) + _lc[I_NET_SLIST_SERVERS];
1756 e_TextureFontPrintEx(gScreenWidth - 48 - (Length(ip) + 1)*cw,
1757 motdh-20+3, ip, gStdFont, 205, 205, 205, 1);
1758 end;
1761 //==========================================================================
1762 //
1763 // g_Serverlist_GenerateTable
1764 //
1765 //==========================================================================
1766 procedure g_Serverlist_GenerateTable (SL: TNetServerList; var ST: TNetServerTable);
1767 var
1768 i, j: Integer;
1770 function FindServerInTable(Name: AnsiString): Integer;
1771 var
1772 i: Integer;
1773 begin
1774 Result := -1;
1775 if ST = nil then
1776 Exit;
1777 for i := Low(ST) to High(ST) do
1778 begin
1779 if Length(ST[i].Indices) = 0 then
1780 continue;
1781 if SL[ST[i].Indices[0]].Name = Name then
1782 begin
1783 Result := i;
1784 Exit;
1785 end;
1786 end;
1787 end;
1788 function ComparePing(i1, i2: Integer): Boolean;
1789 var
1790 p1, p2: Int64;
1791 begin
1792 p1 := SL[i1].Ping;
1793 p2 := SL[i2].Ping;
1794 if (p1 < 0) then p1 := 999;
1795 if (p2 < 0) then p2 := 999;
1796 Result := p1 > p2;
1797 end;
1798 procedure SortIndices(var ind: Array of Integer);
1799 var
1800 I, J: Integer;
1801 T: Integer;
1802 begin
1803 for I := High(ind) downto Low(ind) do
1804 for J := Low(ind) to High(ind) - 1 do
1805 if ComparePing(ind[j], ind[j+1]) then
1806 begin
1807 T := ind[j];
1808 ind[j] := ind[j+1];
1809 ind[j+1] := T;
1810 end;
1811 end;
1812 procedure SortRows();
1813 var
1814 I, J: Integer;
1815 T: TNetServerRow;
1816 begin
1817 for I := High(ST) downto Low(ST) do
1818 for J := Low(ST) to High(ST) - 1 do
1819 if ComparePing(ST[j].Indices[0], ST[j+1].Indices[0]) then
1820 begin
1821 T := ST[j];
1822 ST[j] := ST[j+1];
1823 ST[j+1] := T;
1824 end;
1825 end;
1826 begin
1827 ST := nil;
1828 if SL = nil then
1829 Exit;
1830 for i := Low(SL) to High(SL) do
1831 begin
1832 j := FindServerInTable(SL[i].Name);
1833 if j = -1 then
1834 begin
1835 j := Length(ST);
1836 SetLength(ST, j + 1);
1837 ST[j].Current := 0;
1838 SetLength(ST[j].Indices, 1);
1839 ST[j].Indices[0] := i;
1840 end
1841 else
1842 begin
1843 SetLength(ST[j].Indices, Length(ST[j].Indices) + 1);
1844 ST[j].Indices[High(ST[j].Indices)] := i;
1845 end;
1846 end;
1848 for i := Low(ST) to High(ST) do
1849 SortIndices(ST[i].Indices);
1851 SortRows();
1852 end;
1855 //==========================================================================
1856 //
1857 // g_Serverlist_Control
1858 //
1859 //==========================================================================
1860 procedure g_Serverlist_Control (var SL: TNetServerList; var ST: TNetServerTable);
1861 var
1862 qm: Boolean;
1863 Srv: TNetServer;
1864 begin
1865 g_Net_Slist_Pulse();
1867 if gConsoleShow or gChatShow then
1868 Exit;
1870 qm := sys_HandleInput(); // this updates kbd
1872 if qm or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
1873 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or
1874 e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
1875 begin
1876 SL := nil;
1877 ST := nil;
1878 gState := STATE_MENU;
1879 g_GUI_ShowWindow('MainMenu');
1880 g_GUI_ShowWindow('NetGameMenu');
1881 g_GUI_ShowWindow('NetClientMenu');
1882 g_Sound_PlayEx(WINDOW_CLOSESOUND);
1883 Exit;
1884 end;
1886 // if there's a message on the screen,
1887 if not slReadUrgent and (slUrgent <> '') then
1888 begin
1889 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1890 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
1891 slReadUrgent := True;
1892 Exit;
1893 end;
1895 if e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_JUMP) or
1896 e_KeyPressed(JOY0_ACTIVATE) or e_KeyPressed(JOY1_ACTIVATE) or e_KeyPressed(JOY2_ACTIVATE) or e_KeyPressed(JOY3_ACTIVATE) then
1897 begin
1898 if not slFetched then
1899 begin
1900 slWaitStr := _lc[I_NET_SLIST_WAIT];
1902 g_Game_Draw;
1903 sys_Repaint;
1905 if g_Net_Slist_Fetch(SL) then
1906 begin
1907 if SL = nil then
1908 slWaitStr := _lc[I_NET_SLIST_NOSERVERS];
1909 end
1910 else
1911 if SL = nil then
1912 slWaitStr := _lc[I_NET_SLIST_ERROR];
1913 slFetched := True;
1914 slSelection := 0;
1915 g_Serverlist_GenerateTable(SL, ST);
1916 end;
1917 end
1918 else
1919 slFetched := False;
1921 if SL = nil then Exit;
1923 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1924 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
1925 begin
1926 if not slReturnPressed then
1927 begin
1928 Srv := GetServerFromTable(slSelection, SL, ST);
1929 if Srv.Password then
1930 begin
1931 PromptIP := Srv.IP;
1932 PromptPort := Srv.Port;
1933 gState := STATE_MENU;
1934 g_GUI_ShowWindow('ClientPasswordMenu');
1935 SL := nil;
1936 ST := nil;
1937 slReturnPressed := True;
1938 Exit;
1939 end
1940 else
1941 g_Game_StartClient(Srv.IP, Srv.Port, '');
1942 SL := nil;
1943 ST := nil;
1944 slReturnPressed := True;
1945 Exit;
1946 end;
1947 end
1948 else
1949 slReturnPressed := False;
1951 if e_KeyPressed(IK_DOWN) or e_KeyPressed(IK_KPDOWN) or e_KeyPressed(VK_DOWN) or
1952 e_KeyPressed(JOY0_DOWN) or e_KeyPressed(JOY1_DOWN) or e_KeyPressed(JOY2_DOWN) or e_KeyPressed(JOY3_DOWN) then
1953 begin
1954 if not slDirPressed then
1955 begin
1956 Inc(slSelection);
1957 if slSelection > High(ST) then slSelection := 0;
1958 slDirPressed := True;
1959 end;
1960 end;
1962 if e_KeyPressed(IK_UP) or e_KeyPressed(IK_KPUP) or e_KeyPressed(VK_UP) or
1963 e_KeyPressed(JOY0_UP) or e_KeyPressed(JOY1_UP) or e_KeyPressed(JOY2_UP) or e_KeyPressed(JOY3_UP) then
1964 begin
1965 if not slDirPressed then
1966 begin
1967 if slSelection = 0 then slSelection := Length(ST);
1968 Dec(slSelection);
1970 slDirPressed := True;
1971 end;
1972 end;
1974 if e_KeyPressed(IK_RIGHT) or e_KeyPressed(IK_KPRIGHT) or e_KeyPressed(VK_RIGHT) or
1975 e_KeyPressed(JOY0_RIGHT) or e_KeyPressed(JOY1_RIGHT) or e_KeyPressed(JOY2_RIGHT) or e_KeyPressed(JOY3_RIGHT) then
1976 begin
1977 if not slDirPressed then
1978 begin
1979 Inc(ST[slSelection].Current);
1980 if ST[slSelection].Current > High(ST[slSelection].Indices) then ST[slSelection].Current := 0;
1981 slDirPressed := True;
1982 end;
1983 end;
1985 if e_KeyPressed(IK_LEFT) or e_KeyPressed(IK_KPLEFT) or e_KeyPressed(VK_LEFT) or
1986 e_KeyPressed(JOY0_LEFT) or e_KeyPressed(JOY1_LEFT) or e_KeyPressed(JOY2_LEFT) or e_KeyPressed(JOY3_LEFT) then
1987 begin
1988 if not slDirPressed then
1989 begin
1990 if ST[slSelection].Current = 0 then ST[slSelection].Current := Length(ST[slSelection].Indices);
1991 Dec(ST[slSelection].Current);
1993 slDirPressed := True;
1994 end;
1995 end;
1997 if (not e_KeyPressed(IK_DOWN)) and
1998 (not e_KeyPressed(IK_UP)) and
1999 (not e_KeyPressed(IK_RIGHT)) and
2000 (not e_KeyPressed(IK_LEFT)) and
2001 (not e_KeyPressed(IK_KPDOWN)) and
2002 (not e_KeyPressed(IK_KPUP)) and
2003 (not e_KeyPressed(IK_KPRIGHT)) and
2004 (not e_KeyPressed(IK_KPLEFT)) and
2005 (not e_KeyPressed(VK_DOWN)) and
2006 (not e_KeyPressed(VK_UP)) and
2007 (not e_KeyPressed(VK_RIGHT)) and
2008 (not e_KeyPressed(VK_LEFT)) and
2009 (not e_KeyPressed(JOY0_UP)) and (not e_KeyPressed(JOY1_UP)) and (not e_KeyPressed(JOY2_UP)) and (not e_KeyPressed(JOY3_UP)) and
2010 (not e_KeyPressed(JOY0_DOWN)) and (not e_KeyPressed(JOY1_DOWN)) and (not e_KeyPressed(JOY2_DOWN)) and (not e_KeyPressed(JOY3_DOWN)) and
2011 (not e_KeyPressed(JOY0_LEFT)) and (not e_KeyPressed(JOY1_LEFT)) and (not e_KeyPressed(JOY2_LEFT)) and (not e_KeyPressed(JOY3_LEFT)) and
2012 (not e_KeyPressed(JOY0_RIGHT)) and (not e_KeyPressed(JOY1_RIGHT)) and (not e_KeyPressed(JOY2_RIGHT)) and (not e_KeyPressed(JOY3_RIGHT))
2013 then
2014 slDirPressed := False;
2015 end;
2018 end.