DEADSOFTWARE

net: mostly restored master-comm logic
[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 type
34 TNetServer = record
35 Number: Byte;
36 Protocol: Byte;
37 Name: AnsiString;
38 IP: AnsiString;
39 Port: Word;
40 Map: AnsiString;
41 Players, MaxPlayers, LocalPl, Bots: Byte;
42 Ping: Int64;
43 GameMode: Byte;
44 Password: Boolean;
45 PingAddr: ENetAddress;
46 end;
47 pTNetServer = ^TNetServer;
48 TNetServerRow = record
49 Indices: Array of Integer;
50 Current: Integer;
51 end;
53 TNetServerList = array of TNetServer;
54 pTNetServerList = ^TNetServerList;
55 TNetServerTable = array of TNetServerRow;
57 type
58 TMasterHost = record
59 public
60 hostName: AnsiString;
61 hostPort: Word;
63 public
64 peer: pENetPeer;
65 enetAddr: ENetAddress;
66 // inside the game, calling `connect()` is disasterous, as it is blocking.
67 // so we'll use this variable to indicate if "connected" event is received.
68 NetHostConnected: Boolean;
69 NetHostConReqTime: Int64; // to timeout `connect`; -1 means "waiting for shutdown"
70 NetUpdatePending: Boolean; // should we send an update after connection completes?
71 lastConnectTime: Int64;
72 updateSent: Boolean;
73 lastUpdateTime: Int64;
74 // server list request working flags
75 srvAnswered: Integer;
76 srvAnswer: array of TNetServer;
77 slMOTD: AnsiString;
78 slUrgent: AnsiString;
79 slReadUrgent: Boolean;
81 private
82 netmsg: TMsg;
84 public
85 constructor Create (hostandport: AnsiString);
87 procedure clear ();
89 function setAddress (hostandport: AnsiString): Boolean;
91 function isSameAddress (hostandport: AnsiString): Boolean;
93 function isValid (): Boolean;
94 function isAlive (): Boolean; // not disconnected
95 function isConnecting (): Boolean; // is connection in progress?
96 function isConnected (): Boolean;
98 // call as often as you want, the object will do the rest
99 // but try to call this at least once in 100 msecs
100 procedure pulse ();
102 procedure disconnect (forced: Boolean);
103 function connect (): Boolean;
105 procedure update ();
106 procedure remove ();
108 class procedure writeInfo (var msg: TMsg); static;
110 procedure connectedEvent ();
111 procedure disconnectedEvent ();
112 procedure receivedEvent (pkt: pENetPacket); // `pkt` is never `nil`
113 end;
116 var
117 slCurrent: TNetServerList = nil;
118 slTable: TNetServerTable = nil;
119 slWaitStr: AnsiString = '';
120 slReturnPressed: Boolean = True;
122 slMOTD: AnsiString = '';
123 slUrgent: AnsiString = '';
126 procedure g_Net_Slist_Set (IP: AnsiString; Port: Word);
127 function g_Net_Slist_Fetch (var SL: TNetServerList): Boolean;
129 // make this server private
130 procedure g_Net_Slist_Private ();
131 // make this server public
132 procedure g_Net_Slist_Public ();
134 // called while the server is running
135 procedure g_Net_Slist_ServerUpdate ();
136 // called when the server is started
137 procedure g_Net_Slist_ServerStarted ();
138 // called when the server is stopped
139 procedure g_Net_Slist_ServerClosed ();
141 // called when new netword player comes
142 procedure g_Net_Slist_ServerPlayerComes ();
143 // called when new netword player comes
144 procedure g_Net_Slist_ServerPlayerLeaves ();
145 // started new map
146 procedure g_Net_Slist_ServerMapStarted ();
147 // this server renamed (or password mode changed, or other params changed)
148 procedure g_Net_Slist_ServerRenamed ();
150 procedure g_Net_Slist_Pulse (timeout: Integer=0);
152 procedure g_Net_Slist_ShutdownAll ();
154 procedure g_Serverlist_GenerateTable (SL: TNetServerList; var ST: TNetServerTable);
155 procedure g_Serverlist_Draw (var SL: TNetServerList; var ST: TNetServerTable);
156 procedure g_Serverlist_Control (var SL: TNetServerList; var ST: TNetServerTable);
158 function GetTimerMS (): Int64;
161 implementation
163 uses
164 e_input, e_graphics, e_log, g_window, g_net, g_console,
165 g_map, g_game, g_sound, g_gui, g_menu, g_options, g_language, g_basic,
166 wadreader, g_system, utils;
168 // ////////////////////////////////////////////////////////////////////////// //
169 var
170 NetMHost: pENetHost = nil;
171 NetMEvent: ENetEvent;
172 mlist: array of TMasterHost = nil;
174 slSelection: Byte = 0;
175 slFetched: Boolean = False;
176 slDirPressed: Boolean = False;
177 slReadUrgent: Boolean = False;
179 reportsEnabled: Boolean = true;
182 //==========================================================================
183 //
184 // GetTimerMS
185 //
186 //==========================================================================
187 function GetTimerMS (): Int64;
188 begin
189 Result := sys_GetTicks() {div 1000};
190 end;
193 //==========================================================================
194 //
195 // findByPeer
196 //
197 //==========================================================================
198 function findByPeer (peer: pENetPeer): Integer;
199 var
200 f: Integer;
201 begin
202 for f := 0 to High(mlist) do if (mlist[f].peer = peer) then begin result := f; exit; end;
203 result := -1;
204 end;
207 //==========================================================================
208 //
209 // ShutdownAll
210 //
211 //==========================================================================
212 procedure g_Net_Slist_ShutdownAll ();
213 var
214 f, sres, idx: Integer;
215 stt, ct: Int64;
216 activeCount: Integer = 0;
217 begin
218 if (NetMHost = nil) then exit;
219 for f := 0 to High(mlist) do
220 begin
221 if (mlist[f].isAlive()) then
222 begin
223 Inc(activeCount);
224 if (mlist[f].isConnected() and mlist[f].updateSent) then
225 begin
226 writeln('unregistering from ', f);
227 mlist[f].remove();
228 end;
229 //mlist[f].disconnect(false);
230 enet_peer_disconnect_later(mlist[f].peer, 0);
231 end;
232 end;
233 if (activeCount = 0) then exit;
234 stt := GetTimerMS();
235 while (activeCount > 0) do
236 begin
237 ct := GetTimerMS();
238 if (ct < stt) or (ct-stt >= 1500) then break;
240 sres := enet_host_service(NetMHost, @NetMEvent, 100);
241 if (sres < 0) then break;
242 if (sres = 0) then continue;
244 idx := findByPeer(NetMEvent.peer);
245 if (idx < 0) then
246 begin
247 if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then enet_packet_destroy(NetMEvent.packet);
248 continue;
249 end;
251 if (NetMEvent.kind = ENET_EVENT_TYPE_CONNECT) then
252 begin
253 mlist[idx].connectedEvent();
254 //mlist[idx].disconnect(false);
255 enet_peer_disconnect(mlist[f].peer, 0);
256 end
257 else if (NetMEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
258 begin
259 mlist[idx].disconnectedEvent();
260 Dec(activeCount);
261 end
262 else if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
263 begin
264 mlist[idx].receivedEvent(NetMEvent.packet);
265 enet_packet_destroy(NetMEvent.packet);
266 end;
267 end;
268 enet_host_destroy(NetMHost);
269 NetMHost := nil;
270 end;
273 //==========================================================================
274 //
275 // DisconnectAll
276 //
277 //==========================================================================
278 procedure DisconnectAll ();
279 var
280 f: Integer;
281 begin
282 for f := 0 to High(mlist) do
283 begin
284 if (mlist[f].isAlive()) then mlist[f].disconnect(false);
285 end;
286 end;
289 //==========================================================================
290 //
291 // ConnectAll
292 //
293 //==========================================================================
294 procedure ConnectAll (sendUpdate: Boolean);
295 var
296 f: Integer;
297 begin
298 for f := 0 to High(mlist) do
299 begin
300 // force reconnect
301 mlist[f].lastConnectTime := 0;
302 //if (not mlist[f].isAlive()) then continue;
303 // force updating
304 if (sendUpdate) then
305 begin
306 mlist[f].NetUpdatePending := true;
307 mlist[f].lastUpdateTime := 0;
308 end;
309 end;
310 end;
313 //==========================================================================
314 //
315 // UpdateAll
316 //
317 //==========================================================================
318 procedure UpdateAll (force: Boolean);
319 var
320 f: Integer;
321 begin
322 for f := 0 to High(mlist) do
323 begin
324 if (not mlist[f].isAlive()) then continue;
325 mlist[f].NetUpdatePending := true;
326 if (force) then mlist[f].lastUpdateTime := 0;
327 end;
328 end;
331 //**************************************************************************
332 //
333 // public api
334 //
335 //**************************************************************************
337 //==========================================================================
338 //
339 // g_Net_Slist_Private
340 //
341 // make this server private
342 //
343 //==========================================================================
344 procedure g_Net_Slist_Private ();
345 begin
346 DisconnectAll();
347 reportsEnabled := false;
348 end;
351 //==========================================================================
352 //
353 // g_Net_Slist_Public
354 //
355 // make this server public
356 //
357 //==========================================================================
358 procedure g_Net_Slist_Public ();
359 begin
360 if (not reportsEnabled) then
361 begin
362 reportsEnabled := true;
363 ConnectAll(true);
364 end;
365 end;
368 //==========================================================================
369 //
370 // g_Net_Slist_ServerUpdate
371 //
372 // called while the server is running
373 //
374 //==========================================================================
375 procedure g_Net_Slist_ServerUpdate ();
376 begin
377 UpdateAll(false);
378 end;
381 // called when the server is started
382 procedure g_Net_Slist_ServerStarted ();
383 begin
384 reportsEnabled := NetUseMaster;
385 if reportsEnabled and g_Game_IsServer() and g_Game_IsNet() then
386 begin
387 writeln('*** server started; reporting to master...');
388 ConnectAll(true);
389 end;
390 end;
393 //==========================================================================
394 //
395 // g_Net_Slist_ServerClosed
396 //
397 // called when the server is stopped
398 //
399 //==========================================================================
400 procedure g_Net_Slist_ServerClosed ();
401 var
402 f: Integer;
403 begin
404 if reportsEnabled then
405 begin
406 reportsEnabled := false;
407 for f := 0 to High(mlist) do
408 begin
409 if (mlist[f].isConnected()) then mlist[f].remove();
410 end;
411 end;
412 DisconnectAll();
413 end;
416 //==========================================================================
417 //
418 // g_Net_Slist_ServerPlayerComes
419 //
420 // called when new netword player comes
421 //
422 //==========================================================================
423 procedure g_Net_Slist_ServerPlayerComes ();
424 begin
425 UpdateAll(true);
426 end;
429 //==========================================================================
430 //
431 // g_Net_Slist_ServerPlayerLeaves
432 //
433 // called when new netword player comes
434 //
435 //==========================================================================
436 procedure g_Net_Slist_ServerPlayerLeaves ();
437 begin
438 UpdateAll(true);
439 end;
442 //==========================================================================
443 //
444 // g_Net_Slist_ServerMapStarted
445 //
446 // started new map
447 //
448 //==========================================================================
449 procedure g_Net_Slist_ServerMapStarted ();
450 begin
451 UpdateAll(true);
452 end;
455 //==========================================================================
456 //
457 // g_Net_Slist_ServerRenamed
458 //
459 // this server renamed (or password mode changed, or other params changed)
460 //
461 //==========================================================================
462 procedure g_Net_Slist_ServerRenamed ();
463 begin
464 UpdateAll(true);
465 end;
468 //**************************************************************************
469 //
470 // TMasterHost
471 //
472 //**************************************************************************
474 //==========================================================================
475 //
476 // TMasterHost.Create
477 //
478 //==========================================================================
479 constructor TMasterHost.Create (hostandport: AnsiString);
480 begin
481 peer := nil;
482 NetHostConnected := false;
483 NetHostConReqTime := 0;
484 NetUpdatePending := false;
485 lastConnectTime := 0;
486 updateSent := false;
487 lastUpdateTime := 0;
488 hostName := '';
489 hostPort := 25665;
490 SetLength(srvAnswer, 0);
491 srvAnswered := 0;
492 slMOTD := '';
493 slUrgent := '';
494 slReadUrgent := true;
495 netmsg.Alloc(NET_BUFSIZE);
496 setAddress(hostandport);
497 end;
500 //==========================================================================
501 //
502 // TMasterHost.clear
503 //
504 //==========================================================================
505 procedure TMasterHost.clear ();
506 begin
507 updateSent := false; // do not send 'remove'
508 disconnect(true);
509 hostName := '';
510 hostPort := 25665;
511 netmsg.Free();
512 SetLength(srvAnswer, 0);
513 srvAnswered := 0;
514 slMOTD := '';
515 slUrgent := '';
516 slReadUrgent := true;
517 end;
520 //==========================================================================
521 //
522 // TMasterHost.isSameAddress
523 //
524 //==========================================================================
525 function TMasterHost.isSameAddress (hostandport: AnsiString): Boolean;
526 var
527 cp, pp: Integer;
528 hn: AnsiString;
529 begin
530 result := false;
531 if not isValid() then exit;
532 hostandport := Trim(hostandport);
533 if (length(hostandport) = 0) then exit;
534 hn := hostandport;
535 cp := Pos(':', hostandport);
536 if (cp > 0) then
537 begin
538 hn := Copy(hostandport, 1, cp-1);
539 Delete(hostandport, 1, cp);
540 if (length(hostandport) > 0) then
541 begin
542 try
543 pp := StrToInt(hostandport);
544 except
545 pp := -1;
546 end;
547 end;
548 end
549 else
550 begin
551 pp := 25665;
552 end;
553 result := strEquCI1251(hn, hostName) and (hostPort = pp);
554 end;
557 //==========================================================================
558 //
559 // TMasterHost.setAddress
560 //
561 //==========================================================================
562 function TMasterHost.setAddress (hostandport: AnsiString): Boolean;
563 var
564 cp, pp: Integer;
565 begin
566 result := false;
567 SetLength(srvAnswer, 0);
568 srvAnswered := 0;
569 slMOTD := '';
570 slUrgent := '';
571 slReadUrgent := true;
572 updateSent := false; // do not send 'remove'
573 disconnect(true);
574 hostName := '';
575 hostPort := 25665;
577 if (not g_Net_IsNetworkAvailable()) then exit;
579 hostandport := Trim(hostandport);
580 if (length(hostandport) > 0) then
581 begin
582 hostName := hostandport;
583 cp := Pos(':', hostandport);
584 if (cp > 0) then
585 begin
586 hostName := Copy(hostandport, 1, cp-1);
587 Delete(hostandport, 1, cp);
588 if (length(hostandport) > 0) then
589 begin
590 try
591 pp := StrToInt(hostandport);
592 except
593 pp := -1;
594 end;
595 if (pp > 0) and (pp < 65536) then hostPort := pp else hostPort := 0;
596 end;
597 end;
598 end;
600 if not isValid() then exit;
602 if (enet_address_set_host(@enetAddr, PChar(Addr(hostName[1]))) <> 0) then
603 begin
604 writeln('SHIT!');
605 hostName := '';
606 hostPort := 0;
607 end;
608 enetAddr.Port := hostPort;
610 result := isValid();
611 //writeln('*********************: ', hostandport, ' [', hostName, ':', hostPort, '] ', result);
612 end;
615 //==========================================================================
616 //
617 // TMasterHost.isValid
618 //
619 //==========================================================================
620 function TMasterHost.isValid (): Boolean;
621 begin
622 result := (length(hostName) > 0) and (hostPort > 0);
623 end;
626 //==========================================================================
627 //
628 // TMasterHost.isAlive
629 //
630 // not disconnected
631 //
632 //==========================================================================
633 function TMasterHost.isAlive (): Boolean;
634 begin
635 result := (NetMHost <> nil) and (peer <> nil);
636 end;
639 //==========================================================================
640 //
641 // TMasterHost.isConnecting
642 //
643 // is connection in progress?
644 //
645 //==========================================================================
646 function TMasterHost.isConnecting (): Boolean;
647 begin
648 result := isAlive() and (not NetHostConnected) and (NetHostConReqTime <> -1);
649 end;
652 //==========================================================================
653 //
654 // TMasterHost.isConnected
655 //
656 //==========================================================================
657 function TMasterHost.isConnected (): Boolean;
658 begin
659 result := isAlive() and (NetHostConnected) and (NetHostConReqTime <> -1);
660 end;
663 //==========================================================================
664 //
665 // TMasterHost.connectedEvent
666 //
667 //==========================================================================
668 procedure TMasterHost.connectedEvent ();
669 begin
670 if not isAlive() then exit;
671 if NetHostConnected then exit;
672 NetHostConnected := true;
673 e_LogWritefln('connected to master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
674 end;
677 //==========================================================================
678 //
679 // TMasterHost.disconnectedEvent
680 //
681 //==========================================================================
682 procedure TMasterHost.disconnectedEvent ();
683 begin
684 if not isAlive() then exit;
685 e_LogWritefln('disconnected from master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
686 disconnect(true);
687 //if (spamConsole) then g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_DISC]);
688 end;
691 //==========================================================================
692 //
693 // TMasterHost.receivedEvent
694 //
695 // `pkt` is never `nil`
696 //
697 //==========================================================================
698 procedure TMasterHost.receivedEvent (pkt: pENetPacket);
699 var
700 msg: TMsg;
701 MID: Byte;
702 Cnt: Byte;
703 f: Integer;
704 s: AnsiString;
705 begin
706 e_LogWritefln('received packed from master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
707 if not msg.Init(pkt^.data, pkt^.dataLength, True) then exit;
708 // packet type
709 MID := msg.ReadByte();
710 if (MID <> NET_MMSG_GET) then exit;
711 e_LogWritefln('received list packet from master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
712 SetLength(srvAnswer, 0);
713 if (srvAnswered > 0) then Inc(srvAnswered);
714 slMOTD := '';
715 //slUrgent := '';
716 slReadUrgent := true;
717 // number of items
718 Cnt := msg.ReadByte();
719 g_Console_Add(_lc[I_NET_MSG]+Format(_lc[I_NET_SLIST_RETRIEVED], [Cnt]), True);
720 if (Cnt > 0) then
721 begin
722 SetLength(srvAnswer, Cnt);
723 for f := 0 to Cnt-1 do
724 begin
725 srvAnswer[f].Number := f;
726 srvAnswer[f].IP := msg.ReadString();
727 srvAnswer[f].Port := msg.ReadWord();
728 srvAnswer[f].Name := msg.ReadString();
729 srvAnswer[f].Map := msg.ReadString();
730 srvAnswer[f].GameMode := msg.ReadByte();
731 srvAnswer[f].Players := msg.ReadByte();
732 srvAnswer[f].MaxPlayers := msg.ReadByte();
733 srvAnswer[f].Protocol := msg.ReadByte();
734 srvAnswer[f].Password := msg.ReadByte() = 1;
735 enet_address_set_host(Addr(srvAnswer[f].PingAddr), PChar(Addr(srvAnswer[f].IP[1])));
736 srvAnswer[f].Ping := -1;
737 srvAnswer[f].PingAddr.port := NET_PING_PORT;
738 end;
739 end;
741 if (msg.ReadCount < msg.CurSize) then
742 begin
743 // new master, supports version reports
744 s := msg.ReadString();
745 if (s <> {MyVer}GAME_VERSION) then
746 begin
747 { TODO }
748 g_Console_Add('!!! UpdVer = `'+s+'`');
749 end;
750 // even newer master, supports extra info
751 if (msg.ReadCount < msg.CurSize) then
752 begin
753 slMOTD := b_Text_Format(msg.ReadString());
754 s := b_Text_Format(msg.ReadString());
755 // check if the message has updated and the user has to read it again
756 if (slUrgent <> s) then slReadUrgent := false;
757 slUrgent := s;
758 end;
759 end;
760 end;
763 //==========================================================================
764 //
765 // TMasterHost.pulse
766 //
767 // this performs various scheduled tasks, if necessary
768 //
769 //==========================================================================
770 procedure TMasterHost.pulse ();
771 var
772 ct: Int64;
773 mrate: Cardinal;
774 begin
775 if not isAlive() then exit;
776 if (NetHostConReqTime = -1) then exit; // waiting for shutdown (disconnect in progress)
777 ct := GetTimerMS();
778 // process pending connection timeout
779 if (not NetHostConnected) then
780 begin
781 if (ct < NetHostConReqTime) or (ct-NetHostConReqTime >= 3000) then
782 begin
783 e_LogWritefln('failed to connect to master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
784 // do not spam with error messages, it looks like the master is down
785 //g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR], True);
786 enet_peer_disconnect(peer, 0);
787 // main pulse will take care of the rest
788 end;
789 exit;
790 end;
791 // send update, if necessary
792 if (NetUpdatePending) then
793 begin
794 mrate := NetMasterRate;
795 if (mrate < 10000) then mrate := 10000
796 else if (mrate > 1000*60*10) then mrate := 1000*60*10;
797 if (lastUpdateTime = 0) or (ct < lastUpdateTime) or (ct-lastUpdateTime >= mrate) then
798 begin
799 lastUpdateTime := ct;
800 update();
801 end;
802 end;
803 end;
806 //==========================================================================
807 //
808 // TMasterHost.disconnect
809 //
810 //==========================================================================
811 procedure TMasterHost.disconnect (forced: Boolean);
812 begin
813 if not isAlive() then exit;
815 if (forced) then
816 begin
817 enet_peer_reset(peer);
818 peer := nil;
819 NetHostConReqTime := 0;
820 end
821 else
822 begin
823 enet_peer_disconnect_later(peer, 0);
824 // main pulse will take care of the rest
825 NetHostConReqTime := -1;
826 end;
828 NetHostConnected := false;
829 NetUpdatePending := false;
830 //updateSent := false;
831 lastUpdateTime := 0;
832 //lastConnectTime := 0;
833 //if (spamConsole) then g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_DISC]);
834 end;
837 //==========================================================================
838 //
839 // TMasterHost.connect
840 //
841 //==========================================================================
842 function TMasterHost.connect (): Boolean;
843 begin
844 result := false;
845 if not isValid() then exit;
846 if (NetHostConReqTime = -1) then
847 begin
848 disconnect(true);
849 end
850 else
851 begin
852 if isAlive() then begin result := true; exit; end;
853 end;
855 lastConnectTime := GetTimerMS();
856 SetLength(srvAnswer, 0);
857 srvAnswered := 0;
858 NetHostConnected := false;
859 NetHostConReqTime := 0;
860 NetUpdatePending := false;
861 updateSent := false;
862 lastUpdateTime := 0;
864 peer := enet_host_connect(NetMHost, @enetAddr, NET_MCHANS, 0);
865 if (peer = nil) then
866 begin
867 g_Console_Add(_lc[I_NET_MSG_ERROR]+_lc[I_NET_ERR_CLIENT], true);
868 exit;
869 end;
871 NetHostConReqTime := lastConnectTime;
872 e_LogWritefln('connecting to master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
873 end;
876 //==========================================================================
877 //
878 // TMasterHost.writeInfo
879 //
880 //==========================================================================
881 class procedure TMasterHost.writeInfo (var msg: TMsg);
882 var
883 wad, map: AnsiString;
884 begin
885 wad := g_ExtractWadNameNoPath(gMapInfo.Map);
886 map := g_ExtractFileName(gMapInfo.Map);
888 msg.Write(NetServerName);
890 msg.Write(wad+':/'+map);
891 msg.Write(gGameSettings.GameMode);
893 msg.Write(Byte(NetClientCount));
895 msg.Write(NetMaxClients);
897 msg.Write(Byte(NET_PROTOCOL_VER));
898 msg.Write(Byte(NetPassword <> ''));
899 end;
902 //==========================================================================
903 //
904 // TMasterHost.update
905 //
906 //==========================================================================
907 procedure TMasterHost.update ();
908 var
909 pkt: pENetPacket;
910 begin
911 if not isAlive() then exit;
912 if not isConnected() then
913 begin
914 NetUpdatePending := isConnecting();
915 exit;
916 end;
918 netmsg.Clear();
920 if reportsEnabled and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster then
921 begin
922 try
923 netmsg.Write(Byte(NET_MMSG_UPD));
924 netmsg.Write(NetAddr.port);
926 writeInfo(netmsg);
928 pkt := enet_packet_create(netmsg.Data, netmsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
929 if assigned(pkt) then
930 begin
931 if (enet_peer_send(peer, NET_MCHAN_UPD, pkt) = 0) then
932 begin
933 NetUpdatePending := false;
934 updateSent := true;
935 end;
936 end;
937 finally
938 netmsg.Clear();
939 end;
940 end
941 else
942 begin
943 NetUpdatePending := false;
944 end;
945 end;
948 //==========================================================================
949 //
950 // TMasterHost.remove
951 //
952 //==========================================================================
953 procedure TMasterHost.remove ();
954 var
955 pkt: pENetPacket;
956 begin
957 NetUpdatePending := false;
958 lastUpdateTime := 0;
959 updateSent := false;
960 if not isAlive() then exit;
961 if not isConnected() then exit;
963 netmsg.Clear();
964 try
965 netmsg.Write(Byte(NET_MMSG_DEL));
966 netmsg.Write(NetAddr.port);
968 pkt := enet_packet_create(netmsg.Data, netmsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
969 if assigned(pkt) then
970 begin
971 enet_peer_send(peer, NET_MCHAN_MAIN, pkt);
972 end;
973 finally
974 netmsg.Clear();
975 end;
976 end;
979 //**************************************************************************
980 //
981 // other functions
982 //
983 //**************************************************************************
985 procedure g_Net_Slist_Set (IP: AnsiString; Port: Word);
986 var
987 f: Integer;
988 sa: AnsiString;
989 begin
990 if (not g_Net_IsNetworkAvailable()) then exit;
991 IP := Trim(IP);
992 if (length(IP) = 0) or (Port = 0) then exit;
993 sa := IP+':'+IntToStr(Port);
994 for f := 0 to High(mlist) do if (mlist[f].isSameAddress(sa)) then exit;
995 SetLength(mlist, length(mlist)+1);
996 mlist[High(mlist)].Create(sa);
997 mlist[High(mlist)].setAddress(sa);
998 e_LogWritefln('Masterserver address set to [%s:%u]', [IP, Port], TMsgType.Notify);
999 end;
1002 //**************************************************************************
1003 //
1004 // main pulse
1005 //
1006 //**************************************************************************
1007 procedure g_Net_Slist_Pulse (timeout: Integer=0);
1008 var
1009 f: Integer;
1010 sres: Integer;
1011 idx: Integer;
1012 ct: Int64;
1013 begin
1014 if (not g_Net_IsNetworkAvailable()) then exit;
1016 if (length(mlist) = 0) then
1017 begin
1018 if (NetMHost <> nil) then
1019 begin
1020 enet_host_destroy(NetMHost);
1021 NetMHost := nil;
1022 exit;
1023 end;
1024 end;
1026 if (NetMHost = nil) then
1027 begin
1028 NetMHost := enet_host_create(nil, 1, NET_MCHANS, 0, 0);
1029 if (NetMHost = nil) then
1030 begin
1031 g_Console_Add(_lc[I_NET_MSG_ERROR]+_lc[I_NET_ERR_CLIENT], True);
1032 for f := 0 to High(mlist) do mlist[f].clear();
1033 SetLength(mlist, 0);
1034 Exit;
1035 end;
1036 end;
1038 ct := GetTimerMS();
1039 for f := 0 to High(mlist) do
1040 begin
1041 if (not mlist[f].isValid()) then continue;
1042 if (not mlist[f].isAlive()) then
1043 begin
1044 if reportsEnabled and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster then
1045 begin
1046 if (mlist[f].lastConnectTime = 0) or (ct < mlist[f].lastConnectTime) or (ct-mlist[f].lastConnectTime >= 1000*60*5) then
1047 begin
1048 mlist[f].connect();
1049 end;
1050 end;
1051 end
1052 else
1053 begin
1054 if not reportsEnabled or not g_Game_IsServer() or not g_Game_IsNet() or not NetUseMaster then
1055 begin
1056 if (mlist[f].isConnected()) and (mlist[f].updateSent) then mlist[f].remove();
1057 mlist[f].disconnect(false);
1058 end;
1059 end;
1060 mlist[f].pulse();
1061 end;
1063 while true do
1064 begin
1065 sres := enet_host_service(NetMHost, @NetMEvent, timeout);
1066 if (sres < 0) then
1067 begin
1068 g_Console_Add(_lc[I_NET_MSG_ERROR]+_lc[I_NET_ERR_CLIENT], True);
1069 for f := 0 to High(mlist) do mlist[f].clear();
1070 SetLength(mlist, 0);
1071 enet_host_destroy(NetMHost);
1072 NetMHost := nil;
1073 exit;
1074 end;
1076 if (sres = 0) then break;
1077 idx := findByPeer(NetMEvent.peer);
1078 if (idx < 0) then
1079 begin
1080 e_LogWriteln('network event from unknown master host. ignored.', TMsgType.Warning);
1081 if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then enet_packet_destroy(NetMEvent.packet);
1082 continue;
1083 end;
1085 if (NetMEvent.kind = ENET_EVENT_TYPE_CONNECT) then
1086 begin
1087 mlist[idx].connectedEvent();
1088 end
1089 else if (NetMEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
1090 begin
1091 mlist[idx].disconnectedEvent();
1092 end
1093 else if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
1094 begin
1095 mlist[idx].receivedEvent(NetMEvent.packet);
1096 enet_packet_destroy(NetMEvent.packet);
1097 end;
1098 end;
1099 end;
1102 //**************************************************************************
1103 //
1104 // gui and server list
1105 //
1106 //**************************************************************************
1108 //==========================================================================
1109 //
1110 // PingServer
1111 //
1112 //==========================================================================
1113 procedure PingServer (var S: TNetServer; Sock: ENetSocket);
1114 var
1115 Buf: ENetBuffer;
1116 Ping: array [0..9] of Byte;
1117 ClTime: Int64;
1118 begin
1119 ClTime := GetTimerMS();
1121 Buf.data := Addr(Ping[0]);
1122 Buf.dataLength := 2+8;
1124 Ping[0] := Ord('D');
1125 Ping[1] := Ord('F');
1126 Int64(Addr(Ping[2])^) := ClTime;
1128 enet_socket_send(Sock, Addr(S.PingAddr), @Buf, 1);
1129 end;
1132 //==========================================================================
1133 //
1134 // PingBcast
1135 //
1136 //==========================================================================
1137 procedure PingBcast (Sock: ENetSocket);
1138 var
1139 S: TNetServer;
1140 begin
1141 S.IP := '255.255.255.255';
1142 S.Port := NET_PING_PORT;
1143 enet_address_set_host(Addr(S.PingAddr), PChar(Addr(S.IP[1])));
1144 S.Ping := -1;
1145 S.PingAddr.port := S.Port;
1146 PingServer(S, Sock);
1147 end;
1150 //==========================================================================
1151 //
1152 // g_Net_Slist_Fetch
1153 //
1154 //==========================================================================
1155 function g_Net_Slist_Fetch (var SL: TNetServerList): Boolean;
1156 var
1157 Cnt: Byte;
1158 pkt: pENetPacket;
1159 I, RX: Integer;
1160 T: Int64;
1161 Sock: ENetSocket;
1162 Buf: ENetBuffer;
1163 InMsg: TMsg;
1164 SvAddr: ENetAddress;
1165 FromSL: Boolean;
1166 MyVer: AnsiString;
1168 procedure ProcessLocal ();
1169 begin
1170 I := Length(SL);
1171 SetLength(SL, I + 1);
1172 with SL[I] do
1173 begin
1174 IP := DecodeIPV4(SvAddr.host);
1175 Port := InMsg.ReadWord();
1176 Ping := InMsg.ReadInt64();
1177 Ping := GetTimerMS() - Ping;
1178 Name := InMsg.ReadString();
1179 Map := InMsg.ReadString();
1180 GameMode := InMsg.ReadByte();
1181 Players := InMsg.ReadByte();
1182 MaxPlayers := InMsg.ReadByte();
1183 Protocol := InMsg.ReadByte();
1184 Password := InMsg.ReadByte() = 1;
1185 LocalPl := InMsg.ReadByte();
1186 Bots := InMsg.ReadWord();
1187 end;
1188 end;
1190 procedure CheckLocalServers ();
1191 begin
1192 SetLength(SL, 0);
1194 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1195 if Sock = ENET_SOCKET_NULL then Exit;
1196 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
1197 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
1198 PingBcast(Sock);
1200 T := GetTimerMS();
1202 InMsg.Alloc(NET_BUFSIZE);
1203 Buf.data := InMsg.Data;
1204 Buf.dataLength := InMsg.MaxSize;
1205 while GetTimerMS() - T <= 500 do
1206 begin
1207 InMsg.Clear();
1209 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
1210 if RX <= 0 then continue;
1211 InMsg.CurSize := RX;
1213 InMsg.BeginReading();
1215 if InMsg.ReadChar() <> 'D' then continue;
1216 if InMsg.ReadChar() <> 'F' then continue;
1218 ProcessLocal();
1219 end;
1221 InMsg.Free();
1222 enet_socket_destroy(Sock);
1224 if Length(SL) = 0 then SL := nil;
1225 end;
1227 var
1228 f, c, n, pos: Integer;
1229 aliveCount: Integer;
1230 hasUnanswered: Boolean;
1231 stt, ct: Int64;
1232 begin
1233 result := false;
1234 SL := nil;
1236 if (not g_Net_IsNetworkAvailable()) then
1237 begin
1238 SetLength(SL, 0);
1239 exit;
1240 end;
1242 g_Net_Slist_Pulse(); // this will create mhost
1244 NetOut.Clear();
1245 NetOut.Write(Byte(NET_MMSG_GET));
1247 // TODO: what should we identify the build with?
1248 MyVer := GAME_VERSION;
1249 NetOut.Write(MyVer);
1251 try
1252 e_WriteLog('Fetching serverlist...', TMsgType.Notify);
1253 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_FETCH]);
1255 // wait until all servers connected and answered
1256 stt := GetTimerMS();
1257 while true do
1258 begin
1259 g_Net_Slist_Pulse(300);
1260 aliveCount := 0;
1261 hasUnanswered := false;
1262 for f := 0 to High(mlist) do
1263 begin
1265 e_LogWritefln(' master #%d: [%s:%u] valid=%d; alive=%d; connected=%d; connecting=%d',
1266 [f, mlist[f].hostName, mlist[f].hostPort, Integer(mlist[f].isValid()), Integer(mlist[f].isAlive()),
1267 Integer(mlist[f].isConnected()), Integer(mlist[f].isConnecting())], TMsgType.Notify);
1269 if (not mlist[f].isValid()) then continue;
1270 if (not mlist[f].isAlive()) then
1271 begin
1272 mlist[f].connect();
1273 if (mlist[f].isAlive()) then
1274 begin
1275 hasUnanswered := true;
1276 stt := GetTimerMS();
1277 end;
1278 end
1279 else if (mlist[f].isConnected()) then
1280 begin
1281 if (mlist[f].srvAnswered = 0) then
1282 begin
1283 pkt := enet_packet_create(NetOut.Data, NetOut.CurSize, Cardinal(ENET_PACKET_FLAG_RELIABLE));
1284 if assigned(pkt) then
1285 begin
1286 if (enet_peer_send(mlist[f].peer, NET_MCHAN_MAIN, pkt) = 0) then
1287 begin
1288 hasUnanswered := true;
1289 mlist[f].srvAnswered := 1;
1290 stt := GetTimerMS();
1291 end;
1292 end;
1293 end
1294 else if (mlist[f].srvAnswered = 1) then
1295 begin
1296 hasUnanswered := true;
1297 end
1298 else if (mlist[f].srvAnswered > 1) then
1299 begin
1300 Inc(aliveCount);
1301 end;
1302 end
1303 else if (mlist[f].isConnecting()) then
1304 begin
1305 hasUnanswered := true;
1306 end;
1307 end;
1308 if (not hasUnanswered) then break;
1309 // check for timeout
1310 ct := GetTimerMS();
1311 if (ct < stt) or (ct-stt > 4000) then break;
1312 end;
1314 if (aliveCount = 0) then
1315 begin
1316 DisconnectAll();
1317 CheckLocalServers();
1318 exit;
1319 end;
1321 slMOTD := '';
1323 slUrgent := '';
1324 slReadUrgent := true;
1327 SetLength(SL, 0);
1328 for f := 0 to High(mlist) do
1329 begin
1330 if (mlist[f].srvAnswered < 2) then continue;
1331 for n := 0 to High(mlist[f].srvAnswer) do
1332 begin
1333 pos := -1;
1334 for c := 0 to High(SL) do
1335 begin
1336 if (SL[c].IP = mlist[f].srvAnswer[n].IP) and (SL[c].Port = mlist[f].srvAnswer[n].Port) then
1337 begin
1338 pos := c;
1339 break;
1340 end;
1341 end;
1342 if (pos < 0) then
1343 begin
1344 pos := length(SL);
1345 SetLength(SL, pos+1);
1346 SL[pos] := mlist[f].srvAnswer[n];
1347 SL[pos].Number := pos;
1348 end;
1349 end;
1350 if (not mlist[f].slReadUrgent) and (mlist[f].slUrgent <> '') then
1351 begin
1352 if (mlist[f].slUrgent <> slUrgent) then
1353 begin
1354 slUrgent := mlist[f].slUrgent;
1355 slReadUrgent := false;
1356 end;
1357 end;
1358 if (slMOTD <> '') and (mlist[f].slMOTD <> '') then
1359 begin
1360 slMOTD := mlist[f].slMOTD;
1361 end;
1362 end;
1364 DisconnectAll();
1366 if (length(SL) = 0) then
1367 begin
1368 CheckLocalServers();
1369 exit;
1370 end;
1372 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1373 if Sock = ENET_SOCKET_NULL then Exit;
1374 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
1376 for I := Low(SL) to High(SL) do PingServer(SL[I], Sock);
1378 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
1379 PingBcast(Sock);
1381 T := GetTimerMS();
1383 InMsg.Alloc(NET_BUFSIZE);
1384 Buf.data := InMsg.Data;
1385 Buf.dataLength := InMsg.MaxSize;
1386 Cnt := 0;
1387 while GetTimerMS() - T <= 500 do
1388 begin
1389 InMsg.Clear();
1391 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
1392 if RX <= 0 then continue;
1393 InMsg.CurSize := RX;
1395 InMsg.BeginReading();
1397 if InMsg.ReadChar() <> 'D' then continue;
1398 if InMsg.ReadChar() <> 'F' then continue;
1400 FromSL := False;
1401 for I := Low(SL) to High(SL) do
1402 if (SL[I].PingAddr.host = SvAddr.host) and
1403 (SL[I].PingAddr.port = SvAddr.port) then
1404 begin
1405 with SL[I] do
1406 begin
1407 Port := InMsg.ReadWord();
1408 Ping := InMsg.ReadInt64();
1409 Ping := GetTimerMS() - Ping;
1410 Name := InMsg.ReadString();
1411 Map := InMsg.ReadString();
1412 GameMode := InMsg.ReadByte();
1413 Players := InMsg.ReadByte();
1414 MaxPlayers := InMsg.ReadByte();
1415 Protocol := InMsg.ReadByte();
1416 Password := InMsg.ReadByte() = 1;
1417 LocalPl := InMsg.ReadByte();
1418 Bots := InMsg.ReadWord();
1419 end;
1420 FromSL := True;
1421 Inc(Cnt);
1422 break;
1423 end;
1424 if not FromSL then
1425 ProcessLocal();
1426 end;
1428 InMsg.Free();
1429 enet_socket_destroy(Sock);
1430 finally
1431 NetOut.Clear();
1432 end;
1433 end;
1436 //==========================================================================
1437 //
1438 // GetServerFromTable
1439 //
1440 //==========================================================================
1441 function GetServerFromTable (Index: Integer; SL: TNetServerList; ST: TNetServerTable): TNetServer;
1442 begin
1443 Result.Number := 0;
1444 Result.Protocol := 0;
1445 Result.Name := '';
1446 Result.IP := '';
1447 Result.Port := 0;
1448 Result.Map := '';
1449 Result.Players := 0;
1450 Result.MaxPlayers := 0;
1451 Result.LocalPl := 0;
1452 Result.Bots := 0;
1453 Result.Ping := 0;
1454 Result.GameMode := 0;
1455 Result.Password := false;
1456 FillChar(Result.PingAddr, SizeOf(ENetAddress), 0);
1457 if ST = nil then
1458 Exit;
1459 if (Index < 0) or (Index >= Length(ST)) then
1460 Exit;
1461 Result := SL[ST[Index].Indices[ST[Index].Current]];
1462 end;
1465 //==========================================================================
1466 //
1467 // g_Serverlist_Draw
1468 //
1469 //==========================================================================
1470 procedure g_Serverlist_Draw (var SL: TNetServerList; var ST: TNetServerTable);
1471 var
1472 Srv: TNetServer;
1473 sy, i, y, mw, mx, l, motdh: Integer;
1474 cw: Byte = 0;
1475 ch: Byte = 0;
1476 ww: Word = 0;
1477 hh: Word = 0;
1478 ip: AnsiString;
1479 begin
1480 ip := '';
1481 sy := 0;
1483 e_CharFont_GetSize(gMenuFont, _lc[I_NET_SLIST], ww, hh);
1484 e_CharFont_Print(gMenuFont, (gScreenWidth div 2) - (ww div 2), 16, _lc[I_NET_SLIST]);
1486 e_TextureFontGetSize(gStdFont, cw, ch);
1488 ip := _lc[I_NET_SLIST_HELP];
1489 mw := (Length(ip) * cw) div 2;
1491 motdh := gScreenHeight - 49 - ch * b_Text_LineCount(slMOTD);
1493 e_DrawFillQuad(16, 64, gScreenWidth-16, motdh, 64, 64, 64, 110);
1494 e_DrawQuad(16, 64, gScreenWidth-16, motdh, 255, 127, 0);
1496 e_TextureFontPrintEx(gScreenWidth div 2 - mw, gScreenHeight-24, ip, gStdFont, 225, 225, 225, 1);
1498 // MOTD
1499 if slMOTD <> '' then
1500 begin
1501 e_DrawFillQuad(16, motdh, gScreenWidth-16, gScreenHeight-44, 64, 64, 64, 110);
1502 e_DrawQuad(16, motdh, gScreenWidth-16, gScreenHeight-44, 255, 127, 0);
1503 e_TextureFontPrintFmt(20, motdh + 3, slMOTD, gStdFont, False, True);
1504 end;
1506 // Urgent message
1507 if not slReadUrgent and (slUrgent <> '') then
1508 begin
1509 e_DrawFillQuad(17, 65, gScreenWidth-17, motdh-1, 64, 64, 64, 128);
1510 e_DrawFillQuad(gScreenWidth div 2 - 256, gScreenHeight div 2 - 60,
1511 gScreenWidth div 2 + 256, gScreenHeight div 2 + 60, 64, 64, 64, 128);
1512 e_DrawQuad(gScreenWidth div 2 - 256, gScreenHeight div 2 - 60,
1513 gScreenWidth div 2 + 256, gScreenHeight div 2 + 60, 255, 127, 0);
1514 e_DrawLine(1, gScreenWidth div 2 - 256, gScreenHeight div 2 - 40,
1515 gScreenWidth div 2 + 256, gScreenHeight div 2 - 40, 255, 127, 0);
1516 l := Length(_lc[I_NET_SLIST_URGENT]) div 2;
1517 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 - 58,
1518 _lc[I_NET_SLIST_URGENT], gStdFont);
1519 l := Length(slUrgent) div 2;
1520 e_TextureFontPrintFmt(gScreenWidth div 2 - 253, gScreenHeight div 2 - 38,
1521 slUrgent, gStdFont, False, True);
1522 l := Length(_lc[I_NET_SLIST_URGENT_CONT]) div 2;
1523 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 + 41,
1524 _lc[I_NET_SLIST_URGENT_CONT], gStdFont);
1525 e_DrawLine(1, gScreenWidth div 2 - 256, gScreenHeight div 2 + 40,
1526 gScreenWidth div 2 + 256, gScreenHeight div 2 + 40, 255, 127, 0);
1527 Exit;
1528 end;
1530 if SL = nil then
1531 begin
1532 l := Length(slWaitStr) div 2;
1533 e_DrawFillQuad(17, 65, gScreenWidth-17, motdh-1, 64, 64, 64, 128);
1534 e_DrawQuad(gScreenWidth div 2 - 192, gScreenHeight div 2 - 10,
1535 gScreenWidth div 2 + 192, gScreenHeight div 2 + 11, 255, 127, 0);
1536 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 - ch div 2,
1537 slWaitStr, gStdFont);
1538 Exit;
1539 end;
1541 y := 90;
1542 if (slSelection < Length(ST)) then
1543 begin
1544 I := slSelection;
1545 sy := y + 42 * I - 4;
1546 Srv := GetServerFromTable(I, SL, ST);
1547 ip := _lc[I_NET_ADDRESS] + ' ' + Srv.IP + ':' + IntToStr(Srv.Port);
1548 if Srv.Password then
1549 ip := ip + ' ' + _lc[I_NET_SERVER_PASSWORD] + ' ' + _lc[I_MENU_YES]
1550 else
1551 ip := ip + ' ' + _lc[I_NET_SERVER_PASSWORD] + ' ' + _lc[I_MENU_NO];
1552 end else
1553 if Length(ST) > 0 then
1554 slSelection := 0;
1556 mw := (gScreenWidth - 188);
1557 mx := 16 + mw;
1559 e_DrawFillQuad(16 + 1, sy, gScreenWidth - 16 - 1, sy + 40, 64, 64, 64, 0);
1560 e_DrawLine(1, 16 + 1, sy, gScreenWidth - 16 - 1, sy, 205, 205, 205);
1561 e_DrawLine(1, 16 + 1, sy + 41, gScreenWidth - 16 - 1, sy + 41, 255, 255, 255);
1563 e_DrawLine(1, 16, 85, gScreenWidth - 16, 85, 255, 127, 0);
1564 e_DrawLine(1, 16, motdh-20, gScreenWidth-16, motdh-20, 255, 127, 0);
1566 e_DrawLine(1, mx - 70, 64, mx - 70, motdh, 255, 127, 0);
1567 e_DrawLine(1, mx, 64, mx, motdh-20, 255, 127, 0);
1568 e_DrawLine(1, mx + 52, 64, mx + 52, motdh-20, 255, 127, 0);
1569 e_DrawLine(1, mx + 104, 64, mx + 104, motdh-20, 255, 127, 0);
1571 e_TextureFontPrintEx(18, 68, 'NAME/MAP', gStdFont, 255, 127, 0, 1);
1572 e_TextureFontPrintEx(mx - 68, 68, 'PING', gStdFont, 255, 127, 0, 1);
1573 e_TextureFontPrintEx(mx + 2, 68, 'MODE', gStdFont, 255, 127, 0, 1);
1574 e_TextureFontPrintEx(mx + 54, 68, 'PLRS', gStdFont, 255, 127, 0, 1);
1575 e_TextureFontPrintEx(mx + 106, 68, 'VER', gStdFont, 255, 127, 0, 1);
1577 y := 90;
1578 for I := 0 to High(ST) do
1579 begin
1580 Srv := GetServerFromTable(I, SL, ST);
1581 // Name and map
1582 e_TextureFontPrintEx(18, y, Srv.Name, gStdFont, 255, 255, 255, 1);
1583 e_TextureFontPrintEx(18, y + 16, Srv.Map, gStdFont, 210, 210, 210, 1);
1585 // Ping and similar count
1586 if (Srv.Ping < 0) or (Srv.Ping > 999) then
1587 e_TextureFontPrintEx(mx - 68, y, _lc[I_NET_SLIST_NO_ACCESS], gStdFont, 255, 0, 0, 1)
1588 else
1589 if Srv.Ping = 0 then
1590 e_TextureFontPrintEx(mx - 68, y, '<1' + _lc[I_NET_SLIST_PING_MS], gStdFont, 255, 255, 255, 1)
1591 else
1592 e_TextureFontPrintEx(mx - 68, y, IntToStr(Srv.Ping) + _lc[I_NET_SLIST_PING_MS], gStdFont, 255, 255, 255, 1);
1594 if Length(ST[I].Indices) > 1 then
1595 e_TextureFontPrintEx(mx - 68, y + 16, '< ' + IntToStr(Length(ST[I].Indices)) + ' >', gStdFont, 210, 210, 210, 1);
1597 // Game mode
1598 e_TextureFontPrintEx(mx + 2, y, g_Game_ModeToText(Srv.GameMode), gStdFont, 255, 255, 255, 1);
1600 // Players
1601 e_TextureFontPrintEx(mx + 54, y, IntToStr(Srv.Players) + '/' + IntToStr(Srv.MaxPlayers), gStdFont, 255, 255, 255, 1);
1602 e_TextureFontPrintEx(mx + 54, y + 16, IntToStr(Srv.LocalPl) + '+' + IntToStr(Srv.Bots), gStdFont, 210, 210, 210, 1);
1604 // Version
1605 e_TextureFontPrintEx(mx + 106, y, IntToStr(Srv.Protocol), gStdFont, 255, 255, 255, 1);
1607 y := y + 42;
1608 end;
1610 e_TextureFontPrintEx(20, motdh-20+3, ip, gStdFont, 205, 205, 205, 1);
1611 ip := IntToStr(Length(ST)) + _lc[I_NET_SLIST_SERVERS];
1612 e_TextureFontPrintEx(gScreenWidth - 48 - (Length(ip) + 1)*cw,
1613 motdh-20+3, ip, gStdFont, 205, 205, 205, 1);
1614 end;
1617 //==========================================================================
1618 //
1619 // g_Serverlist_GenerateTable
1620 //
1621 //==========================================================================
1622 procedure g_Serverlist_GenerateTable (SL: TNetServerList; var ST: TNetServerTable);
1623 var
1624 i, j: Integer;
1626 function FindServerInTable(Name: AnsiString): Integer;
1627 var
1628 i: Integer;
1629 begin
1630 Result := -1;
1631 if ST = nil then
1632 Exit;
1633 for i := Low(ST) to High(ST) do
1634 begin
1635 if Length(ST[i].Indices) = 0 then
1636 continue;
1637 if SL[ST[i].Indices[0]].Name = Name then
1638 begin
1639 Result := i;
1640 Exit;
1641 end;
1642 end;
1643 end;
1644 function ComparePing(i1, i2: Integer): Boolean;
1645 var
1646 p1, p2: Int64;
1647 begin
1648 p1 := SL[i1].Ping;
1649 p2 := SL[i2].Ping;
1650 if (p1 < 0) then p1 := 999;
1651 if (p2 < 0) then p2 := 999;
1652 Result := p1 > p2;
1653 end;
1654 procedure SortIndices(var ind: Array of Integer);
1655 var
1656 I, J: Integer;
1657 T: Integer;
1658 begin
1659 for I := High(ind) downto Low(ind) do
1660 for J := Low(ind) to High(ind) - 1 do
1661 if ComparePing(ind[j], ind[j+1]) then
1662 begin
1663 T := ind[j];
1664 ind[j] := ind[j+1];
1665 ind[j+1] := T;
1666 end;
1667 end;
1668 procedure SortRows();
1669 var
1670 I, J: Integer;
1671 T: TNetServerRow;
1672 begin
1673 for I := High(ST) downto Low(ST) do
1674 for J := Low(ST) to High(ST) - 1 do
1675 if ComparePing(ST[j].Indices[0], ST[j+1].Indices[0]) then
1676 begin
1677 T := ST[j];
1678 ST[j] := ST[j+1];
1679 ST[j+1] := T;
1680 end;
1681 end;
1682 begin
1683 ST := nil;
1684 if SL = nil then
1685 Exit;
1686 for i := Low(SL) to High(SL) do
1687 begin
1688 j := FindServerInTable(SL[i].Name);
1689 if j = -1 then
1690 begin
1691 j := Length(ST);
1692 SetLength(ST, j + 1);
1693 ST[j].Current := 0;
1694 SetLength(ST[j].Indices, 1);
1695 ST[j].Indices[0] := i;
1696 end
1697 else
1698 begin
1699 SetLength(ST[j].Indices, Length(ST[j].Indices) + 1);
1700 ST[j].Indices[High(ST[j].Indices)] := i;
1701 end;
1702 end;
1704 for i := Low(ST) to High(ST) do
1705 SortIndices(ST[i].Indices);
1707 SortRows();
1708 end;
1711 //==========================================================================
1712 //
1713 // g_Serverlist_Control
1714 //
1715 //==========================================================================
1716 procedure g_Serverlist_Control (var SL: TNetServerList; var ST: TNetServerTable);
1717 var
1718 qm: Boolean;
1719 Srv: TNetServer;
1720 begin
1721 g_Net_Slist_Pulse();
1723 if gConsoleShow or gChatShow then
1724 Exit;
1726 qm := sys_HandleInput(); // this updates kbd
1728 if qm or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
1729 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or
1730 e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
1731 begin
1732 SL := nil;
1733 ST := nil;
1734 gState := STATE_MENU;
1735 g_GUI_ShowWindow('MainMenu');
1736 g_GUI_ShowWindow('NetGameMenu');
1737 g_GUI_ShowWindow('NetClientMenu');
1738 g_Sound_PlayEx(WINDOW_CLOSESOUND);
1739 Exit;
1740 end;
1742 // if there's a message on the screen,
1743 if not slReadUrgent and (slUrgent <> '') then
1744 begin
1745 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1746 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
1747 slReadUrgent := True;
1748 Exit;
1749 end;
1751 if e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_JUMP) or
1752 e_KeyPressed(JOY0_ACTIVATE) or e_KeyPressed(JOY1_ACTIVATE) or e_KeyPressed(JOY2_ACTIVATE) or e_KeyPressed(JOY3_ACTIVATE) then
1753 begin
1754 if not slFetched then
1755 begin
1756 slWaitStr := _lc[I_NET_SLIST_WAIT];
1758 g_Game_Draw;
1759 sys_Repaint;
1761 if g_Net_Slist_Fetch(SL) then
1762 begin
1763 if SL = nil then
1764 slWaitStr := _lc[I_NET_SLIST_NOSERVERS];
1765 end
1766 else
1767 if SL = nil then
1768 slWaitStr := _lc[I_NET_SLIST_ERROR];
1769 slFetched := True;
1770 slSelection := 0;
1771 g_Serverlist_GenerateTable(SL, ST);
1772 end;
1773 end
1774 else
1775 slFetched := False;
1777 if SL = nil then Exit;
1779 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1780 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
1781 begin
1782 if not slReturnPressed then
1783 begin
1784 Srv := GetServerFromTable(slSelection, SL, ST);
1785 if Srv.Password then
1786 begin
1787 PromptIP := Srv.IP;
1788 PromptPort := Srv.Port;
1789 gState := STATE_MENU;
1790 g_GUI_ShowWindow('ClientPasswordMenu');
1791 SL := nil;
1792 ST := nil;
1793 slReturnPressed := True;
1794 Exit;
1795 end
1796 else
1797 g_Game_StartClient(Srv.IP, Srv.Port, '');
1798 SL := nil;
1799 ST := nil;
1800 slReturnPressed := True;
1801 Exit;
1802 end;
1803 end
1804 else
1805 slReturnPressed := False;
1807 if e_KeyPressed(IK_DOWN) or e_KeyPressed(IK_KPDOWN) or e_KeyPressed(VK_DOWN) or
1808 e_KeyPressed(JOY0_DOWN) or e_KeyPressed(JOY1_DOWN) or e_KeyPressed(JOY2_DOWN) or e_KeyPressed(JOY3_DOWN) then
1809 begin
1810 if not slDirPressed then
1811 begin
1812 Inc(slSelection);
1813 if slSelection > High(ST) then slSelection := 0;
1814 slDirPressed := True;
1815 end;
1816 end;
1818 if e_KeyPressed(IK_UP) or e_KeyPressed(IK_KPUP) or e_KeyPressed(VK_UP) or
1819 e_KeyPressed(JOY0_UP) or e_KeyPressed(JOY1_UP) or e_KeyPressed(JOY2_UP) or e_KeyPressed(JOY3_UP) then
1820 begin
1821 if not slDirPressed then
1822 begin
1823 if slSelection = 0 then slSelection := Length(ST);
1824 Dec(slSelection);
1826 slDirPressed := True;
1827 end;
1828 end;
1830 if e_KeyPressed(IK_RIGHT) or e_KeyPressed(IK_KPRIGHT) or e_KeyPressed(VK_RIGHT) or
1831 e_KeyPressed(JOY0_RIGHT) or e_KeyPressed(JOY1_RIGHT) or e_KeyPressed(JOY2_RIGHT) or e_KeyPressed(JOY3_RIGHT) then
1832 begin
1833 if not slDirPressed then
1834 begin
1835 Inc(ST[slSelection].Current);
1836 if ST[slSelection].Current > High(ST[slSelection].Indices) then ST[slSelection].Current := 0;
1837 slDirPressed := True;
1838 end;
1839 end;
1841 if e_KeyPressed(IK_LEFT) or e_KeyPressed(IK_KPLEFT) or e_KeyPressed(VK_LEFT) or
1842 e_KeyPressed(JOY0_LEFT) or e_KeyPressed(JOY1_LEFT) or e_KeyPressed(JOY2_LEFT) or e_KeyPressed(JOY3_LEFT) then
1843 begin
1844 if not slDirPressed then
1845 begin
1846 if ST[slSelection].Current = 0 then ST[slSelection].Current := Length(ST[slSelection].Indices);
1847 Dec(ST[slSelection].Current);
1849 slDirPressed := True;
1850 end;
1851 end;
1853 if (not e_KeyPressed(IK_DOWN)) and
1854 (not e_KeyPressed(IK_UP)) and
1855 (not e_KeyPressed(IK_RIGHT)) and
1856 (not e_KeyPressed(IK_LEFT)) and
1857 (not e_KeyPressed(IK_KPDOWN)) and
1858 (not e_KeyPressed(IK_KPUP)) and
1859 (not e_KeyPressed(IK_KPRIGHT)) and
1860 (not e_KeyPressed(IK_KPLEFT)) and
1861 (not e_KeyPressed(VK_DOWN)) and
1862 (not e_KeyPressed(VK_UP)) and
1863 (not e_KeyPressed(VK_RIGHT)) and
1864 (not e_KeyPressed(VK_LEFT)) and
1865 (not e_KeyPressed(JOY0_UP)) and (not e_KeyPressed(JOY1_UP)) and (not e_KeyPressed(JOY2_UP)) and (not e_KeyPressed(JOY3_UP)) and
1866 (not e_KeyPressed(JOY0_DOWN)) and (not e_KeyPressed(JOY1_DOWN)) and (not e_KeyPressed(JOY2_DOWN)) and (not e_KeyPressed(JOY3_DOWN)) and
1867 (not e_KeyPressed(JOY0_LEFT)) and (not e_KeyPressed(JOY1_LEFT)) and (not e_KeyPressed(JOY2_LEFT)) and (not e_KeyPressed(JOY3_LEFT)) and
1868 (not e_KeyPressed(JOY0_RIGHT)) and (not e_KeyPressed(JOY1_RIGHT)) and (not e_KeyPressed(JOY2_RIGHT)) and (not e_KeyPressed(JOY3_RIGHT))
1869 then
1870 slDirPressed := False;
1871 end;
1874 end.