Net: Don't send large unreliable packets by reliable fragments
[d2df-sdl.git] / src / game / g_netmaster.pas
blob0cdd43bf9973d10b354dff35cd63394e8a8b3aef
1 (* Copyright (C) Doom 2D: Forever Developers
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.
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.
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/>.
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);
96 procedure finish ();
97 procedure cleanup ();
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;
125 slCurrent: TNetServerList;
126 slTable: TNetServerTable;
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_Draw (var SL: TNetServerList; var ST: TNetServerTable);
167 procedure g_Serverlist_Control (var SL: TNetServerList; var ST: TNetServerTable);
169 function GetTimerMS (): Int64;
172 implementation
174 uses
175 {$IFDEF ENABLE_SOUND}
176 g_sound,
177 {$ENDIF}
178 e_input, e_graphics, e_log, g_window, g_net, g_console,
179 g_map, g_game, g_gui, g_menu, g_options, g_language, g_basic,
180 wadreader, g_system, utils, hashtable;
182 // ////////////////////////////////////////////////////////////////////////// //
184 type
185 THashStrDWord = specialize THashBase<AnsiString, LongWord, THashKeyStrAnsiCI>;
188 NetMHost: pENetHost;
189 NetMEvent: ENetEvent;
190 mlist: array of TMasterHost;
192 slSelection: Byte;
193 slFetched: Boolean;
194 slDirPressed: Boolean;
195 slReadUrgent: Boolean;
197 reportsEnabled: Boolean = True;
198 knownHosts: THashStrDWord;
200 //==========================================================================
202 // GetTimerMS
204 //==========================================================================
205 function GetTimerMS (): Int64;
206 begin
207 Result := sys_GetTicks() {div 1000};
208 end;
210 //==========================================================================
212 // findByPeer
214 //==========================================================================
215 function findByPeer (peer: pENetPeer): Integer;
217 f: Integer;
218 begin
219 for f := 0 to High(mlist) do
220 if (mlist[f].peer = peer) then
221 exit(f);
223 Result := -1;
224 end;
226 //==========================================================================
228 // ShutdownAll
230 //==========================================================================
231 procedure g_Net_Slist_ShutdownAll ();
233 f, sres, idx: Integer;
234 stt, ct: Int64;
235 activeCount: Integer = 0;
236 label // all this code is retarded anyway, so I feel no shame
237 discard;
238 begin
239 if (NetMHost = nil) then goto discard;
240 for f := 0 to High(mlist) do
241 begin
242 if (mlist[f].isAlive()) then
243 begin
244 Inc(activeCount);
245 if (mlist[f].isConnected() and mlist[f].updateSent) then
246 begin
247 writeln('unregistering from [', mlist[f].hostName, ']');
248 mlist[f].remove();
249 end;
250 //mlist[f].disconnect(false);
251 enet_peer_disconnect_later(mlist[f].peer, 0);
252 end;
253 end;
254 if (activeCount = 0) then goto discard;
255 stt := GetTimerMS();
256 while (activeCount > 0) do
257 begin
258 ct := GetTimerMS();
259 if (ct < stt) or (ct-stt >= 1500) then break;
261 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
262 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
263 // thank you, enet. let's ignore failures altogether then.
264 sres := enet_host_service(NetMHost, @NetMEvent, 100);
265 // if (sres < 0) then break;
266 if (sres <= 0) then continue;
268 idx := findByPeer(NetMEvent.peer);
269 if (idx < 0) then
270 begin
271 if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then enet_packet_destroy(NetMEvent.packet);
272 continue;
273 end;
275 if (NetMEvent.kind = ENET_EVENT_TYPE_CONNECT) then
276 begin
277 mlist[idx].connectedEvent();
278 //mlist[idx].disconnect(false);
279 enet_peer_disconnect(mlist[f].peer, 0);
281 else if (NetMEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
282 begin
283 mlist[idx].disconnectedEvent();
284 Dec(activeCount);
286 else if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
287 begin
288 mlist[idx].receivedEvent(NetMEvent.packet);
289 enet_packet_destroy(NetMEvent.packet);
290 end;
291 end;
292 enet_host_destroy(NetMHost);
293 NetMHost := nil;
295 discard:
296 for f := 0 to High(mlist) do
297 mlist[f].finish();
298 SetLength(mlist, 0);
300 FreeAndNil(knownHosts);
301 end;
303 //==========================================================================
305 // DisconnectAll
307 //==========================================================================
308 procedure DisconnectAll (forced: Boolean=false);
310 f: Integer;
311 begin
312 for f := 0 to High(mlist) do
313 begin
314 if (mlist[f].isAlive()) then mlist[f].disconnect(forced);
315 end;
316 end;
318 //==========================================================================
320 // ConnectAll
322 //==========================================================================
323 procedure ConnectAll (sendUpdate: Boolean);
325 f: Integer;
326 begin
327 // set flags; pulse will take care of the rest
328 for f := 0 to High(mlist) do
329 begin
330 // force reconnect
331 mlist[f].lastDisconnectTime := 0;
332 // force updating
333 if (sendUpdate) then
334 begin
335 mlist[f].NetUpdatePending := true;
336 mlist[f].lastUpdateTime := 0;
337 end;
338 end;
339 end;
341 //==========================================================================
343 // UpdateAll
345 //==========================================================================
346 procedure UpdateAll (force: Boolean);
348 f: Integer;
349 begin
350 // set flags; pulse will take care of the rest
351 for f := 0 to High(mlist) do
352 begin
353 if (not mlist[f].isAlive()) then continue;
354 mlist[f].NetUpdatePending := true;
355 if (force) then mlist[f].lastUpdateTime := 0;
356 end;
357 end;
359 //**************************************************************************
361 // public api
363 //**************************************************************************
365 //==========================================================================
367 // g_Net_Slist_Private
369 // make this server private
371 //==========================================================================
372 procedure g_Net_Slist_Private ();
373 begin
374 DisconnectAll();
375 reportsEnabled := false;
376 end;
378 //==========================================================================
380 // g_Net_Slist_Public
382 // make this server public
384 //==========================================================================
385 procedure g_Net_Slist_Public ();
386 begin
387 if (not reportsEnabled) then
388 begin
389 reportsEnabled := true;
390 ConnectAll(true);
391 end;
392 end;
394 //==========================================================================
396 // g_Net_Slist_ServerUpdate
398 // called while the server is running
400 //==========================================================================
401 procedure g_Net_Slist_ServerUpdate ();
402 begin
403 UpdateAll(false);
404 end;
406 // called when the server is started
407 procedure g_Net_Slist_ServerStarted ();
408 begin
409 reportsEnabled := NetUseMaster;
410 if reportsEnabled and g_Game_IsServer() and g_Game_IsNet() then
411 begin
412 writeln('*** server started; reporting to master...');
413 ConnectAll(true);
414 end;
415 end;
417 //==========================================================================
419 // g_Net_Slist_ServerClosed
421 // called when the server is stopped
423 //==========================================================================
424 procedure g_Net_Slist_ServerClosed ();
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;
439 //==========================================================================
441 // g_Net_Slist_ServerPlayerComes
443 // called when new netword player comes
445 //==========================================================================
446 procedure g_Net_Slist_ServerPlayerComes ();
447 begin
448 UpdateAll(true);
449 end;
451 //==========================================================================
453 // g_Net_Slist_ServerPlayerLeaves
455 // called when new netword player comes
457 //==========================================================================
458 procedure g_Net_Slist_ServerPlayerLeaves ();
459 begin
460 UpdateAll(true);
461 end;
463 //==========================================================================
465 // g_Net_Slist_ServerMapStarted
467 // started new map
469 //==========================================================================
470 procedure g_Net_Slist_ServerMapStarted ();
471 begin
472 UpdateAll(true);
473 end;
475 //==========================================================================
477 // g_Net_Slist_ServerRenamed
479 // this server renamed (or password mode changed, or other params changed)
481 //==========================================================================
482 procedure g_Net_Slist_ServerRenamed ();
483 begin
484 UpdateAll(true);
485 end;
487 //**************************************************************************
489 // TMasterHost
491 //**************************************************************************
493 constructor TMasterHost.Create (var ea: ENetAddress);
494 begin
495 peer := nil;
496 NetHostConnected := false;
497 NetHostConReqTime := 0;
498 NetUpdatePending := false;
499 lastDisconnectTime := 0;
500 updateSent := false;
501 lastUpdateTime := 0;
502 hostName := '';
503 ZeroMemory(@enetAddr, sizeof(enetAddr));
504 SetLength(srvAnswer, 0);
505 srvAnswered := 0;
506 slMOTD := '';
507 slUrgent := '';
508 slReadUrgent := true;
509 justAdded := false;
510 connectCount := 0;
511 netmsg.Alloc(NET_BUFSIZE);
512 setAddress(ea, '');
513 end;
515 procedure TMasterHost.finish ();
516 begin
517 netmsg.Free();
518 end;
520 procedure TMasterHost.cleanup ();
521 begin
522 updateSent := False; // do not send 'remove'
523 disconnect(True);
524 hostName := '';
525 netmsg.Clear();
526 SetLength(srvAnswer, 0);
527 srvAnswered := 0;
528 slMOTD := '';
529 slUrgent := '';
530 slReadUrgent := True;
531 ZeroMemory(@enetAddr, sizeof(enetAddr));
532 end;
534 //==========================================================================
536 // TMasterHost.setAddress
538 //==========================================================================
539 function TMasterHost.setAddress (var ea: ENetAddress; hostStr: AnsiString): Boolean;
540 begin
541 result := false;
542 SetLength(srvAnswer, 0);
543 srvAnswered := 0;
544 slMOTD := '';
545 slUrgent := '';
546 slReadUrgent := true;
547 updateSent := false; // do not send 'remove'
548 disconnect(true);
549 hostName := '';
551 if (not g_Net_IsNetworkAvailable()) then exit;
553 enetAddr := ea;
554 if (enetAddr.host = 0) or (enetAddr.port = 0) then exit;
556 if (length(hostStr) > 0) then hostName := hostStr else hostName := IntToStr(enetAddr.host)+':'+IntToStr(ea.port);
558 result := isValid();
559 end;
561 //==========================================================================
563 // TMasterHost.isValid
565 //==========================================================================
566 function TMasterHost.isValid (): Boolean;
567 begin
568 result := (enetAddr.host <> 0) and (enetAddr.port <> 0);
569 end;
571 //==========================================================================
573 // TMasterHost.isAlive
575 // not disconnected
577 //==========================================================================
578 function TMasterHost.isAlive (): Boolean;
579 begin
580 result := (NetMHost <> nil) and (peer <> nil);
581 end;
583 //==========================================================================
585 // TMasterHost.isConnecting
587 // is connection in progress?
589 //==========================================================================
590 function TMasterHost.isConnecting (): Boolean;
591 begin
592 result := isAlive() and (not NetHostConnected) and (NetHostConReqTime <> -1);
593 end;
595 //==========================================================================
597 // TMasterHost.isConnected
599 //==========================================================================
600 function TMasterHost.isConnected (): Boolean;
601 begin
602 result := isAlive() and (NetHostConnected) and (NetHostConReqTime <> -1);
603 end;
605 //==========================================================================
607 // TMasterHost.connectedEvent
609 //==========================================================================
610 procedure TMasterHost.connectedEvent ();
611 begin
612 if not isAlive() then exit;
613 if NetHostConnected then exit;
614 NetHostConnected := true;
615 NetHostConReqTime := 0; // just in case
616 e_LogWritefln('connected to master at [%s]', [hostName], TMsgType.Notify);
617 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_CONN], [mlist[f].hostName]));
618 end;
620 //==========================================================================
622 // TMasterHost.disconnectedEvent
624 //==========================================================================
625 procedure TMasterHost.disconnectedEvent ();
626 begin
627 if not isAlive() then exit;
628 e_LogWritefln('disconnected from master at [%s]', [hostName], TMsgType.Notify);
629 disconnect(true);
630 //if (spamConsole) then g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_DISC], [hostName]));
631 end;
633 //==========================================================================
635 // TMasterHost.receivedEvent
637 // `pkt` is never `nil`
639 //==========================================================================
640 procedure TMasterHost.receivedEvent (pkt: pENetPacket);
642 msg: TMsg;
643 MID: Byte;
644 Cnt: Byte;
645 f: Integer;
646 s: AnsiString;
647 begin
648 e_LogWritefln('received packed from master at [%s]', [hostName], TMsgType.Notify);
649 if not msg.Init(pkt^.data, pkt^.dataLength, True) then exit;
650 // packet type
651 MID := msg.ReadByte();
652 if (MID <> NET_MMSG_GET) then exit;
653 e_LogWritefln('received list packet from master at [%s]', [hostName], TMsgType.Notify);
654 SetLength(srvAnswer, 0);
655 if (srvAnswered > 0) then Inc(srvAnswered);
656 slMOTD := '';
657 //slUrgent := '';
658 slReadUrgent := true;
659 // number of items
660 Cnt := msg.ReadByte();
661 //g_Console_Add(_lc[I_NET_MSG]+Format(_lc[I_NET_SLIST_RETRIEVED], [Cnt, hostName]), True);
662 e_LogWritefln('got %u server(s) from master at [%s]', [Cnt, hostName], TMsgType.Notify);
663 if (Cnt > 0) then
664 begin
665 SetLength(srvAnswer, Cnt);
666 for f := 0 to Cnt-1 do
667 begin
668 srvAnswer[f].Number := f;
669 srvAnswer[f].IP := msg.ReadString();
670 srvAnswer[f].Port := msg.ReadWord();
671 srvAnswer[f].Name := msg.ReadString();
672 srvAnswer[f].Map := msg.ReadString();
673 srvAnswer[f].GameMode := msg.ReadByte();
674 srvAnswer[f].Players := msg.ReadByte();
675 srvAnswer[f].MaxPlayers := msg.ReadByte();
676 srvAnswer[f].Protocol := msg.ReadByte();
677 srvAnswer[f].Password := msg.ReadByte() = 1;
678 enet_address_set_host(Addr(srvAnswer[f].PingAddr), PChar(Addr(srvAnswer[f].IP[1])));
679 srvAnswer[f].Ping := -1;
680 srvAnswer[f].PingAddr.port := NET_PING_PORT;
681 end;
682 end;
684 if (msg.ReadCount < msg.CurSize) then
685 begin
686 // new master, supports version reports
687 s := msg.ReadString();
688 if (s <> {MyVer}GAME_VERSION) then
689 begin
690 { TODO }
691 g_Console_Add('!!! UpdVer = `'+s+'`');
692 end;
693 // even newer master, supports extra info
694 if (msg.ReadCount < msg.CurSize) then
695 begin
696 slMOTD := b_Text_Format(msg.ReadString());
697 if (slMOTD <> '') then e_LogWritefln('got MOTD from master at [%s]: %s', [hostName, slMOTD], TMsgType.Notify);
698 s := b_Text_Format(msg.ReadString());
699 // check if the message has updated and the user has to read it again
700 if (slUrgent <> s) then slReadUrgent := false;
701 slUrgent := s;
702 if (s <> '') then e_LogWritefln('got urgent from master at [%s]: %s', [hostName, s], TMsgType.Notify);
703 end;
704 end;
705 end;
707 //==========================================================================
709 // TMasterHost.disconnect
711 //==========================================================================
712 procedure TMasterHost.disconnect (forced: Boolean);
713 begin
714 if isAlive() then
715 begin
716 lastDisconnectTime := GetTimerMS();
717 if forced or (not NetHostConnected) or (NetHostConReqTime = -1) then
718 begin
719 enet_peer_reset(peer);
720 peer := nil;
721 NetHostConReqTime := 0;
722 updateSent := false;
724 else
725 begin
726 enet_peer_disconnect_later(peer, 0);
727 // main pulse will take care of the rest
728 NetHostConReqTime := -1;
729 end;
731 else
732 begin
733 // just in case
734 NetHostConReqTime := 0;
735 updateSent := false;
736 end;
738 NetHostConnected := false;
739 NetUpdatePending := false;
740 lastUpdateTime := 0;
741 //if (spamConsole) then g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_DISC], [hostName]));
742 end;
744 //==========================================================================
746 // TMasterHost.connect
748 //==========================================================================
749 function TMasterHost.connect (): Boolean;
750 begin
751 result := false;
752 if not isValid() then exit;
753 if (NetHostConReqTime = -1) then
754 begin
755 disconnect(true);
756 if (NetHostConReqTime = -1) then e_LogWritefln('ketmar broke master [%s] logic! (000)', [hostName], TMsgType.Notify);
757 if (isAlive()) then e_LogWritefln('ketmar broke master [%s] logic! (001)', [hostName], TMsgType.Notify);
759 else
760 begin
761 if isAlive() then begin result := true; exit; end;
762 end;
764 lastDisconnectTime := GetTimerMS(); // why not?
765 SetLength(srvAnswer, 0);
766 srvAnswered := 0;
767 NetHostConnected := false;
768 NetHostConReqTime := 0;
769 NetUpdatePending := false;
770 updateSent := false;
771 lastUpdateTime := 0;
772 Inc(connectCount);
774 peer := enet_host_connect(NetMHost, @enetAddr, NET_MCHANS, 0);
775 if (peer = nil) then
776 begin
777 g_Console_Add(_lc[I_NET_MSG_ERROR]+_lc[I_NET_ERR_CLIENT], true);
778 exit;
779 end;
781 NetHostConReqTime := lastDisconnectTime;
782 e_LogWritefln('connecting to master at [%s]', [hostName], TMsgType.Notify);
783 end;
785 //==========================================================================
787 // TMasterHost.writeInfo
789 //==========================================================================
790 class procedure TMasterHost.writeInfo (var msg: TMsg);
792 wad, map: AnsiString;
793 begin
794 wad := g_ExtractWadNameNoPath(gMapInfo.Map);
795 map := g_ExtractFileName(gMapInfo.Map);
797 msg.Write(NetServerName);
799 msg.Write(wad+':/'+map);
800 msg.Write(gGameSettings.GameMode);
802 msg.Write(Byte(NetClientCount));
804 msg.Write(NetMaxClients);
806 msg.Write(Byte(NET_PROTOCOL_VER));
807 msg.Write(Byte(NetPassword <> ''));
808 end;
810 //==========================================================================
812 // TMasterHost.update
814 //==========================================================================
815 procedure TMasterHost.update ();
817 pkt: pENetPacket;
818 begin
819 if not isAlive() then exit;
820 if not isConnected() then
821 begin
822 NetUpdatePending := isConnecting();
823 exit;
824 end;
826 netmsg.Clear();
828 if reportsEnabled and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster then
829 begin
831 netmsg.Write(Byte(NET_MMSG_UPD));
832 netmsg.Write(NetAddr.port);
833 //writeln(formatstrf('%08x', [NetAddr.host]), ' : ', NetAddr.host);
835 writeInfo(netmsg);
837 pkt := enet_packet_create(netmsg.Data, netmsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
838 if assigned(pkt) then
839 begin
840 if (enet_peer_send(peer, NET_MCHAN_UPD, pkt) = 0) then
841 begin
842 e_LogWritefln('sent update to master at [%s]', [hostName], TMsgType.Notify);
843 NetUpdatePending := false;
844 updateSent := true;
845 end;
846 end;
847 finally
848 netmsg.Clear();
849 end;
851 else
852 begin
853 NetUpdatePending := false;
854 end;
855 end;
857 //==========================================================================
859 // TMasterHost.remove
861 //==========================================================================
862 procedure TMasterHost.remove ();
864 pkt: pENetPacket;
865 begin
866 NetUpdatePending := false;
867 lastUpdateTime := 0;
868 updateSent := false;
869 if not isAlive() then exit;
870 if not isConnected() then exit;
872 netmsg.Clear();
874 netmsg.Write(Byte(NET_MMSG_DEL));
875 netmsg.Write(NetAddr.port);
877 pkt := enet_packet_create(netmsg.Data, netmsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
878 if assigned(pkt) then
879 begin
880 enet_peer_send(peer, NET_MCHAN_MAIN, pkt);
881 end;
882 finally
883 netmsg.Clear();
884 end;
885 end;
887 //==========================================================================
889 // TMasterHost.pulse
891 // this performs various scheduled tasks, if necessary
893 //==========================================================================
894 procedure TMasterHost.pulse ();
896 ct: Int64;
897 mrate: Cardinal;
898 begin
899 if not isAlive() then exit;
900 if (NetHostConReqTime = -1) then exit; // waiting for shutdown (disconnect in progress)
901 ct := GetTimerMS();
902 // process pending connection timeout
903 if (not NetHostConnected) then
904 begin
905 if (ct < NetHostConReqTime) or (ct-NetHostConReqTime >= 1000*NMASTER_TIMEOUT_CONNECT) then
906 begin
907 e_LogWritefln('failed to connect to master at [%s]', [hostName], TMsgType.Notify);
908 // do not spam with error messages, it looks like the master is down
909 //g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR], True);
910 disconnect(true);
911 end;
912 exit;
913 end;
914 // send update, if necessary
915 if (NetUpdatePending) then
916 begin
917 mrate := NetMasterRate;
918 if (mrate < 10000) then mrate := 10000
919 else if (mrate > 1000*60*10) then mrate := 1000*60*10;
920 if (NMASTER_FORCE_UPDATE_TIMEOUT > 0) then mrate := NMASTER_FORCE_UPDATE_TIMEOUT*1000;
921 if (lastUpdateTime = 0) or (ct < lastUpdateTime) or (ct-lastUpdateTime >= mrate) then
922 begin
923 //e_LogWritefln('update timeout: %d', [Integer(mrate)], TMsgType.Notify);
924 lastUpdateTime := ct;
925 update();
926 end;
927 end;
928 end;
930 //==========================================================================
932 // parseAddressPort
934 //==========================================================================
935 function parseAddressPort (var ea: ENetAddress; hostandport: AnsiString): Boolean;
937 cp, port: Integer;
938 hostName: AnsiString;
939 ip: LongWord;
940 begin
941 result := false;
942 if (not g_Net_IsNetworkAvailable()) then exit;
944 hostandport := Trim(hostandport);
945 if (length(hostandport) = 0) then exit;
947 hostName := hostandport;
948 port := 25665;
950 cp := Pos(':', hostandport);
951 if (cp > 0) then
952 begin
953 hostName := Trim(Copy(hostandport, 1, cp-1));
954 Delete(hostandport, 1, cp);
955 hostandport := Trim(hostandport);
956 if (length(hostandport) > 0) then
957 begin
959 port := StrToInt(hostandport);
960 except
961 port := -1;
962 end;
963 end;
964 end;
966 if (length(hostName) = 0) then exit;
967 if (port < 1) or (port > 65535) then exit;
969 if not assigned(knownHosts) then knownHosts := THashStrDWord.Create();
971 if knownHosts.get(hostName, ip) then
972 begin
973 ea.host := ip;
975 else
976 begin
977 if (enet_address_set_host(@ea, PChar(Addr(hostName[1]))) <> 0) then
978 begin
979 knownHosts.put(hostName, 0);
980 exit;
981 end;
982 knownHosts.put(hostName, ea.host);
983 end;
984 ea.Port := port;
985 result := true;
986 end;
988 //==========================================================================
990 // addMasterRecord
992 //==========================================================================
993 procedure addMasterRecord (var ea: ENetAddress; sa: AnsiString);
995 f: Integer;
996 freeIdx: Integer;
997 begin
998 freeIdx := -1;
999 for f := 0 to High(mlist) do
1000 begin
1001 if (mlist[f].enetAddr.host = ea.host) and (mlist[f].enetAddr.port = ea.port) then
1002 begin
1003 mlist[f].justAdded := true;
1004 exit;
1005 end;
1006 if (freeIdx < 0) and (not mlist[f].isValid()) then freeIdx := f;
1007 end;
1008 if (freeIdx < 0) then
1009 begin
1010 freeIdx := length(mlist);
1011 SetLength(mlist, freeIdx+1);
1012 mlist[freeIdx].Create(ea);
1013 end;
1014 mlist[freeIdx].justAdded := true;
1015 mlist[freeIdx].setAddress(ea, sa);
1016 e_LogWritefln('added masterserver with address [%s]', [sa], TMsgType.Notify);
1017 end;
1019 //==========================================================================
1021 // g_Net_Slist_Set
1023 //==========================================================================
1024 procedure g_Net_Slist_Set (list: AnsiString);
1026 f, dest: Integer;
1027 sa: AnsiString;
1028 ea: ENetAddress;
1029 pp: Integer;
1030 begin
1031 if (not g_Net_IsNetworkAvailable()) then exit;
1033 for f := 0 to High(mlist) do mlist[f].justAdded := false;
1035 list := Trim(list);
1036 //writeln('list=[', list, ']');
1037 while (length(list) > 0) do
1038 begin
1039 pp := Pos(',', list);
1040 if (pp < 1) then pp := length(list)+1;
1041 sa := Trim(Copy(list, 1, pp-1));
1042 Delete(list, 1, pp);
1043 //writeln(' sa=[', sa, ']');
1044 if (length(sa) > 0) and parseAddressPort(ea, sa) then addMasterRecord(ea, sa);
1045 end;
1047 // remove unknown master servers
1048 dest := 0;
1049 for f := 0 to High(mlist) do
1050 begin
1051 if (not mlist[f].justAdded) then mlist[f].cleanup();
1052 if (mlist[f].isValid()) then
1053 begin
1054 if (dest < f) then
1055 begin
1056 mlist[dest].finish();
1057 mlist[dest] := mlist[f];
1058 end;
1059 dest += 1;
1060 end;
1061 end;
1062 SetLength(mlist, dest);
1063 end;
1065 //**************************************************************************
1067 // main pulse
1069 //**************************************************************************
1071 //==========================================================================
1073 // isMasterReportsEnabled
1075 //==========================================================================
1076 function isMasterReportsEnabled (): Boolean;
1077 begin
1078 result := (reportsEnabled and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster);
1079 end;
1081 //==========================================================================
1083 // g_Net_Slist_Pulse
1085 // non-zero timeout ignores current status (used to fetch server list)
1087 //==========================================================================
1088 procedure g_Net_Slist_Pulse (timeout: Integer=0);
1090 f: Integer;
1091 sres: Integer;
1092 idx: Integer;
1093 ct: Int64;
1094 isListQuery: Boolean;
1095 count: Integer;
1096 begin
1097 if (not g_Net_IsNetworkAvailable()) then exit;
1099 if (length(mlist) = 0) then
1100 begin
1101 if (NetMHost <> nil) then
1102 begin
1103 enet_host_destroy(NetMHost);
1104 NetMHost := nil;
1105 exit;
1106 end;
1107 end;
1109 if (NetMHost = nil) then
1110 begin
1111 NetMHost := enet_host_create(nil, 64, NET_MCHANS, 1024*1024, 1024*1024);
1112 if (NetMHost = nil) then
1113 begin
1114 e_LogWriteln(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT] + ' (host_create)', TMsgType.Notify);
1115 for f := 0 to High(mlist) do mlist[f].finish();
1116 SetLength(mlist, 0);
1117 Exit;
1118 end;
1119 end;
1121 isListQuery := (timeout > 0);
1122 ct := GetTimerMS();
1123 // reconnect/disconnect/pulse for each master
1124 for f := 0 to High(mlist) do
1125 begin
1126 if (not mlist[f].isValid()) then continue;
1127 if (not mlist[f].isAlive()) then
1128 begin
1129 // not connected; try to reconnect if we're asking for a host list, or we are in netgame, and we are the host
1130 if (not isListQuery) and isMasterReportsEnabled() then
1131 begin
1132 if (mlist[f].lastDisconnectTime = 0) or (ct < mlist[f].lastDisconnectTime) or (ct-mlist[f].lastDisconnectTime >= 1000*NMASTER_TIMEOUT_RECONNECT) then
1133 begin
1134 e_LogWritefln('reconnecting to master [%s]', [mlist[f].hostName], TMsgType.Notify);
1135 mlist[f].connect();
1137 else
1138 begin
1139 //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);
1140 end;
1141 end;
1143 else
1144 begin
1145 // if we're not in slist query, and not in netgame (or not a host), disconnect
1146 if (not isListQuery) and (not isMasterReportsEnabled()) then
1147 begin
1148 if (mlist[f].isConnected()) and (mlist[f].updateSent) then
1149 begin
1150 e_LogWritefln('removing from master [%s]', [mlist[f].hostName], TMsgType.Notify);
1151 mlist[f].remove();
1152 end;
1153 e_LogWritefln('disconnecting from master [%s]', [mlist[f].hostName], TMsgType.Notify);
1154 mlist[f].disconnect(false);
1155 end;
1156 end;
1157 mlist[f].pulse();
1158 end;
1160 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
1161 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
1162 // thank you, enet. let's ignore failures altogether then.
1163 count := 10; // no more than ten events in a row
1164 sres := enet_host_service(NetMHost, @NetMEvent, timeout);
1165 while (sres > 0) do
1166 begin
1168 if (sres < 0) then
1169 begin
1170 e_LogWriteln(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT] + ' (host_service)', TMsgType.Notify);
1171 for f := 0 to High(mlist) do mlist[f].finish();
1172 SetLength(mlist, 0);
1173 enet_host_destroy(NetMHost);
1174 NetMHost := nil;
1175 exit;
1176 end;
1179 idx := findByPeer(NetMEvent.peer);
1180 if (idx < 0) then
1181 begin
1182 e_LogWriteln('network event from unknown master host. ignored.', TMsgType.Warning);
1183 if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then enet_packet_destroy(NetMEvent.packet);
1185 else
1186 begin
1187 if (NetMEvent.kind = ENET_EVENT_TYPE_CONNECT) then
1188 begin
1189 mlist[idx].connectedEvent();
1191 else if (NetMEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
1192 begin
1193 mlist[idx].disconnectedEvent();
1195 else if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
1196 begin
1197 mlist[idx].receivedEvent(NetMEvent.packet);
1198 enet_packet_destroy(NetMEvent.packet);
1199 end;
1200 end;
1202 Dec(count);
1203 if (count = 0) then break;
1204 sres := enet_host_service(NetMHost, @NetMEvent, 0);
1205 end;
1206 end;
1208 //**************************************************************************
1210 // gui and server list
1212 //**************************************************************************
1214 //==========================================================================
1216 // PingServer
1218 //==========================================================================
1219 procedure PingServer (var S: TNetServer; Sock: ENetSocket);
1221 Buf: ENetBuffer;
1222 Ping: array [0..9] of Byte;
1223 ClTime: Int64;
1224 begin
1225 ClTime := GetTimerMS();
1227 Buf.data := Addr(Ping[0]);
1228 Buf.dataLength := 2+8;
1230 Ping[0] := Ord('D');
1231 Ping[1] := Ord('F');
1232 Int64(Addr(Ping[2])^) := ClTime;
1234 enet_socket_send(Sock, Addr(S.PingAddr), @Buf, 1);
1235 end;
1237 //==========================================================================
1239 // PingBcast
1241 //==========================================================================
1242 procedure PingBcast (Sock: ENetSocket);
1244 S: TNetServer;
1245 begin
1246 S.IP := '255.255.255.255';
1247 S.Port := NET_PING_PORT;
1248 enet_address_set_host(Addr(S.PingAddr), PChar(Addr(S.IP[1])));
1249 S.Ping := -1;
1250 S.PingAddr.port := S.Port;
1251 PingServer(S, Sock);
1252 end;
1254 //==========================================================================
1256 // g_Net_Slist_Fetch
1258 //==========================================================================
1259 function g_Net_Slist_Fetch (var SL: TNetServerList): Boolean;
1261 Cnt: Byte;
1262 pkt: pENetPacket;
1263 I, RX: Integer;
1264 T: Int64;
1265 Sock: ENetSocket;
1266 Buf: ENetBuffer;
1267 InMsg: TMsg;
1268 SvAddr: ENetAddress;
1269 FromSL: Boolean;
1270 MyVer: AnsiString;
1272 procedure ProcessLocal ();
1273 begin
1274 I := Length(SL);
1275 SetLength(SL, I + 1);
1276 with SL[I] do
1277 begin
1278 IP := DecodeIPV4(SvAddr.host);
1279 Port := InMsg.ReadWord();
1280 Ping := InMsg.ReadInt64();
1281 Ping := GetTimerMS() - Ping;
1282 Name := InMsg.ReadString();
1283 Map := InMsg.ReadString();
1284 GameMode := InMsg.ReadByte();
1285 Players := InMsg.ReadByte();
1286 MaxPlayers := InMsg.ReadByte();
1287 Protocol := InMsg.ReadByte();
1288 Password := InMsg.ReadByte() = 1;
1289 LocalPl := InMsg.ReadByte();
1290 Bots := InMsg.ReadWord();
1291 end;
1292 end;
1294 procedure CheckLocalServers ();
1295 begin
1296 SetLength(SL, 0);
1298 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1299 if Sock = ENET_SOCKET_NULL then Exit;
1300 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
1301 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
1302 PingBcast(Sock);
1304 T := GetTimerMS();
1306 InMsg.Alloc(NET_BUFSIZE);
1307 Buf.data := InMsg.Data;
1308 Buf.dataLength := InMsg.MaxSize;
1309 while GetTimerMS() - T <= 500 do
1310 begin
1311 InMsg.Clear();
1313 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
1314 if RX <= 0 then continue;
1315 InMsg.CurSize := RX;
1317 InMsg.BeginReading();
1319 if InMsg.ReadChar() <> 'D' then continue;
1320 if InMsg.ReadChar() <> 'F' then continue;
1322 ProcessLocal();
1323 end;
1325 InMsg.Free();
1326 enet_socket_destroy(Sock);
1328 if Length(SL) = 0 then SL := nil;
1329 end;
1332 f, c, n, pos: Integer;
1333 aliveCount: Integer;
1334 hasUnanswered: Boolean;
1335 stt, ct: Int64;
1336 tmpsv: TNetServer;
1337 begin
1338 result := false;
1339 SL := nil;
1341 if (not g_Net_IsNetworkAvailable()) then
1342 begin
1343 SetLength(SL, 0);
1344 exit;
1345 end;
1347 g_Net_Slist_Pulse(); // this will create mhost
1349 DisconnectAll(true); // forced disconnect
1351 for f := 0 to High(mlist) do
1352 begin
1353 mlist[f].connectCount := 0;
1354 mlist[f].srvAnswered := 0;
1355 end;
1357 NetOut.Clear();
1358 NetOut.Write(Byte(NET_MMSG_GET));
1360 // TODO: what should we identify the build with?
1361 MyVer := GAME_VERSION;
1362 NetOut.Write(MyVer);
1365 e_WriteLog('Fetching serverlist...', TMsgType.Notify);
1366 g_Console_Add(_lc[I_NET_MSG]+_lc[I_NET_SLIST_FETCH]);
1368 // wait until all servers connected and answered
1369 stt := GetTimerMS();
1370 while true do
1371 begin
1372 aliveCount := 0;
1373 hasUnanswered := false;
1374 for f := 0 to High(mlist) do
1375 begin
1377 e_LogWritefln(' master #%d: [%s] valid=%d; alive=%d; connected=%d; connecting=%d',
1378 [f, mlist[f].hostName, Integer(mlist[f].isValid()), Integer(mlist[f].isAlive()),
1379 Integer(mlist[f].isConnected()), Integer(mlist[f].isConnecting())], TMsgType.Notify);
1381 if (not mlist[f].isValid()) then continue;
1382 if (not mlist[f].isAlive()) then
1383 begin
1384 if (mlist[f].connectCount = 0) then
1385 begin
1386 mlist[f].connect();
1387 if (mlist[f].isAlive()) then
1388 begin
1389 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_WCONN], [mlist[f].hostName]));
1390 hasUnanswered := true;
1391 stt := GetTimerMS();
1392 end;
1394 else if (mlist[f].srvAnswered > 1) then
1395 begin
1396 Inc(aliveCount);
1397 end;
1399 else if (mlist[f].isConnected()) then
1400 begin
1401 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_CONN], [mlist[f].hostName]));
1402 if (mlist[f].srvAnswered = 0) then
1403 begin
1404 pkt := enet_packet_create(NetOut.Data, NetOut.CurSize, Cardinal(ENET_PACKET_FLAG_RELIABLE));
1405 if assigned(pkt) then
1406 begin
1407 if (enet_peer_send(mlist[f].peer, NET_MCHAN_MAIN, pkt) = 0) then
1408 begin
1409 hasUnanswered := true;
1410 mlist[f].srvAnswered := 1;
1411 stt := GetTimerMS();
1412 end;
1413 end;
1415 else if (mlist[f].srvAnswered = 1) then
1416 begin
1417 hasUnanswered := true;
1419 else if (mlist[f].srvAnswered > 1) then
1420 begin
1421 Inc(aliveCount);
1422 mlist[f].disconnect(false); // not forced
1423 end;
1425 else if (mlist[f].isConnecting()) then
1426 begin
1427 hasUnanswered := true;
1428 end;
1429 end;
1430 if (not hasUnanswered) then break;
1431 // check for timeout
1432 ct := GetTimerMS();
1433 if (ct < stt) or (ct-stt > 4000) then break;
1434 g_Net_Slist_Pulse(300);
1435 end;
1437 if (aliveCount = 0) then
1438 begin
1439 DisconnectAll();
1440 CheckLocalServers();
1441 exit;
1442 end;
1444 Result := True;
1445 slMOTD := '';
1447 slUrgent := '';
1448 slReadUrgent := true;
1451 SetLength(SL, 0);
1452 for f := 0 to High(mlist) do
1453 begin
1454 if (mlist[f].srvAnswered < 2) then continue;
1455 for n := 0 to High(mlist[f].srvAnswer) do
1456 begin
1457 pos := -1;
1458 for c := 0 to High(SL) do
1459 begin
1460 if (SL[c].IP = mlist[f].srvAnswer[n].IP) and (SL[c].Port = mlist[f].srvAnswer[n].Port) then
1461 begin
1462 pos := c;
1463 break;
1464 end;
1465 end;
1466 if (pos < 0) then
1467 begin
1468 pos := length(SL);
1469 SetLength(SL, pos+1);
1470 SL[pos] := mlist[f].srvAnswer[n];
1471 SL[pos].Number := pos;
1472 end;
1473 end;
1474 if (not mlist[f].slReadUrgent) and (mlist[f].slUrgent <> '') then
1475 begin
1476 if (mlist[f].slUrgent <> slUrgent) then
1477 begin
1478 slUrgent := mlist[f].slUrgent;
1479 slReadUrgent := false;
1480 end;
1481 end;
1482 if (slMOTD <> '') and (mlist[f].slMOTD <> '') then
1483 begin
1484 slMOTD := mlist[f].slMOTD;
1485 end;
1486 end;
1488 DisconnectAll();
1490 if (length(SL) = 0) then
1491 begin
1492 CheckLocalServers();
1493 exit;
1494 end;
1496 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1497 if Sock = ENET_SOCKET_NULL then Exit;
1498 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
1500 for I := Low(SL) to High(SL) do PingServer(SL[I], Sock);
1502 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
1503 PingBcast(Sock);
1505 T := GetTimerMS();
1507 InMsg.Alloc(NET_BUFSIZE);
1508 Buf.data := InMsg.Data;
1509 Buf.dataLength := InMsg.MaxSize;
1510 Cnt := 0;
1511 while GetTimerMS() - T <= 500 do
1512 begin
1513 InMsg.Clear();
1515 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
1516 if RX <= 0 then continue;
1517 InMsg.CurSize := RX;
1519 InMsg.BeginReading();
1521 if InMsg.ReadChar() <> 'D' then continue;
1522 if InMsg.ReadChar() <> 'F' then continue;
1524 with tmpsv do
1525 begin
1526 Port := InMsg.ReadWord();
1527 Ping := InMsg.ReadInt64();
1528 Ping := GetTimerMS() - Ping;
1529 Name := InMsg.ReadString();
1530 Map := InMsg.ReadString();
1531 GameMode := InMsg.ReadByte();
1532 Players := InMsg.ReadByte();
1533 MaxPlayers := InMsg.ReadByte();
1534 Protocol := InMsg.ReadByte();
1535 Password := InMsg.ReadByte() = 1;
1536 LocalPl := InMsg.ReadByte();
1537 Bots := InMsg.ReadWord();
1538 PingAddr := SvAddr;
1539 end;
1541 FromSL := False;
1542 for I := Low(SL) to High(SL) do
1543 if (SL[I].PingAddr.host = SvAddr.host) and
1544 (SL[I].PingAddr.port = SvAddr.port) and
1545 (SL[I].Port = tmpsv.Port) and
1546 (SL[I].Name = tmpsv.Name) then
1547 begin
1548 tmpsv.IP := SL[I].IP;
1549 SL[I] := tmpsv;
1550 FromSL := True;
1551 Inc(Cnt);
1552 break;
1553 end;
1555 if not FromSL then
1556 begin
1557 I := Length(SL);
1558 SetLength(SL, I + 1);
1559 tmpsv.IP := DecodeIPV4(SvAddr.host);
1560 SL[I] := tmpsv;
1561 end;
1562 end;
1564 InMsg.Free();
1565 enet_socket_destroy(Sock);
1566 finally
1567 NetOut.Clear();
1568 end;
1569 end;
1571 //==========================================================================
1573 // GetServerFromTable
1575 //==========================================================================
1576 function GetServerFromTable (Index: Integer; SL: TNetServerList; ST: TNetServerTable): TNetServer;
1577 begin
1578 Result.Number := 0;
1579 Result.Protocol := 0;
1580 Result.Name := '';
1581 Result.IP := '';
1582 Result.Port := 0;
1583 Result.Map := '';
1584 Result.Players := 0;
1585 Result.MaxPlayers := 0;
1586 Result.LocalPl := 0;
1587 Result.Bots := 0;
1588 Result.Ping := 0;
1589 Result.GameMode := 0;
1590 Result.Password := false;
1591 FillChar(Result.PingAddr, SizeOf(ENetAddress), 0);
1592 if ST = nil then
1593 Exit;
1594 if (Index < 0) or (Index >= Length(ST)) then
1595 Exit;
1596 Result := SL[ST[Index].Indices[ST[Index].Current]];
1597 end;
1599 //==========================================================================
1601 // g_Serverlist_Draw
1603 //==========================================================================
1604 procedure g_Serverlist_Draw (var SL: TNetServerList; var ST: TNetServerTable);
1606 Srv: TNetServer;
1607 sy, i, y, mw, mx, l, motdh: Integer;
1608 cw: Byte = 0;
1609 ch: Byte = 0;
1610 ww: Word = 0;
1611 hh: Word = 0;
1612 ip: AnsiString;
1613 begin
1614 ip := '';
1615 sy := 0;
1617 e_CharFont_GetSize(gMenuFont, _lc[I_NET_SLIST], ww, hh);
1618 e_CharFont_Print(gMenuFont, (gScreenWidth div 2) - (ww div 2), 16, _lc[I_NET_SLIST]);
1620 e_TextureFontGetSize(gStdFont, cw, ch);
1622 ip := _lc[I_NET_SLIST_HELP];
1623 mw := (Length(ip) * cw) div 2;
1625 motdh := gScreenHeight - 49 - ch * b_Text_LineCount(slMOTD);
1627 e_DrawFillQuad(16, 64, gScreenWidth-16, motdh, 64, 64, 64, 110);
1628 e_DrawQuad(16, 64, gScreenWidth-16, motdh, 255, 127, 0);
1630 e_TextureFontPrintEx(gScreenWidth div 2 - mw, gScreenHeight-24, ip, gStdFont, 225, 225, 225, 1);
1632 // MOTD
1633 if slMOTD <> '' then
1634 begin
1635 e_DrawFillQuad(16, motdh, gScreenWidth-16, gScreenHeight-44, 64, 64, 64, 110);
1636 e_DrawQuad(16, motdh, gScreenWidth-16, gScreenHeight-44, 255, 127, 0);
1637 e_TextureFontPrintFmt(20, motdh + 3, slMOTD, gStdFont, False, True);
1638 end;
1640 // Urgent message
1641 if not slReadUrgent and (slUrgent <> '') then
1642 begin
1643 e_DrawFillQuad(17, 65, gScreenWidth-17, motdh-1, 64, 64, 64, 128);
1644 e_DrawFillQuad(gScreenWidth div 2 - 256, gScreenHeight div 2 - 60,
1645 gScreenWidth div 2 + 256, gScreenHeight div 2 + 60, 64, 64, 64, 128);
1646 e_DrawQuad(gScreenWidth div 2 - 256, gScreenHeight div 2 - 60,
1647 gScreenWidth div 2 + 256, gScreenHeight div 2 + 60, 255, 127, 0);
1648 e_DrawLine(1, gScreenWidth div 2 - 256, gScreenHeight div 2 - 40,
1649 gScreenWidth div 2 + 256, gScreenHeight div 2 - 40, 255, 127, 0);
1650 l := Length(_lc[I_NET_SLIST_URGENT]) div 2;
1651 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 - 58,
1652 _lc[I_NET_SLIST_URGENT], gStdFont);
1653 l := Length(slUrgent) div 2;
1654 e_TextureFontPrintFmt(gScreenWidth div 2 - 253, gScreenHeight div 2 - 38,
1655 slUrgent, gStdFont, False, True);
1656 l := Length(_lc[I_NET_SLIST_URGENT_CONT]) div 2;
1657 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 + 41,
1658 _lc[I_NET_SLIST_URGENT_CONT], gStdFont);
1659 e_DrawLine(1, gScreenWidth div 2 - 256, gScreenHeight div 2 + 40,
1660 gScreenWidth div 2 + 256, gScreenHeight div 2 + 40, 255, 127, 0);
1661 Exit;
1662 end;
1664 if SL = nil then
1665 begin
1666 l := Length(slWaitStr) div 2;
1667 e_DrawFillQuad(17, 65, gScreenWidth-17, motdh-1, 64, 64, 64, 128);
1668 e_DrawQuad(gScreenWidth div 2 - 192, gScreenHeight div 2 - 10,
1669 gScreenWidth div 2 + 192, gScreenHeight div 2 + 11, 255, 127, 0);
1670 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 - ch div 2,
1671 slWaitStr, gStdFont);
1672 Exit;
1673 end;
1675 y := 90;
1676 if (slSelection < Length(ST)) then
1677 begin
1678 I := slSelection;
1679 sy := y + 42 * I - 4;
1680 Srv := GetServerFromTable(I, SL, ST);
1681 ip := _lc[I_NET_ADDRESS] + ' ' + Srv.IP + ':' + IntToStr(Srv.Port);
1682 if Srv.Password then
1683 ip := ip + ' ' + _lc[I_NET_SERVER_PASSWORD] + ' ' + _lc[I_MENU_YES]
1684 else
1685 ip := ip + ' ' + _lc[I_NET_SERVER_PASSWORD] + ' ' + _lc[I_MENU_NO];
1686 end else
1687 if Length(ST) > 0 then
1688 slSelection := 0;
1690 mw := (gScreenWidth - 188);
1691 mx := 16 + mw;
1693 e_DrawFillQuad(16 + 1, sy, gScreenWidth - 16 - 1, sy + 40, 64, 64, 64, 0);
1694 e_DrawLine(1, 16 + 1, sy, gScreenWidth - 16 - 1, sy, 205, 205, 205);
1695 e_DrawLine(1, 16 + 1, sy + 41, gScreenWidth - 16 - 1, sy + 41, 255, 255, 255);
1697 e_DrawLine(1, 16, 85, gScreenWidth - 16, 85, 255, 127, 0);
1698 e_DrawLine(1, 16, motdh-20, gScreenWidth-16, motdh-20, 255, 127, 0);
1700 e_DrawLine(1, mx - 70, 64, mx - 70, motdh, 255, 127, 0);
1701 e_DrawLine(1, mx, 64, mx, motdh-20, 255, 127, 0);
1702 e_DrawLine(1, mx + 52, 64, mx + 52, motdh-20, 255, 127, 0);
1703 e_DrawLine(1, mx + 104, 64, mx + 104, motdh-20, 255, 127, 0);
1705 e_TextureFontPrintEx(18, 68, 'NAME/MAP', gStdFont, 255, 127, 0, 1);
1706 e_TextureFontPrintEx(mx - 68, 68, 'PING', gStdFont, 255, 127, 0, 1);
1707 e_TextureFontPrintEx(mx + 2, 68, 'MODE', gStdFont, 255, 127, 0, 1);
1708 e_TextureFontPrintEx(mx + 54, 68, 'PLRS', gStdFont, 255, 127, 0, 1);
1709 e_TextureFontPrintEx(mx + 106, 68, 'VER', gStdFont, 255, 127, 0, 1);
1711 y := 90;
1712 for I := 0 to High(ST) do
1713 begin
1714 Srv := GetServerFromTable(I, SL, ST);
1715 // Name and map
1716 e_TextureFontPrintEx(18, y, Srv.Name, gStdFont, 255, 255, 255, 1);
1717 e_TextureFontPrintEx(18, y + 16, Srv.Map, gStdFont, 210, 210, 210, 1);
1719 // Ping and similar count
1720 if (Srv.Ping < 0) or (Srv.Ping > 999) then
1721 e_TextureFontPrintEx(mx - 68, y, _lc[I_NET_SLIST_NO_ACCESS], gStdFont, 255, 0, 0, 1)
1722 else
1723 if Srv.Ping = 0 then
1724 e_TextureFontPrintEx(mx - 68, y, '<1' + _lc[I_NET_SLIST_PING_MS], gStdFont, 255, 255, 255, 1)
1725 else
1726 e_TextureFontPrintEx(mx - 68, y, IntToStr(Srv.Ping) + _lc[I_NET_SLIST_PING_MS], gStdFont, 255, 255, 255, 1);
1728 if Length(ST[I].Indices) > 1 then
1729 e_TextureFontPrintEx(mx - 68, y + 16, '< ' + IntToStr(Length(ST[I].Indices)) + ' >', gStdFont, 210, 210, 210, 1);
1731 // Game mode
1732 e_TextureFontPrintEx(mx + 2, y, g_Game_ModeToText(Srv.GameMode), gStdFont, 255, 255, 255, 1);
1734 // Players
1735 e_TextureFontPrintEx(mx + 54, y, IntToStr(Srv.Players) + '/' + IntToStr(Srv.MaxPlayers), gStdFont, 255, 255, 255, 1);
1736 e_TextureFontPrintEx(mx + 54, y + 16, IntToStr(Srv.LocalPl) + '+' + IntToStr(Srv.Bots), gStdFont, 210, 210, 210, 1);
1738 // Version
1739 e_TextureFontPrintEx(mx + 106, y, IntToStr(Srv.Protocol), gStdFont, 255, 255, 255, 1);
1741 y := y + 42;
1742 end;
1744 e_TextureFontPrintEx(20, motdh-20+3, ip, gStdFont, 205, 205, 205, 1);
1745 ip := IntToStr(Length(ST)) + _lc[I_NET_SLIST_SERVERS];
1746 e_TextureFontPrintEx(gScreenWidth - 48 - (Length(ip) + 1)*cw,
1747 motdh-20+3, ip, gStdFont, 205, 205, 205, 1);
1748 end;
1750 //==========================================================================
1752 // g_Serverlist_GenerateTable
1754 //==========================================================================
1755 procedure g_Serverlist_GenerateTable (SL: TNetServerList; var ST: TNetServerTable);
1757 i, j: Integer;
1759 function FindServerInTable(Name: AnsiString; Port: Word): Integer;
1761 i: Integer;
1762 begin
1763 Result := -1;
1764 if ST = nil then
1765 Exit;
1766 for i := Low(ST) to High(ST) do
1767 begin
1768 if Length(ST[i].Indices) = 0 then
1769 continue;
1770 if (SL[ST[i].Indices[0]].Name = Name) and (SL[ST[i].Indices[0]].Port = Port) then
1771 begin
1772 Result := i;
1773 Exit;
1774 end;
1775 end;
1776 end;
1777 function ComparePing(i1, i2: Integer): Boolean;
1779 p1, p2: Int64;
1780 begin
1781 p1 := SL[i1].Ping;
1782 p2 := SL[i2].Ping;
1783 if (p1 < 0) then p1 := 999;
1784 if (p2 < 0) then p2 := 999;
1785 Result := p1 > p2;
1786 end;
1787 procedure SortIndices(var ind: Array of Integer);
1789 I, J: Integer;
1790 T: Integer;
1791 begin
1792 for I := High(ind) downto Low(ind) do
1793 for J := Low(ind) to High(ind) - 1 do
1794 if ComparePing(ind[j], ind[j+1]) then
1795 begin
1796 T := ind[j];
1797 ind[j] := ind[j+1];
1798 ind[j+1] := T;
1799 end;
1800 end;
1801 procedure SortRows();
1803 I, J: Integer;
1804 T: TNetServerRow;
1805 begin
1806 for I := High(ST) downto Low(ST) do
1807 for J := Low(ST) to High(ST) - 1 do
1808 if ComparePing(ST[j].Indices[0], ST[j+1].Indices[0]) then
1809 begin
1810 T := ST[j];
1811 ST[j] := ST[j+1];
1812 ST[j+1] := T;
1813 end;
1814 end;
1815 begin
1816 ST := nil;
1817 if SL = nil then
1818 Exit;
1820 for i := Low(SL) to High(SL) do
1821 begin
1822 j := FindServerInTable(SL[i].Name, SL[i].Port);
1823 if j = -1 then
1824 begin
1825 j := Length(ST);
1826 SetLength(ST, j + 1);
1827 ST[j].Current := 0;
1828 SetLength(ST[j].Indices, 1);
1829 ST[j].Indices[0] := i;
1831 else
1832 begin
1833 SetLength(ST[j].Indices, Length(ST[j].Indices) + 1);
1834 ST[j].Indices[High(ST[j].Indices)] := i;
1835 end;
1836 end;
1838 for i := Low(ST) to High(ST) do
1839 SortIndices(ST[i].Indices);
1841 SortRows();
1842 end;
1844 //==========================================================================
1846 // g_Serverlist_Control
1848 //==========================================================================
1849 procedure g_Serverlist_Control (var SL: TNetServerList; var ST: TNetServerTable);
1851 qm: Boolean;
1852 Srv: TNetServer;
1853 begin
1854 g_Net_Slist_Pulse();
1856 if gConsoleShow or gChatShow then
1857 Exit;
1859 qm := sys_HandleEvents(); // this updates kbd
1861 if qm or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
1862 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or
1863 e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
1864 begin
1865 SL := nil;
1866 ST := nil;
1867 gState := STATE_MENU;
1868 g_GUI_ShowWindow('MainMenu');
1869 g_GUI_ShowWindow('NetGameMenu');
1870 g_GUI_ShowWindow('NetClientMenu');
1871 {$IFDEF ENABLE_SOUND}
1872 g_Sound_PlayEx(WINDOW_CLOSESOUND);
1873 {$ENDIF}
1874 Exit;
1875 end;
1877 // if there's a message on the screen,
1878 if not slReadUrgent and (slUrgent <> '') then
1879 begin
1880 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(IK_SELECT) or
1881 e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1882 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
1883 slReadUrgent := True;
1884 Exit;
1885 end;
1887 if e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_JUMP) or
1888 e_KeyPressed(JOY0_ACTIVATE) or e_KeyPressed(JOY1_ACTIVATE) or e_KeyPressed(JOY2_ACTIVATE) or e_KeyPressed(JOY3_ACTIVATE) then
1889 begin
1890 if not slFetched then
1891 begin
1892 slWaitStr := _lc[I_NET_SLIST_WAIT];
1894 g_Game_Draw;
1895 sys_Repaint;
1897 if g_Net_Slist_Fetch(SL) then
1898 begin
1899 if SL = nil then
1900 slWaitStr := _lc[I_NET_SLIST_NOSERVERS];
1902 else
1903 if SL = nil then
1904 slWaitStr := _lc[I_NET_SLIST_ERROR];
1905 slFetched := True;
1906 slSelection := 0;
1907 g_Serverlist_GenerateTable(SL, ST);
1908 end;
1910 else
1911 slFetched := False;
1913 if SL = nil then Exit;
1915 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(IK_SELECT) or
1916 e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1917 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
1918 begin
1919 if not slReturnPressed then
1920 begin
1921 Srv := GetServerFromTable(slSelection, SL, ST);
1922 if Srv.Password then
1923 begin
1924 PromptIP := Srv.IP;
1925 PromptPort := Srv.Port;
1926 gState := STATE_MENU;
1927 g_GUI_ShowWindow('ClientPasswordMenu');
1928 SL := nil;
1929 ST := nil;
1930 slReturnPressed := True;
1931 Exit;
1933 else
1934 g_Game_StartClient(Srv.IP, Srv.Port, '');
1935 SL := nil;
1936 ST := nil;
1937 slReturnPressed := True;
1938 Exit;
1939 end;
1941 else
1942 slReturnPressed := False;
1944 if e_KeyPressed(IK_DOWN) or e_KeyPressed(IK_KPDOWN) or e_KeyPressed(VK_DOWN) or
1945 e_KeyPressed(JOY0_DOWN) or e_KeyPressed(JOY1_DOWN) or e_KeyPressed(JOY2_DOWN) or e_KeyPressed(JOY3_DOWN) then
1946 begin
1947 if not slDirPressed then
1948 begin
1949 Inc(slSelection);
1950 if slSelection > High(ST) then slSelection := 0;
1951 slDirPressed := True;
1952 end;
1953 end;
1955 if e_KeyPressed(IK_UP) or e_KeyPressed(IK_KPUP) or e_KeyPressed(VK_UP) or
1956 e_KeyPressed(JOY0_UP) or e_KeyPressed(JOY1_UP) or e_KeyPressed(JOY2_UP) or e_KeyPressed(JOY3_UP) then
1957 begin
1958 if not slDirPressed then
1959 begin
1960 if slSelection = 0 then slSelection := Length(ST);
1961 Dec(slSelection);
1963 slDirPressed := True;
1964 end;
1965 end;
1967 if e_KeyPressed(IK_RIGHT) or e_KeyPressed(IK_KPRIGHT) or e_KeyPressed(VK_RIGHT) or
1968 e_KeyPressed(JOY0_RIGHT) or e_KeyPressed(JOY1_RIGHT) or e_KeyPressed(JOY2_RIGHT) or e_KeyPressed(JOY3_RIGHT) then
1969 begin
1970 if not slDirPressed then
1971 begin
1972 Inc(ST[slSelection].Current);
1973 if ST[slSelection].Current > High(ST[slSelection].Indices) then ST[slSelection].Current := 0;
1974 slDirPressed := True;
1975 end;
1976 end;
1978 if e_KeyPressed(IK_LEFT) or e_KeyPressed(IK_KPLEFT) or e_KeyPressed(VK_LEFT) or
1979 e_KeyPressed(JOY0_LEFT) or e_KeyPressed(JOY1_LEFT) or e_KeyPressed(JOY2_LEFT) or e_KeyPressed(JOY3_LEFT) then
1980 begin
1981 if not slDirPressed then
1982 begin
1983 if ST[slSelection].Current = 0 then ST[slSelection].Current := Length(ST[slSelection].Indices);
1984 Dec(ST[slSelection].Current);
1986 slDirPressed := True;
1987 end;
1988 end;
1990 if (not e_KeyPressed(IK_DOWN)) and
1991 (not e_KeyPressed(IK_UP)) and
1992 (not e_KeyPressed(IK_RIGHT)) and
1993 (not e_KeyPressed(IK_LEFT)) and
1994 (not e_KeyPressed(IK_KPDOWN)) and
1995 (not e_KeyPressed(IK_KPUP)) and
1996 (not e_KeyPressed(IK_KPRIGHT)) and
1997 (not e_KeyPressed(IK_KPLEFT)) and
1998 (not e_KeyPressed(VK_DOWN)) and
1999 (not e_KeyPressed(VK_UP)) and
2000 (not e_KeyPressed(VK_RIGHT)) and
2001 (not e_KeyPressed(VK_LEFT)) and
2002 (not e_KeyPressed(JOY0_UP)) and (not e_KeyPressed(JOY1_UP)) and (not e_KeyPressed(JOY2_UP)) and (not e_KeyPressed(JOY3_UP)) and
2003 (not e_KeyPressed(JOY0_DOWN)) and (not e_KeyPressed(JOY1_DOWN)) and (not e_KeyPressed(JOY2_DOWN)) and (not e_KeyPressed(JOY3_DOWN)) and
2004 (not e_KeyPressed(JOY0_LEFT)) and (not e_KeyPressed(JOY1_LEFT)) and (not e_KeyPressed(JOY2_LEFT)) and (not e_KeyPressed(JOY3_LEFT)) and
2005 (not e_KeyPressed(JOY0_RIGHT)) and (not e_KeyPressed(JOY1_RIGHT)) and (not e_KeyPressed(JOY2_RIGHT)) and (not e_KeyPressed(JOY3_RIGHT))
2006 then
2007 slDirPressed := False;
2008 end;
2010 end.