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}
21 ENet
, SysUtils
, e_msg
;
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;
49 Players
, MaxPlayers
, LocalPl
, Bots
: Byte;
53 PingAddr
: ENetAddress
;
55 pTNetServer
= ^TNetServer
;
56 TNetServerRow
= record
57 Indices
: Array of Integer;
61 TNetServerList
= array of TNetServer
;
62 pTNetServerList
= ^TNetServerList
;
63 TNetServerTable
= array of TNetServerRow
;
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
83 srvAnswer
: array of TNetServer
;
86 slReadUrgent
: Boolean;
89 connectCount
: Integer;
95 constructor Create (var ea
: ENetAddress
);
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
110 procedure disconnect (forced
: Boolean);
111 function connect (): Boolean;
116 class procedure writeInfo (var msg
: TMsg
); static
;
118 procedure connectedEvent ();
119 procedure disconnectedEvent ();
120 procedure receivedEvent (pkt
: pENetPacket
); // `pkt` is never `nil`
125 slCurrent
: TNetServerList
;
126 slTable
: TNetServerTable
;
127 slWaitStr
: AnsiString
;
128 slReturnPressed
: Boolean = True;
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 ();
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;
175 {$IFDEF ENABLE_SOUND}
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 // ////////////////////////////////////////////////////////////////////////// //
185 THashStrDWord
= specialize THashBase
<AnsiString
, LongWord
, THashKeyStrAnsiCI
>;
189 NetMEvent
: ENetEvent
;
190 mlist
: array of TMasterHost
;
194 slDirPressed
: Boolean;
195 slReadUrgent
: Boolean;
197 reportsEnabled
: Boolean = True;
198 knownHosts
: THashStrDWord
;
200 //==========================================================================
204 //==========================================================================
205 function GetTimerMS (): Int64;
207 Result
:= sys_GetTicks() {div 1000};
210 //==========================================================================
214 //==========================================================================
215 function findByPeer (peer
: pENetPeer
): Integer;
219 for f
:= 0 to High(mlist
) do
220 if (mlist
[f
].peer
= peer
) then
226 //==========================================================================
230 //==========================================================================
231 procedure g_Net_Slist_ShutdownAll ();
233 f
, sres
, idx
: Integer;
235 activeCount
: Integer = 0;
236 label // all this code is retarded anyway, so I feel no shame
239 if (NetMHost
= nil) then goto discard
;
240 for f
:= 0 to High(mlist
) do
242 if (mlist
[f
].isAlive()) then
245 if (mlist
[f
].isConnected() and mlist
[f
].updateSent
) then
247 writeln('unregistering from [', mlist
[f
].hostName
, ']');
250 //mlist[f].disconnect(false);
251 enet_peer_disconnect_later(mlist
[f
].peer
, 0);
254 if (activeCount
= 0) then goto discard
;
256 while (activeCount
> 0) do
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
);
271 if (NetMEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then enet_packet_destroy(NetMEvent
.packet
);
275 if (NetMEvent
.kind
= ENET_EVENT_TYPE_CONNECT
) then
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
283 mlist
[idx
].disconnectedEvent();
286 else if (NetMEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
288 mlist
[idx
].receivedEvent(NetMEvent
.packet
);
289 enet_packet_destroy(NetMEvent
.packet
);
292 enet_host_destroy(NetMHost
);
296 for f
:= 0 to High(mlist
) do
300 FreeAndNil(knownHosts
);
303 //==========================================================================
307 //==========================================================================
308 procedure DisconnectAll (forced
: Boolean=false);
312 for f
:= 0 to High(mlist
) do
314 if (mlist
[f
].isAlive()) then mlist
[f
].disconnect(forced
);
318 //==========================================================================
322 //==========================================================================
323 procedure ConnectAll (sendUpdate
: Boolean);
327 // set flags; pulse will take care of the rest
328 for f
:= 0 to High(mlist
) do
331 mlist
[f
].lastDisconnectTime
:= 0;
335 mlist
[f
].NetUpdatePending
:= true;
336 mlist
[f
].lastUpdateTime
:= 0;
341 //==========================================================================
345 //==========================================================================
346 procedure UpdateAll (force
: Boolean);
350 // set flags; pulse will take care of the rest
351 for f
:= 0 to High(mlist
) do
353 if (not mlist
[f
].isAlive()) then continue
;
354 mlist
[f
].NetUpdatePending
:= true;
355 if (force
) then mlist
[f
].lastUpdateTime
:= 0;
359 //**************************************************************************
363 //**************************************************************************
365 //==========================================================================
367 // g_Net_Slist_Private
369 // make this server private
371 //==========================================================================
372 procedure g_Net_Slist_Private ();
375 reportsEnabled
:= false;
378 //==========================================================================
380 // g_Net_Slist_Public
382 // make this server public
384 //==========================================================================
385 procedure g_Net_Slist_Public ();
387 if (not reportsEnabled
) then
389 reportsEnabled
:= true;
394 //==========================================================================
396 // g_Net_Slist_ServerUpdate
398 // called while the server is running
400 //==========================================================================
401 procedure g_Net_Slist_ServerUpdate ();
406 // called when the server is started
407 procedure g_Net_Slist_ServerStarted ();
409 reportsEnabled
:= NetUseMaster
;
410 if reportsEnabled
and g_Game_IsServer() and g_Game_IsNet() then
412 writeln('*** server started; reporting to master...');
417 //==========================================================================
419 // g_Net_Slist_ServerClosed
421 // called when the server is stopped
423 //==========================================================================
424 procedure g_Net_Slist_ServerClosed ();
428 if reportsEnabled
then
430 reportsEnabled
:= false;
431 for f
:= 0 to High(mlist
) do
433 if (mlist
[f
].isConnected()) then mlist
[f
].remove();
439 //==========================================================================
441 // g_Net_Slist_ServerPlayerComes
443 // called when new netword player comes
445 //==========================================================================
446 procedure g_Net_Slist_ServerPlayerComes ();
451 //==========================================================================
453 // g_Net_Slist_ServerPlayerLeaves
455 // called when new netword player comes
457 //==========================================================================
458 procedure g_Net_Slist_ServerPlayerLeaves ();
463 //==========================================================================
465 // g_Net_Slist_ServerMapStarted
469 //==========================================================================
470 procedure g_Net_Slist_ServerMapStarted ();
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 ();
487 //**************************************************************************
491 //**************************************************************************
493 constructor TMasterHost
.Create (var ea
: ENetAddress
);
496 NetHostConnected
:= false;
497 NetHostConReqTime
:= 0;
498 NetUpdatePending
:= false;
499 lastDisconnectTime
:= 0;
503 ZeroMemory(@enetAddr
, sizeof(enetAddr
));
504 SetLength(srvAnswer
, 0);
508 slReadUrgent
:= true;
511 netmsg
.Alloc(NET_BUFSIZE
);
515 procedure TMasterHost
.finish ();
520 procedure TMasterHost
.cleanup ();
522 updateSent
:= False; // do not send 'remove'
526 SetLength(srvAnswer
, 0);
530 slReadUrgent
:= True;
531 ZeroMemory(@enetAddr
, sizeof(enetAddr
));
534 //==========================================================================
536 // TMasterHost.setAddress
538 //==========================================================================
539 function TMasterHost
.setAddress (var ea
: ENetAddress
; hostStr
: AnsiString
): Boolean;
542 SetLength(srvAnswer
, 0);
546 slReadUrgent
:= true;
547 updateSent
:= false; // do not send 'remove'
551 if (not g_Net_IsNetworkAvailable()) then exit
;
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
);
561 //==========================================================================
563 // TMasterHost.isValid
565 //==========================================================================
566 function TMasterHost
.isValid (): Boolean;
568 result
:= (enetAddr
.host
<> 0) and (enetAddr
.port
<> 0);
571 //==========================================================================
573 // TMasterHost.isAlive
577 //==========================================================================
578 function TMasterHost
.isAlive (): Boolean;
580 result
:= (NetMHost
<> nil) and (peer
<> nil);
583 //==========================================================================
585 // TMasterHost.isConnecting
587 // is connection in progress?
589 //==========================================================================
590 function TMasterHost
.isConnecting (): Boolean;
592 result
:= isAlive() and (not NetHostConnected
) and (NetHostConReqTime
<> -1);
595 //==========================================================================
597 // TMasterHost.isConnected
599 //==========================================================================
600 function TMasterHost
.isConnected (): Boolean;
602 result
:= isAlive() and (NetHostConnected
) and (NetHostConReqTime
<> -1);
605 //==========================================================================
607 // TMasterHost.connectedEvent
609 //==========================================================================
610 procedure TMasterHost
.connectedEvent ();
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]));
620 //==========================================================================
622 // TMasterHost.disconnectedEvent
624 //==========================================================================
625 procedure TMasterHost
.disconnectedEvent ();
627 if not isAlive() then exit
;
628 e_LogWritefln('disconnected from master at [%s]', [hostName
], TMsgType
.Notify
);
630 //if (spamConsole) then g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_DISC], [hostName]));
633 //==========================================================================
635 // TMasterHost.receivedEvent
637 // `pkt` is never `nil`
639 //==========================================================================
640 procedure TMasterHost
.receivedEvent (pkt
: pENetPacket
);
648 e_LogWritefln('received packed from master at [%s]', [hostName
], TMsgType
.Notify
);
649 if not msg
.Init(pkt
^.data
, pkt
^.dataLength
, True) then exit
;
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
);
658 slReadUrgent
:= true;
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
);
665 SetLength(srvAnswer
, Cnt
);
666 for f
:= 0 to Cnt
-1 do
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
;
684 if (msg
.ReadCount
< msg
.CurSize
) then
686 // new master, supports version reports
687 s
:= msg
.ReadString();
688 if (s
<> {MyVer}GAME_VERSION
) then
691 g_Console_Add('!!! UpdVer = `'+s
+'`');
693 // even newer master, supports extra info
694 if (msg
.ReadCount
< msg
.CurSize
) then
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;
702 if (s
<> '') then e_LogWritefln('got urgent from master at [%s]: %s', [hostName
, s
], TMsgType
.Notify
);
707 //==========================================================================
709 // TMasterHost.disconnect
711 //==========================================================================
712 procedure TMasterHost
.disconnect (forced
: Boolean);
716 lastDisconnectTime
:= GetTimerMS();
717 if forced
or (not NetHostConnected
) or (NetHostConReqTime
= -1) then
719 enet_peer_reset(peer
);
721 NetHostConReqTime
:= 0;
726 enet_peer_disconnect_later(peer
, 0);
727 // main pulse will take care of the rest
728 NetHostConReqTime
:= -1;
734 NetHostConReqTime
:= 0;
738 NetHostConnected
:= false;
739 NetUpdatePending
:= false;
741 //if (spamConsole) then g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_DISC], [hostName]));
744 //==========================================================================
746 // TMasterHost.connect
748 //==========================================================================
749 function TMasterHost
.connect (): Boolean;
752 if not isValid() then exit
;
753 if (NetHostConReqTime
= -1) then
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
);
761 if isAlive() then begin result
:= true; exit
; end;
764 lastDisconnectTime
:= GetTimerMS(); // why not?
765 SetLength(srvAnswer
, 0);
767 NetHostConnected
:= false;
768 NetHostConReqTime
:= 0;
769 NetUpdatePending
:= false;
774 peer
:= enet_host_connect(NetMHost
, @enetAddr
, NET_MCHANS
, 0);
777 g_Console_Add(_lc
[I_NET_MSG_ERROR
]+_lc
[I_NET_ERR_CLIENT
], true);
781 NetHostConReqTime
:= lastDisconnectTime
;
782 e_LogWritefln('connecting to master at [%s]', [hostName
], TMsgType
.Notify
);
785 //==========================================================================
787 // TMasterHost.writeInfo
789 //==========================================================================
790 class procedure TMasterHost
.writeInfo (var msg
: TMsg
);
792 wad
, map
: AnsiString
;
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
<> ''));
810 //==========================================================================
812 // TMasterHost.update
814 //==========================================================================
815 procedure TMasterHost
.update ();
819 if not isAlive() then exit
;
820 if not isConnected() then
822 NetUpdatePending
:= isConnecting();
828 if reportsEnabled
and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster
then
831 netmsg
.Write(Byte(NET_MMSG_UPD
));
832 netmsg
.Write(NetAddr
.port
);
833 //writeln(formatstrf('%08x', [NetAddr.host]), ' : ', NetAddr.host);
837 pkt
:= enet_packet_create(netmsg
.Data
, netmsg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
838 if assigned(pkt
) then
840 if (enet_peer_send(peer
, NET_MCHAN_UPD
, pkt
) = 0) then
842 e_LogWritefln('sent update to master at [%s]', [hostName
], TMsgType
.Notify
);
843 NetUpdatePending
:= false;
853 NetUpdatePending
:= false;
857 //==========================================================================
859 // TMasterHost.remove
861 //==========================================================================
862 procedure TMasterHost
.remove ();
866 NetUpdatePending
:= false;
869 if not isAlive() then exit
;
870 if not isConnected() then exit
;
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
880 enet_peer_send(peer
, NET_MCHAN_MAIN
, pkt
);
887 //==========================================================================
891 // this performs various scheduled tasks, if necessary
893 //==========================================================================
894 procedure TMasterHost
.pulse ();
899 if not isAlive() then exit
;
900 if (NetHostConReqTime
= -1) then exit
; // waiting for shutdown (disconnect in progress)
902 // process pending connection timeout
903 if (not NetHostConnected
) then
905 if (ct
< NetHostConReqTime
) or (ct
-NetHostConReqTime
>= 1000*NMASTER_TIMEOUT_CONNECT
) then
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);
914 // send update, if necessary
915 if (NetUpdatePending
) then
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
923 //e_LogWritefln('update timeout: %d', [Integer(mrate)], TMsgType.Notify);
924 lastUpdateTime
:= ct
;
930 //==========================================================================
934 //==========================================================================
935 function parseAddressPort (var ea
: ENetAddress
; hostandport
: AnsiString
): Boolean;
938 hostName
: AnsiString
;
942 if (not g_Net_IsNetworkAvailable()) then exit
;
944 hostandport
:= Trim(hostandport
);
945 if (length(hostandport
) = 0) then exit
;
947 hostName
:= hostandport
;
950 cp
:= Pos(':', hostandport
);
953 hostName
:= Trim(Copy(hostandport
, 1, cp
-1));
954 Delete(hostandport
, 1, cp
);
955 hostandport
:= Trim(hostandport
);
956 if (length(hostandport
) > 0) then
959 port
:= StrToInt(hostandport
);
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
977 if (enet_address_set_host(@ea
, PChar(Addr(hostName
[1]))) <> 0) then
979 knownHosts
.put(hostName
, 0);
982 knownHosts
.put(hostName
, ea
.host
);
988 //==========================================================================
992 //==========================================================================
993 procedure addMasterRecord (var ea
: ENetAddress
; sa
: AnsiString
);
999 for f
:= 0 to High(mlist
) do
1001 if (mlist
[f
].enetAddr
.host
= ea
.host
) and (mlist
[f
].enetAddr
.port
= ea
.port
) then
1003 mlist
[f
].justAdded
:= true;
1006 if (freeIdx
< 0) and (not mlist
[f
].isValid()) then freeIdx
:= f
;
1008 if (freeIdx
< 0) then
1010 freeIdx
:= length(mlist
);
1011 SetLength(mlist
, freeIdx
+1);
1012 mlist
[freeIdx
].Create(ea
);
1014 mlist
[freeIdx
].justAdded
:= true;
1015 mlist
[freeIdx
].setAddress(ea
, sa
);
1016 e_LogWritefln('added masterserver with address [%s]', [sa
], TMsgType
.Notify
);
1019 //==========================================================================
1023 //==========================================================================
1024 procedure g_Net_Slist_Set (list
: AnsiString
);
1031 if (not g_Net_IsNetworkAvailable()) then exit
;
1033 for f
:= 0 to High(mlist
) do mlist
[f
].justAdded
:= false;
1036 //writeln('list=[', list, ']');
1037 while (length(list
) > 0) do
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
);
1047 // remove unknown master servers
1049 for f
:= 0 to High(mlist
) do
1051 if (not mlist
[f
].justAdded
) then mlist
[f
].cleanup();
1052 if (mlist
[f
].isValid()) then
1056 mlist
[dest
].finish();
1057 mlist
[dest
] := mlist
[f
];
1062 SetLength(mlist
, dest
);
1065 //**************************************************************************
1069 //**************************************************************************
1071 //==========================================================================
1073 // isMasterReportsEnabled
1075 //==========================================================================
1076 function isMasterReportsEnabled (): Boolean;
1078 result
:= (reportsEnabled
and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster
);
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);
1094 isListQuery
: Boolean;
1097 if (not g_Net_IsNetworkAvailable()) then exit
;
1099 if (length(mlist
) = 0) then
1101 if (NetMHost
<> nil) then
1103 enet_host_destroy(NetMHost
);
1109 if (NetMHost
= nil) then
1111 NetMHost
:= enet_host_create(nil, 64, NET_MCHANS
, 1024*1024, 1024*1024);
1112 if (NetMHost
= nil) then
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);
1121 isListQuery
:= (timeout
> 0);
1123 // reconnect/disconnect/pulse for each master
1124 for f
:= 0 to High(mlist
) do
1126 if (not mlist
[f
].isValid()) then continue
;
1127 if (not mlist
[f
].isAlive()) then
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
1132 if (mlist
[f
].lastDisconnectTime
= 0) or (ct
< mlist
[f
].lastDisconnectTime
) or (ct
-mlist
[f
].lastDisconnectTime
>= 1000*NMASTER_TIMEOUT_RECONNECT
) then
1134 e_LogWritefln('reconnecting to master [%s]', [mlist
[f
].hostName
], TMsgType
.Notify
);
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);
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
1148 if (mlist
[f
].isConnected()) and (mlist
[f
].updateSent
) then
1150 e_LogWritefln('removing from master [%s]', [mlist
[f
].hostName
], TMsgType
.Notify
);
1153 e_LogWritefln('disconnecting from master [%s]', [mlist
[f
].hostName
], TMsgType
.Notify
);
1154 mlist
[f
].disconnect(false);
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
);
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);
1179 idx
:= findByPeer(NetMEvent
.peer
);
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
);
1187 if (NetMEvent
.kind
= ENET_EVENT_TYPE_CONNECT
) then
1189 mlist
[idx
].connectedEvent();
1191 else if (NetMEvent
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
1193 mlist
[idx
].disconnectedEvent();
1195 else if (NetMEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
1197 mlist
[idx
].receivedEvent(NetMEvent
.packet
);
1198 enet_packet_destroy(NetMEvent
.packet
);
1203 if (count
= 0) then break
;
1204 sres
:= enet_host_service(NetMHost
, @NetMEvent
, 0);
1208 //**************************************************************************
1210 // gui and server list
1212 //**************************************************************************
1214 //==========================================================================
1218 //==========================================================================
1219 procedure PingServer (var S
: TNetServer
; Sock
: ENetSocket
);
1222 Ping
: array [0..9] of Byte;
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);
1237 //==========================================================================
1241 //==========================================================================
1242 procedure PingBcast (Sock
: ENetSocket
);
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])));
1250 S
.PingAddr
.port
:= S
.Port
;
1251 PingServer(S
, Sock
);
1254 //==========================================================================
1256 // g_Net_Slist_Fetch
1258 //==========================================================================
1259 function g_Net_Slist_Fetch (var SL
: TNetServerList
): Boolean;
1268 SvAddr
: ENetAddress
;
1272 procedure ProcessLocal ();
1275 SetLength(SL
, I
+ 1);
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();
1294 procedure CheckLocalServers ();
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);
1306 InMsg
.Alloc(NET_BUFSIZE
);
1307 Buf
.data
:= InMsg
.Data
;
1308 Buf
.dataLength
:= InMsg
.MaxSize
;
1309 while GetTimerMS() - T
<= 500 do
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
;
1326 enet_socket_destroy(Sock
);
1328 if Length(SL
) = 0 then SL
:= nil;
1332 f
, c
, n
, pos
: Integer;
1333 aliveCount
: Integer;
1334 hasUnanswered
: Boolean;
1341 if (not g_Net_IsNetworkAvailable()) then
1347 g_Net_Slist_Pulse(); // this will create mhost
1349 DisconnectAll(true); // forced disconnect
1351 for f
:= 0 to High(mlist
) do
1353 mlist
[f
].connectCount
:= 0;
1354 mlist
[f
].srvAnswered
:= 0;
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();
1373 hasUnanswered
:= false;
1374 for f
:= 0 to High(mlist
) do
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
1384 if (mlist
[f
].connectCount
= 0) then
1387 if (mlist
[f
].isAlive()) then
1389 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_WCONN], [mlist[f].hostName]));
1390 hasUnanswered
:= true;
1391 stt
:= GetTimerMS();
1394 else if (mlist
[f
].srvAnswered
> 1) then
1399 else if (mlist
[f
].isConnected()) then
1401 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_CONN], [mlist[f].hostName]));
1402 if (mlist
[f
].srvAnswered
= 0) then
1404 pkt
:= enet_packet_create(NetOut
.Data
, NetOut
.CurSize
, Cardinal(ENET_PACKET_FLAG_RELIABLE
));
1405 if assigned(pkt
) then
1407 if (enet_peer_send(mlist
[f
].peer
, NET_MCHAN_MAIN
, pkt
) = 0) then
1409 hasUnanswered
:= true;
1410 mlist
[f
].srvAnswered
:= 1;
1411 stt
:= GetTimerMS();
1415 else if (mlist
[f
].srvAnswered
= 1) then
1417 hasUnanswered
:= true;
1419 else if (mlist
[f
].srvAnswered
> 1) then
1422 mlist
[f
].disconnect(false); // not forced
1425 else if (mlist
[f
].isConnecting()) then
1427 hasUnanswered
:= true;
1430 if (not hasUnanswered
) then break
;
1431 // check for timeout
1433 if (ct
< stt
) or (ct
-stt
> 4000) then break
;
1434 g_Net_Slist_Pulse(300);
1437 if (aliveCount
= 0) then
1440 CheckLocalServers();
1448 slReadUrgent := true;
1452 for f
:= 0 to High(mlist
) do
1454 if (mlist
[f
].srvAnswered
< 2) then continue
;
1455 for n
:= 0 to High(mlist
[f
].srvAnswer
) do
1458 for c
:= 0 to High(SL
) do
1460 if (SL
[c
].IP
= mlist
[f
].srvAnswer
[n
].IP
) and (SL
[c
].Port
= mlist
[f
].srvAnswer
[n
].Port
) then
1469 SetLength(SL
, pos
+1);
1470 SL
[pos
] := mlist
[f
].srvAnswer
[n
];
1471 SL
[pos
].Number
:= pos
;
1474 if (not mlist
[f
].slReadUrgent
) and (mlist
[f
].slUrgent
<> '') then
1476 if (mlist
[f
].slUrgent
<> slUrgent
) then
1478 slUrgent
:= mlist
[f
].slUrgent
;
1479 slReadUrgent
:= false;
1482 if (slMOTD
<> '') and (mlist
[f
].slMOTD
<> '') then
1484 slMOTD
:= mlist
[f
].slMOTD
;
1490 if (length(SL
) = 0) then
1492 CheckLocalServers();
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);
1507 InMsg
.Alloc(NET_BUFSIZE
);
1508 Buf
.data
:= InMsg
.Data
;
1509 Buf
.dataLength
:= InMsg
.MaxSize
;
1511 while GetTimerMS() - T
<= 500 do
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
;
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();
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
1548 tmpsv
.IP
:= SL
[I
].IP
;
1558 SetLength(SL
, I
+ 1);
1559 tmpsv
.IP
:= DecodeIPV4(SvAddr
.host
);
1565 enet_socket_destroy(Sock
);
1571 //==========================================================================
1573 // GetServerFromTable
1575 //==========================================================================
1576 function GetServerFromTable (Index
: Integer; SL
: TNetServerList
; ST
: TNetServerTable
): TNetServer
;
1579 Result
.Protocol
:= 0;
1584 Result
.Players
:= 0;
1585 Result
.MaxPlayers
:= 0;
1586 Result
.LocalPl
:= 0;
1589 Result
.GameMode
:= 0;
1590 Result
.Password
:= false;
1591 FillChar(Result
.PingAddr
, SizeOf(ENetAddress
), 0);
1594 if (Index
< 0) or (Index
>= Length(ST
)) then
1596 Result
:= SL
[ST
[Index
].Indices
[ST
[Index
].Current
]];
1599 //==========================================================================
1601 // g_Serverlist_Draw
1603 //==========================================================================
1604 procedure g_Serverlist_Draw (var SL
: TNetServerList
; var ST
: TNetServerTable
);
1607 sy
, i
, y
, mw
, mx
, l
, motdh
: Integer;
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);
1633 if slMOTD
<> '' then
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);
1641 if not slReadUrgent
and (slUrgent
<> '') then
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);
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
);
1676 if (slSelection
< Length(ST
)) then
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
]
1685 ip
:= ip
+ ' ' + _lc
[I_NET_SERVER_PASSWORD
] + ' ' + _lc
[I_MENU_NO
];
1687 if Length(ST
) > 0 then
1690 mw
:= (gScreenWidth
- 188);
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);
1712 for I
:= 0 to High(ST
) do
1714 Srv
:= GetServerFromTable(I
, SL
, ST
);
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)
1723 if Srv
.Ping
= 0 then
1724 e_TextureFontPrintEx(mx
- 68, y
, '<1' + _lc
[I_NET_SLIST_PING_MS
], gStdFont
, 255, 255, 255, 1)
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);
1732 e_TextureFontPrintEx(mx
+ 2, y
, g_Game_ModeToText(Srv
.GameMode
), gStdFont
, 255, 255, 255, 1);
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);
1739 e_TextureFontPrintEx(mx
+ 106, y
, IntToStr(Srv
.Protocol
), gStdFont
, 255, 255, 255, 1);
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);
1750 //==========================================================================
1752 // g_Serverlist_GenerateTable
1754 //==========================================================================
1755 procedure g_Serverlist_GenerateTable (SL
: TNetServerList
; var ST
: TNetServerTable
);
1759 function FindServerInTable(Name
: AnsiString
; Port
: Word): Integer;
1766 for i
:= Low(ST
) to High(ST
) do
1768 if Length(ST
[i
].Indices
) = 0 then
1770 if (SL
[ST
[i
].Indices
[0]].Name
= Name
) and (SL
[ST
[i
].Indices
[0]].Port
= Port
) then
1777 function ComparePing(i1
, i2
: Integer): Boolean;
1783 if (p1
< 0) then p1
:= 999;
1784 if (p2
< 0) then p2
:= 999;
1787 procedure SortIndices(var ind
: Array of Integer);
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
1801 procedure SortRows();
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
1820 for i
:= Low(SL
) to High(SL
) do
1822 j
:= FindServerInTable(SL
[i
].Name
, SL
[i
].Port
);
1826 SetLength(ST
, j
+ 1);
1828 SetLength(ST
[j
].Indices
, 1);
1829 ST
[j
].Indices
[0] := i
;
1833 SetLength(ST
[j
].Indices
, Length(ST
[j
].Indices
) + 1);
1834 ST
[j
].Indices
[High(ST
[j
].Indices
)] := i
;
1838 for i
:= Low(ST
) to High(ST
) do
1839 SortIndices(ST
[i
].Indices
);
1844 //==========================================================================
1846 // g_Serverlist_Control
1848 //==========================================================================
1849 procedure g_Serverlist_Control (var SL
: TNetServerList
; var ST
: TNetServerTable
);
1854 g_Net_Slist_Pulse();
1856 if gConsoleShow
or gChatShow
then
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
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
);
1877 // if there's a message on the screen,
1878 if not slReadUrgent
and (slUrgent
<> '') then
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;
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
1890 if not slFetched
then
1892 slWaitStr
:= _lc
[I_NET_SLIST_WAIT
];
1897 if g_Net_Slist_Fetch(SL
) then
1900 slWaitStr
:= _lc
[I_NET_SLIST_NOSERVERS
];
1904 slWaitStr
:= _lc
[I_NET_SLIST_ERROR
];
1907 g_Serverlist_GenerateTable(SL
, ST
);
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
1919 if not slReturnPressed
then
1921 Srv
:= GetServerFromTable(slSelection
, SL
, ST
);
1922 if Srv
.Password
then
1925 PromptPort
:= Srv
.Port
;
1926 gState
:= STATE_MENU
;
1927 g_GUI_ShowWindow('ClientPasswordMenu');
1930 slReturnPressed
:= True;
1934 g_Game_StartClient(Srv
.IP
, Srv
.Port
, '');
1937 slReturnPressed
:= True;
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
1947 if not slDirPressed
then
1950 if slSelection
> High(ST
) then slSelection
:= 0;
1951 slDirPressed
:= True;
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
1958 if not slDirPressed
then
1960 if slSelection
= 0 then slSelection
:= Length(ST
);
1963 slDirPressed
:= True;
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
1970 if not slDirPressed
then
1972 Inc(ST
[slSelection
].Current
);
1973 if ST
[slSelection
].Current
> High(ST
[slSelection
].Indices
) then ST
[slSelection
].Current
:= 0;
1974 slDirPressed
:= True;
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
1981 if not slDirPressed
then
1983 if ST
[slSelection
].Current
= 0 then ST
[slSelection
].Current
:= Length(ST
[slSelection
].Indices
);
1984 Dec(ST
[slSelection
].Current
);
1986 slDirPressed
:= True;
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
))
2007 slDirPressed
:= False;