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
;
41 Players
, MaxPlayers
, LocalPl
, Bots
: Byte;
45 PingAddr
: ENetAddress
;
47 pTNetServer
= ^TNetServer
;
48 TNetServerRow
= record
49 Indices
: Array of Integer;
53 TNetServerList
= array of TNetServer
;
54 pTNetServerList
= ^TNetServerList
;
55 TNetServerTable
= array of TNetServerRow
;
67 enetAddr
: ENetAddress
;
68 // inside the game, calling `connect()` is disasterous, as it is blocking.
69 // so we'll use this variable to indicate if "connected" event is received.
70 NetHostConnected
: Boolean;
71 NetHostConReqTime
: Int64; // to timeout `connect`
72 NetUpdatePending
: Boolean; // should we send an update after connection completes?
74 lastUpdateTime
: Int64;
75 addressInited
: Boolean;
81 function processPendingConnection (timeout
: Integer=0): Boolean;
84 constructor Create (hostandport
: AnsiString);
88 function setAddress (hostandport
: AnsiString): Boolean;
90 function isValid (): Boolean;
91 function isAlive (): Boolean; // not disconnected
92 function isConnecting (): Boolean; // is connection in progress?
93 function isConnected (): Boolean;
95 // returns `false` if connection failed
96 function waitForConnection (): 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 // returns `true` if we got a packet (it won't be parsed to TMsg)
101 function service (timeout
: Integer=0): Boolean;
103 procedure disconnect (spamConsole
: Boolean=false);
104 function connect (): Boolean;
106 procedure update (immediateSend
: Boolean=true);
109 class procedure writeInfo (var msg
: TMsg
); static
;
111 // call only if `service()` returned `true`!
112 procedure clearPacket ();
117 slCurrent
: TNetServerList
= nil;
118 slTable
: TNetServerTable
= nil;
119 slWaitStr
: string = '';
120 slReturnPressed
: Boolean = True;
123 slUrgent
: string = '';
125 procedure g_Net_Slist_Set (IP
: string; Port
: Word);
126 function g_Net_Slist_Fetch (var SL
: TNetServerList
): Boolean;
129 procedure g_Net_Slist_Update (immediateSend: Boolean=true);
130 procedure g_Net_Slist_Remove ();
131 function g_Net_Slist_Connect (blocking: Boolean=True): Boolean;
132 procedure g_Net_Slist_Check ();
133 procedure g_Net_Slist_Disconnect (spamConsole: Boolean=true);
134 procedure g_Net_Slist_WriteInfo ();
136 function g_Net_Slist_IsConnectionActive (): Boolean; // returns `false` if totally disconnected
137 function g_Net_Slist_IsConnectionInProgress (): Boolean;
140 // make this server private
141 procedure g_Net_Slist_Private ();
143 // called on network mode init
144 procedure g_Net_Slist_NetworkStarted ();
145 // called on network mode shutdown
146 procedure g_Net_Slist_NetworkStopped ();
148 procedure g_Net_Slist_Pulse ();
150 procedure g_Serverlist_GenerateTable (SL
: TNetServerList
; var ST
: TNetServerTable
);
151 procedure g_Serverlist_Draw (var SL
: TNetServerList
; var ST
: TNetServerTable
);
152 procedure g_Serverlist_Control (var SL
: TNetServerList
; var ST
: TNetServerTable
);
154 function GetTimerMS (): Int64;
160 e_input
, e_graphics
, e_log
, g_window
, g_net
, g_console
,
161 g_map
, g_game
, g_sound
, g_gui
, g_menu
, g_options
, g_language
, g_basic
,
162 wadreader
, g_system
, utils
;
164 // make this server private
165 procedure g_Net_Slist_Private ();
170 // called on network mode init
171 procedure g_Net_Slist_NetworkStarted ();
175 // called on network mode shutdown
176 procedure g_Net_Slist_NetworkStopped ();
182 NetMHost
: pENetHost
= nil;
183 mlist
: array of TMasterHost
= nil;
185 slSelection
: Byte = 0;
186 slFetched
: Boolean = False;
187 slDirPressed
: Boolean = False;
188 slReadUrgent
: Boolean = False;
190 NetMHost: pENetHost = nil;
191 NetMPeer: pENetPeer = nil;
192 NetMEvent: ENetEvent;
193 // inside the game, calling `g_Net_Slist_Connect()` is disasterous, as it is blocking.
194 // so we'll use this variable to indicate if "connected" event is received.
195 NetHostConnected: Boolean = false;
196 NetHostConReqTime: Int64 = 0; // to timeout `connect`
197 NetUpdatePending: Boolean = false;
201 //==========================================================================
205 //==========================================================================
206 function GetTimerMS (): Int64;
208 Result
:= sys_GetTicks() {div 1000};
212 //==========================================================================
214 // TMasterHost.Create
216 //==========================================================================
217 constructor TMasterHost
.Create (hostandport
: AnsiString);
221 ZeroMemory(@event
, sizeof(event
));
222 NetHostConnected
:= false;
223 NetHostConReqTime
:= 0;
224 NetUpdatePending
:= false;
228 netmsg
.Alloc(NET_BUFSIZE
);
229 setAddress(hostandport
);
233 //==========================================================================
237 //==========================================================================
238 procedure TMasterHost
.clear ();
240 updateSent
:= false; // do not send 'remove'
248 //==========================================================================
250 // TMasterHost.setAddress
252 //==========================================================================
253 function TMasterHost
.setAddress (hostandport
: AnsiString): Boolean;
258 updateSent
:= false; // do not send 'remove'
260 addressInited
:= false;
263 hostandport
:= Trim(hostandport
);
264 if (length(hostandport
) > 0) then
266 hostName
:= hostandport
;
267 cp
:= Pos(':', hostandport
);
270 hostName
:= Copy(hostandport
, 1, cp
-1);
271 Delete(hostandport
, 1, cp
);
272 if (length(hostandport
) > 0) then
275 pp
:= StrToInt(hostandport
);
279 if (pp
> 0) and (pp
< 65536) then hostPort
:= pp
else hostPort
:= 0;
284 if not isValid() then exit
;
285 if (NetInitDone
) then
287 if (enet_address_set_host(@enetAddr
, PChar(Addr(hostName
[1]))) <> 0) then
292 enetAddr
.Port
:= hostPort
;
299 //==========================================================================
301 // TMasterHost.isValid
303 //==========================================================================
304 function TMasterHost
.isValid (): Boolean;
306 result
:= (length(hostName
) > 0) and (hostPort
> 0);
310 //==========================================================================
312 // TMasterHost.isAlive
316 //==========================================================================
317 function TMasterHost
.isAlive (): Boolean;
319 result
:= (NetMHost
<> nil) and (peer
<> nil);
323 //==========================================================================
325 // TMasterHost.isConnecting
327 // is connection in progress?
329 //==========================================================================
330 function TMasterHost
.isConnecting (): Boolean;
332 result
:= isAlive() and (not NetHostConnected
);
336 //==========================================================================
338 // TMasterHost.isConnected
340 //==========================================================================
341 function TMasterHost
.isConnected (): Boolean;
343 result
:= isAlive() and (NetHostConnected
);
347 //==========================================================================
349 // TMasterHost.disconnect
351 //==========================================================================
352 procedure TMasterHost
.disconnect (spamConsole
: Boolean=false);
354 if not isAlive() then exit
;
355 if (NetMode
= NET_SERVER
) and isConnected() and updateSent
then remove();
357 enet_peer_disconnect(peer
, 0);
358 enet_host_flush(NetMHost
);
360 enet_peer_reset(peer
);
361 //enet_host_destroy(NetMHost);
365 NetHostConnected
:= False;
366 NetHostConReqTime
:= 0;
367 NetUpdatePending
:= false;
370 if (spamConsole
) then g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_SLIST_DISC
]);
374 //==========================================================================
376 // TMasterHost.connect
378 //==========================================================================
379 function TMasterHost
.connect (): Boolean;
383 updateSent
:= false; // do not send 'remove'
386 if not isValid() then exit
;
388 if (not NetInitDone
) then exit
;
390 if (NetMHost
= nil) then
392 NetMHost
:= enet_host_create(nil, 1, NET_MCHANS
, 0, 0);
393 if (NetMHost
= nil) then
395 g_Console_Add(_lc
[I_NET_MSG_ERROR
]+_lc
[I_NET_ERR_CLIENT
], True);
400 if (not addressInited
) then
402 if (enet_address_set_host(@enetAddr
, PChar(Addr(hostName
[1]))) <> 0) then
408 enetAddr
.Port
:= hostPort
;
409 addressInited
:= true;
412 peer
:= enet_host_connect(NetMHost
, @enetAddr
, NET_MCHANS
, 0);
415 g_Console_Add(_lc
[I_NET_MSG_ERROR
]+_lc
[I_NET_ERR_CLIENT
], true);
416 //enet_host_destroy(NetMHost);
421 res
:= enet_host_service(NetMHost
, @event
, 0);
424 enet_peer_reset(peer
);
432 if (event
.kind
= ENET_EVENT_TYPE_CONNECT
) then
434 NetHostConnected
:= true;
435 g_Console_Add(_lc
[I_NET_MSG
]+_lc
[I_NET_SLIST_CONN
]);
438 if (event
.kind
= ENET_EVENT_TYPE_RECEIVE
) then enet_packet_destroy(event
.packet
);
441 if not NetHostConnected
then NetHostConReqTime
:= GetTimerMS();
446 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR], True);
448 if NetMPeer <> nil then enet_peer_reset(NetMPeer);
449 if NetMHost <> nil then enet_host_destroy(NetMHost);
452 NetHostConnected := False;
453 NetHostConReqTime := 0;
454 NetUpdatePending := false;
458 NetHostConReqTime := GetTimerMS();
459 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_WCONN]);
465 //==========================================================================
467 // TMasterHost.processPendingConnection
469 // should be called only if host/peer is here
470 // returns `false` if not connected or dead
472 //==========================================================================
473 function TMasterHost
.processPendingConnection (timeout
: Integer=0): Boolean;
479 if not isAlive() then exit
;
480 // are we waiting for connection?
481 if (not NetHostConnected
) then
483 // check for connection event
484 cres
:= enet_host_service(NetMHost
, @event
, timeout
);
487 //TODO: reconnect here
488 updateSent
:= false; // do not send 'remove'
494 if (event
.kind
= ENET_EVENT_TYPE_CONNECT
) then
496 NetHostConnected
:= true;
497 if NetUpdatePending
then update(false);
498 g_Console_Add(_lc
[I_NET_MSG
]+_lc
[I_NET_SLIST_CONN
]);
502 else if (event
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
504 //TODO: reconnect here
505 updateSent
:= false; // do not send 'remove'
509 else if (event
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
511 enet_packet_destroy(event
.packet
);
514 // check for connection timeout
515 if (not NetHostConnected
) then
518 if (ct
< NetHostConReqTime
) or (ct
-NetHostConReqTime
>= 3000) then
520 // do not spam with error messages, it looks like the master is down
521 //g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR], True);
531 //==========================================================================
533 // TMasterHost.writeInfo
535 //==========================================================================
536 class procedure TMasterHost
.writeInfo (var msg
: TMsg
);
540 wad
:= g_ExtractWadNameNoPath(gMapInfo
.Map
);
541 map
:= g_ExtractFileName(gMapInfo
.Map
);
543 msg
.Write(NetServerName
);
545 msg
.Write(wad
+':/'+map
);
546 msg
.Write(gGameSettings
.GameMode
);
548 msg
.Write(Byte(NetClientCount
));
550 msg
.Write(NetMaxClients
);
552 msg
.Write(Byte(NET_PROTOCOL_VER
));
553 msg
.Write(Byte(NetPassword
<> ''));
557 //==========================================================================
559 // TMasterHost.update
561 //==========================================================================
562 procedure TMasterHost
.update (immediateSend
: Boolean=true);
566 if not processPendingConnection() then
568 NetUpdatePending
:= isConnecting();
572 NetUpdatePending
:= false;
575 netmsg
.Write(Byte(NET_MMSG_UPD
));
576 netmsg
.Write(NetAddr
.port
);
580 pkt
:= enet_packet_create(netmsg
.Data
, netmsg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
581 if assigned(pkt
) then
583 enet_peer_send(peer
, NET_MCHAN_UPD
, pkt
);
584 if (immediateSend
) then enet_host_flush(NetMHost
);
591 //==========================================================================
593 // TMasterHost.remove
595 //==========================================================================
596 procedure TMasterHost
.remove ();
600 if not processPendingConnection() then exit
;
603 netmsg
.Write(Byte(NET_MMSG_DEL
));
604 netmsg
.Write(NetAddr
.port
);
606 pkt
:= enet_packet_create(netmsg
.Data
, netmsg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
607 if assigned(pkt
) then
609 enet_peer_send(peer
, NET_MCHAN_MAIN
, pkt
);
610 enet_host_flush(NetMHost
);
617 //==========================================================================
619 // TMasterHost.waitForConnection
621 // returns `false` if connection failed
623 //==========================================================================
624 function TMasterHost
.waitForConnection (): Boolean;
627 if not result
then exit
;
628 while isAlive() and isConnecting() do
630 if not processPendingConnection(300) then break
;
632 if not isConnected() then
634 updateSent
:= false; // do not send 'remove'
641 //==========================================================================
643 // TMasterHost.service
645 // call as often as you want, the object will do the rest
646 // but try to call this at least once in 100 msecs
648 // returns `true` if we got a packet (it won't be parsed to TMsg)
650 //==========================================================================
651 function TMasterHost
.service (timeout
: Integer=0): Boolean;
657 if not isAlive() then exit
;
658 if not processPendingConnection() then exit
;
661 if (ct
< lastUpdateTime
) or (ct
-lastUpdateTime
>= 1000*60) then
663 lastUpdateTime
:= ct
;
669 hres
:= enet_host_service(NetMHost
, @event
, timeout
);
672 //TODO: reconnect here
673 updateSent
:= false; // do not send 'remove'
677 if (hres
= 0) then break
;
678 if (event
.kind
= ENET_EVENT_TYPE_CONNECT
) then
680 NetHostConnected
:= true;
681 if NetUpdatePending
then update(false);
682 g_Console_Add(_lc
[I_NET_MSG
]+_lc
[I_NET_SLIST_CONN
]);
684 else if (event
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
686 //TODO: reconnect here
687 g_Console_Add(_lc
[I_NET_MSG
]+_lc
[I_NET_SLIST_LOST
], True);
688 updateSent
:= false; // do not send 'remove'
692 else if (event
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
694 //enet_packet_destroy(event.packet);
695 //if (timeout <> 0) then break;
703 //==========================================================================
705 // TMasterHost.clearPacket
707 //==========================================================================
708 procedure TMasterHost
.clearPacket ();
710 if (event
.packet
<> nil) then
712 enet_packet_destroy(event
.packet
);
718 //**************************************************************************
722 //**************************************************************************
724 procedure g_Net_Slist_Set (IP
: string; Port
: Word);
726 if (length(mlist
) = 0) then
729 mlist
[0].Create(ip
+':'+IntToStr(Port
));
733 mlist
[0].setAddress(ip
+':'+IntToStr(Port
));
735 e_LogWritefln('Masterserver address set to %s:%u', [IP
, Port
], TMsgType
.Notify
);
739 enet_address_set_host(@NetSlistAddr, PChar(Addr(IP[1])));
740 NetSlistAddr.Port := Port;
741 e_WriteLog('Masterserver address set to ' + IP + ':' + IntToStr(Port), TMsgType.Notify);
747 //**************************************************************************
751 //**************************************************************************
752 procedure g_Net_Slist_Pulse ();
757 //**************************************************************************
759 // gui and server list
761 //**************************************************************************
763 //==========================================================================
767 //==========================================================================
768 procedure PingServer (var S
: TNetServer
; Sock
: ENetSocket
);
771 Ping
: array [0..9] of Byte;
774 ClTime
:= GetTimerMS();
776 Buf
.data
:= Addr(Ping
[0]);
777 Buf
.dataLength
:= 2+8;
781 Int64(Addr(Ping
[2])^) := ClTime
;
783 enet_socket_send(Sock
, Addr(S
.PingAddr
), @Buf
, 1);
787 //==========================================================================
791 //==========================================================================
792 procedure PingBcast (Sock
: ENetSocket
);
796 S
.IP
:= '255.255.255.255';
797 S
.Port
:= NET_PING_PORT
;
798 enet_address_set_host(Addr(S
.PingAddr
), PChar(Addr(S
.IP
[1])));
800 S
.PingAddr
.port
:= S
.Port
;
805 //==========================================================================
809 //==========================================================================
810 function g_Net_Slist_Fetch (var SL
: TNetServerList
): Boolean;
824 procedure ProcessLocal ();
827 SetLength(SL
, I
+ 1);
830 IP
:= DecodeIPV4(SvAddr
.host
);
831 Port
:= InMsg
.ReadWord();
832 Ping
:= InMsg
.ReadInt64();
833 Ping
:= GetTimerMS() - Ping
;
834 Name
:= InMsg
.ReadString();
835 Map
:= InMsg
.ReadString();
836 GameMode
:= InMsg
.ReadByte();
837 Players
:= InMsg
.ReadByte();
838 MaxPlayers
:= InMsg
.ReadByte();
839 Protocol
:= InMsg
.ReadByte();
840 Password
:= InMsg
.ReadByte() = 1;
841 LocalPl
:= InMsg
.ReadByte();
842 Bots
:= InMsg
.ReadWord();
846 procedure CheckLocalServers ();
850 Sock
:= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
);
851 if Sock
= ENET_SOCKET_NULL
then Exit
;
852 enet_socket_set_option(Sock
, ENET_SOCKOPT_NONBLOCK
, 1);
853 enet_socket_set_option(Sock
, ENET_SOCKOPT_BROADCAST
, 1);
858 InMsg
.Alloc(NET_BUFSIZE
);
859 Buf
.data
:= InMsg
.Data
;
860 Buf
.dataLength
:= InMsg
.MaxSize
;
861 while GetTimerMS() - T
<= 500 do
865 RX
:= enet_socket_receive(Sock
, @SvAddr
, @Buf
, 1);
866 if RX
<= 0 then continue
;
869 InMsg
.BeginReading();
871 if InMsg
.ReadChar() <> 'D' then continue
;
872 if InMsg
.ReadChar() <> 'F' then continue
;
878 enet_socket_destroy(Sock
);
880 if Length(SL
) = 0 then SL
:= nil;
887 if (length(mlist
) > 0) and (mlist
[0].isAlive()) then
893 if (length(mlist
) = 0) or (not mlist
[0].connect()) then
899 if not mlist
[0].waitForConnection() then
905 e_WriteLog('Fetching serverlist...', TMsgType
.Notify
);
906 g_Console_Add(_lc
[I_NET_MSG
] + _lc
[I_NET_SLIST_FETCH
]);
909 NetOut
.Write(Byte(NET_MMSG_GET
));
911 // TODO: what should we identify the build with?
912 MyVer
:= GAME_VERSION
;
915 P
:= enet_packet_create(NetOut
.Data
, NetOut
.CurSize
, Cardinal(ENET_PACKET_FLAG_RELIABLE
));
916 enet_peer_send(mlist
[0].peer
, NET_MCHAN_MAIN
, P
);
917 enet_host_flush(NetMHost
);
919 while mlist
[0].isAlive() do
921 if not mlist
[0].service(5000) then continue
;
922 if not InMsg
.Init(mlist
[0].event
.packet
^.data
, mlist
[0].event
.packet
^.dataLength
, True) then
924 mlist
[0].clearPacket();
928 MID
:= InMsg
.ReadByte();
930 if (MID
<> NET_MMSG_GET
) then
932 mlist
[0].clearPacket();
936 Cnt
:= InMsg
.ReadByte();
937 g_Console_Add(_lc
[I_NET_MSG
]+Format(_lc
[I_NET_SLIST_RETRIEVED
], [Cnt
]), True);
943 for I
:= 0 to Cnt
-1 do
946 SL
[I
].IP
:= InMsg
.ReadString();
947 SL
[I
].Port
:= InMsg
.ReadWord();
948 SL
[I
].Name
:= InMsg
.ReadString();
949 SL
[I
].Map
:= InMsg
.ReadString();
950 SL
[I
].GameMode
:= InMsg
.ReadByte();
951 SL
[I
].Players
:= InMsg
.ReadByte();
952 SL
[I
].MaxPlayers
:= InMsg
.ReadByte();
953 SL
[I
].Protocol
:= InMsg
.ReadByte();
954 SL
[I
].Password
:= InMsg
.ReadByte() = 1;
955 enet_address_set_host(Addr(SL
[I
].PingAddr
), PChar(Addr(SL
[I
].IP
[1])));
957 SL
[I
].PingAddr
.port
:= NET_PING_PORT
;
961 if InMsg
.ReadCount
< InMsg
.CurSize
then
963 // new master, supports version reports
964 Str
:= InMsg
.ReadString();
965 if (Str
<> MyVer
) then
968 g_Console_Add('!!! UpdVer = `' + Str
+ '`');
970 // even newer master, supports extra info
971 if (InMsg
.ReadCount
< InMsg
.CurSize
) then
973 slMOTD
:= b_Text_Format(InMsg
.ReadString());
974 Str
:= b_Text_Format(InMsg
.ReadString());
975 // check if the message has updated and the user has to read it again
976 if slUrgent
<> Str
then slReadUrgent
:= False;
981 mlist
[0].clearPacket();
986 mlist
[0].disconnect(false);
989 if Length(SL
) = 0 then
995 Sock
:= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
);
996 if Sock
= ENET_SOCKET_NULL
then Exit
;
997 enet_socket_set_option(Sock
, ENET_SOCKOPT_NONBLOCK
, 1);
999 for I
:= Low(SL
) to High(SL
) do PingServer(SL
[I
], Sock
);
1001 enet_socket_set_option(Sock
, ENET_SOCKOPT_BROADCAST
, 1);
1006 InMsg
.Alloc(NET_BUFSIZE
);
1007 Buf
.data
:= InMsg
.Data
;
1008 Buf
.dataLength
:= InMsg
.MaxSize
;
1010 while GetTimerMS() - T
<= 500 do
1014 RX
:= enet_socket_receive(Sock
, @SvAddr
, @Buf
, 1);
1015 if RX
<= 0 then continue
;
1016 InMsg
.CurSize
:= RX
;
1018 InMsg
.BeginReading();
1020 if InMsg
.ReadChar() <> 'D' then continue
;
1021 if InMsg
.ReadChar() <> 'F' then continue
;
1024 for I
:= Low(SL
) to High(SL
) do
1025 if (SL
[I
].PingAddr
.host
= SvAddr
.host
) and
1026 (SL
[I
].PingAddr
.port
= SvAddr
.port
) then
1030 Port
:= InMsg
.ReadWord();
1031 Ping
:= InMsg
.ReadInt64();
1032 Ping
:= GetTimerMS() - Ping
;
1033 Name
:= InMsg
.ReadString();
1034 Map
:= InMsg
.ReadString();
1035 GameMode
:= InMsg
.ReadByte();
1036 Players
:= InMsg
.ReadByte();
1037 MaxPlayers
:= InMsg
.ReadByte();
1038 Protocol
:= InMsg
.ReadByte();
1039 Password
:= InMsg
.ReadByte() = 1;
1040 LocalPl
:= InMsg
.ReadByte();
1041 Bots
:= InMsg
.ReadWord();
1052 enet_socket_destroy(Sock
);
1056 //==========================================================================
1058 // GetServerFromTable
1060 //==========================================================================
1061 function GetServerFromTable (Index
: Integer; SL
: TNetServerList
; ST
: TNetServerTable
): TNetServer
;
1064 Result
.Protocol
:= 0;
1069 Result
.Players
:= 0;
1070 Result
.MaxPlayers
:= 0;
1071 Result
.LocalPl
:= 0;
1074 Result
.GameMode
:= 0;
1075 Result
.Password
:= false;
1076 FillChar(Result
.PingAddr
, SizeOf(ENetAddress
), 0);
1079 if (Index
< 0) or (Index
>= Length(ST
)) then
1081 Result
:= SL
[ST
[Index
].Indices
[ST
[Index
].Current
]];
1085 //==========================================================================
1087 // g_Serverlist_Draw
1089 //==========================================================================
1090 procedure g_Serverlist_Draw (var SL
: TNetServerList
; var ST
: TNetServerTable
);
1093 sy
, i
, y
, mw
, mx
, l
, motdh
: Integer;
1103 e_CharFont_GetSize(gMenuFont
, _lc
[I_NET_SLIST
], ww
, hh
);
1104 e_CharFont_Print(gMenuFont
, (gScreenWidth
div 2) - (ww
div 2), 16, _lc
[I_NET_SLIST
]);
1106 e_TextureFontGetSize(gStdFont
, cw
, ch
);
1108 ip
:= _lc
[I_NET_SLIST_HELP
];
1109 mw
:= (Length(ip
) * cw
) div 2;
1111 motdh
:= gScreenHeight
- 49 - ch
* b_Text_LineCount(slMOTD
);
1113 e_DrawFillQuad(16, 64, gScreenWidth
-16, motdh
, 64, 64, 64, 110);
1114 e_DrawQuad(16, 64, gScreenWidth
-16, motdh
, 255, 127, 0);
1116 e_TextureFontPrintEx(gScreenWidth
div 2 - mw
, gScreenHeight
-24, ip
, gStdFont
, 225, 225, 225, 1);
1119 if slMOTD
<> '' then
1121 e_DrawFillQuad(16, motdh
, gScreenWidth
-16, gScreenHeight
-44, 64, 64, 64, 110);
1122 e_DrawQuad(16, motdh
, gScreenWidth
-16, gScreenHeight
-44, 255, 127, 0);
1123 e_TextureFontPrintFmt(20, motdh
+ 3, slMOTD
, gStdFont
, False, True);
1127 if not slReadUrgent
and (slUrgent
<> '') then
1129 e_DrawFillQuad(17, 65, gScreenWidth
-17, motdh
-1, 64, 64, 64, 128);
1130 e_DrawFillQuad(gScreenWidth
div 2 - 256, gScreenHeight
div 2 - 60,
1131 gScreenWidth
div 2 + 256, gScreenHeight
div 2 + 60, 64, 64, 64, 128);
1132 e_DrawQuad(gScreenWidth
div 2 - 256, gScreenHeight
div 2 - 60,
1133 gScreenWidth
div 2 + 256, gScreenHeight
div 2 + 60, 255, 127, 0);
1134 e_DrawLine(1, gScreenWidth
div 2 - 256, gScreenHeight
div 2 - 40,
1135 gScreenWidth
div 2 + 256, gScreenHeight
div 2 - 40, 255, 127, 0);
1136 l
:= Length(_lc
[I_NET_SLIST_URGENT
]) div 2;
1137 e_TextureFontPrint(gScreenWidth
div 2 - cw
* l
, gScreenHeight
div 2 - 58,
1138 _lc
[I_NET_SLIST_URGENT
], gStdFont
);
1139 l
:= Length(slUrgent
) div 2;
1140 e_TextureFontPrintFmt(gScreenWidth
div 2 - 253, gScreenHeight
div 2 - 38,
1141 slUrgent
, gStdFont
, False, True);
1142 l
:= Length(_lc
[I_NET_SLIST_URGENT_CONT
]) div 2;
1143 e_TextureFontPrint(gScreenWidth
div 2 - cw
* l
, gScreenHeight
div 2 + 41,
1144 _lc
[I_NET_SLIST_URGENT_CONT
], gStdFont
);
1145 e_DrawLine(1, gScreenWidth
div 2 - 256, gScreenHeight
div 2 + 40,
1146 gScreenWidth
div 2 + 256, gScreenHeight
div 2 + 40, 255, 127, 0);
1152 l
:= Length(slWaitStr
) div 2;
1153 e_DrawFillQuad(17, 65, gScreenWidth
-17, motdh
-1, 64, 64, 64, 128);
1154 e_DrawQuad(gScreenWidth
div 2 - 192, gScreenHeight
div 2 - 10,
1155 gScreenWidth
div 2 + 192, gScreenHeight
div 2 + 11, 255, 127, 0);
1156 e_TextureFontPrint(gScreenWidth
div 2 - cw
* l
, gScreenHeight
div 2 - ch
div 2,
1157 slWaitStr
, gStdFont
);
1162 if (slSelection
< Length(ST
)) then
1165 sy
:= y
+ 42 * I
- 4;
1166 Srv
:= GetServerFromTable(I
, SL
, ST
);
1167 ip
:= _lc
[I_NET_ADDRESS
] + ' ' + Srv
.IP
+ ':' + IntToStr(Srv
.Port
);
1168 if Srv
.Password
then
1169 ip
:= ip
+ ' ' + _lc
[I_NET_SERVER_PASSWORD
] + ' ' + _lc
[I_MENU_YES
]
1171 ip
:= ip
+ ' ' + _lc
[I_NET_SERVER_PASSWORD
] + ' ' + _lc
[I_MENU_NO
];
1173 if Length(ST
) > 0 then
1176 mw
:= (gScreenWidth
- 188);
1179 e_DrawFillQuad(16 + 1, sy
, gScreenWidth
- 16 - 1, sy
+ 40, 64, 64, 64, 0);
1180 e_DrawLine(1, 16 + 1, sy
, gScreenWidth
- 16 - 1, sy
, 205, 205, 205);
1181 e_DrawLine(1, 16 + 1, sy
+ 41, gScreenWidth
- 16 - 1, sy
+ 41, 255, 255, 255);
1183 e_DrawLine(1, 16, 85, gScreenWidth
- 16, 85, 255, 127, 0);
1184 e_DrawLine(1, 16, motdh
-20, gScreenWidth
-16, motdh
-20, 255, 127, 0);
1186 e_DrawLine(1, mx
- 70, 64, mx
- 70, motdh
, 255, 127, 0);
1187 e_DrawLine(1, mx
, 64, mx
, motdh
-20, 255, 127, 0);
1188 e_DrawLine(1, mx
+ 52, 64, mx
+ 52, motdh
-20, 255, 127, 0);
1189 e_DrawLine(1, mx
+ 104, 64, mx
+ 104, motdh
-20, 255, 127, 0);
1191 e_TextureFontPrintEx(18, 68, 'NAME/MAP', gStdFont
, 255, 127, 0, 1);
1192 e_TextureFontPrintEx(mx
- 68, 68, 'PING', gStdFont
, 255, 127, 0, 1);
1193 e_TextureFontPrintEx(mx
+ 2, 68, 'MODE', gStdFont
, 255, 127, 0, 1);
1194 e_TextureFontPrintEx(mx
+ 54, 68, 'PLRS', gStdFont
, 255, 127, 0, 1);
1195 e_TextureFontPrintEx(mx
+ 106, 68, 'VER', gStdFont
, 255, 127, 0, 1);
1198 for I
:= 0 to High(ST
) do
1200 Srv
:= GetServerFromTable(I
, SL
, ST
);
1202 e_TextureFontPrintEx(18, y
, Srv
.Name
, gStdFont
, 255, 255, 255, 1);
1203 e_TextureFontPrintEx(18, y
+ 16, Srv
.Map
, gStdFont
, 210, 210, 210, 1);
1205 // Ping and similar count
1206 if (Srv
.Ping
< 0) or (Srv
.Ping
> 999) then
1207 e_TextureFontPrintEx(mx
- 68, y
, _lc
[I_NET_SLIST_NO_ACCESS
], gStdFont
, 255, 0, 0, 1)
1209 if Srv
.Ping
= 0 then
1210 e_TextureFontPrintEx(mx
- 68, y
, '<1' + _lc
[I_NET_SLIST_PING_MS
], gStdFont
, 255, 255, 255, 1)
1212 e_TextureFontPrintEx(mx
- 68, y
, IntToStr(Srv
.Ping
) + _lc
[I_NET_SLIST_PING_MS
], gStdFont
, 255, 255, 255, 1);
1214 if Length(ST
[I
].Indices
) > 1 then
1215 e_TextureFontPrintEx(mx
- 68, y
+ 16, '< ' + IntToStr(Length(ST
[I
].Indices
)) + ' >', gStdFont
, 210, 210, 210, 1);
1218 e_TextureFontPrintEx(mx
+ 2, y
, g_Game_ModeToText(Srv
.GameMode
), gStdFont
, 255, 255, 255, 1);
1221 e_TextureFontPrintEx(mx
+ 54, y
, IntToStr(Srv
.Players
) + '/' + IntToStr(Srv
.MaxPlayers
), gStdFont
, 255, 255, 255, 1);
1222 e_TextureFontPrintEx(mx
+ 54, y
+ 16, IntToStr(Srv
.LocalPl
) + '+' + IntToStr(Srv
.Bots
), gStdFont
, 210, 210, 210, 1);
1225 e_TextureFontPrintEx(mx
+ 106, y
, IntToStr(Srv
.Protocol
), gStdFont
, 255, 255, 255, 1);
1230 e_TextureFontPrintEx(20, motdh
-20+3, ip
, gStdFont
, 205, 205, 205, 1);
1231 ip
:= IntToStr(Length(ST
)) + _lc
[I_NET_SLIST_SERVERS
];
1232 e_TextureFontPrintEx(gScreenWidth
- 48 - (Length(ip
) + 1)*cw
,
1233 motdh
-20+3, ip
, gStdFont
, 205, 205, 205, 1);
1237 //==========================================================================
1239 // g_Serverlist_GenerateTable
1241 //==========================================================================
1242 procedure g_Serverlist_GenerateTable (SL
: TNetServerList
; var ST
: TNetServerTable
);
1246 function FindServerInTable(Name
: string): Integer;
1253 for i
:= Low(ST
) to High(ST
) do
1255 if Length(ST
[i
].Indices
) = 0 then
1257 if SL
[ST
[i
].Indices
[0]].Name
= Name
then
1264 function ComparePing(i1
, i2
: Integer): Boolean;
1270 if (p1
< 0) then p1
:= 999;
1271 if (p2
< 0) then p2
:= 999;
1274 procedure SortIndices(var ind
: Array of Integer);
1279 for I
:= High(ind
) downto Low(ind
) do
1280 for J
:= Low(ind
) to High(ind
) - 1 do
1281 if ComparePing(ind
[j
], ind
[j
+1]) then
1288 procedure SortRows();
1293 for I
:= High(ST
) downto Low(ST
) do
1294 for J
:= Low(ST
) to High(ST
) - 1 do
1295 if ComparePing(ST
[j
].Indices
[0], ST
[j
+1].Indices
[0]) then
1306 for i
:= Low(SL
) to High(SL
) do
1308 j
:= FindServerInTable(SL
[i
].Name
);
1312 SetLength(ST
, j
+ 1);
1314 SetLength(ST
[j
].Indices
, 1);
1315 ST
[j
].Indices
[0] := i
;
1319 SetLength(ST
[j
].Indices
, Length(ST
[j
].Indices
) + 1);
1320 ST
[j
].Indices
[High(ST
[j
].Indices
)] := i
;
1324 for i
:= Low(ST
) to High(ST
) do
1325 SortIndices(ST
[i
].Indices
);
1331 //==========================================================================
1333 // g_Serverlist_Control
1335 //==========================================================================
1336 procedure g_Serverlist_Control (var SL
: TNetServerList
; var ST
: TNetServerTable
);
1341 if gConsoleShow
or gChatShow
then
1344 qm
:= sys_HandleInput(); // this updates kbd
1346 if qm
or e_KeyPressed(IK_ESCAPE
) or e_KeyPressed(VK_ESCAPE
) or
1347 e_KeyPressed(JOY0_JUMP
) or e_KeyPressed(JOY1_JUMP
) or
1348 e_KeyPressed(JOY2_JUMP
) or e_KeyPressed(JOY3_JUMP
) then
1352 gState
:= STATE_MENU
;
1353 g_GUI_ShowWindow('MainMenu');
1354 g_GUI_ShowWindow('NetGameMenu');
1355 g_GUI_ShowWindow('NetClientMenu');
1356 g_Sound_PlayEx(WINDOW_CLOSESOUND
);
1360 // if there's a message on the screen,
1361 if not slReadUrgent
and (slUrgent
<> '') then
1363 if e_KeyPressed(IK_RETURN
) or e_KeyPressed(IK_KPRETURN
) or e_KeyPressed(VK_FIRE
) or e_KeyPressed(VK_OPEN
) or
1364 e_KeyPressed(JOY0_ATTACK
) or e_KeyPressed(JOY1_ATTACK
) or e_KeyPressed(JOY2_ATTACK
) or e_KeyPressed(JOY3_ATTACK
) then
1365 slReadUrgent
:= True;
1369 if e_KeyPressed(IK_SPACE
) or e_KeyPressed(VK_JUMP
) or
1370 e_KeyPressed(JOY0_ACTIVATE
) or e_KeyPressed(JOY1_ACTIVATE
) or e_KeyPressed(JOY2_ACTIVATE
) or e_KeyPressed(JOY3_ACTIVATE
) then
1372 if not slFetched
then
1374 slWaitStr
:= _lc
[I_NET_SLIST_WAIT
];
1379 if g_Net_Slist_Fetch(SL
) then
1382 slWaitStr
:= _lc
[I_NET_SLIST_NOSERVERS
];
1386 slWaitStr
:= _lc
[I_NET_SLIST_ERROR
];
1389 g_Serverlist_GenerateTable(SL
, ST
);
1395 if SL
= nil then Exit
;
1397 if e_KeyPressed(IK_RETURN
) or e_KeyPressed(IK_KPRETURN
) or e_KeyPressed(VK_FIRE
) or e_KeyPressed(VK_OPEN
) or
1398 e_KeyPressed(JOY0_ATTACK
) or e_KeyPressed(JOY1_ATTACK
) or e_KeyPressed(JOY2_ATTACK
) or e_KeyPressed(JOY3_ATTACK
) then
1400 if not slReturnPressed
then
1402 Srv
:= GetServerFromTable(slSelection
, SL
, ST
);
1403 if Srv
.Password
then
1406 PromptPort
:= Srv
.Port
;
1407 gState
:= STATE_MENU
;
1408 g_GUI_ShowWindow('ClientPasswordMenu');
1411 slReturnPressed
:= True;
1415 g_Game_StartClient(Srv
.IP
, Srv
.Port
, '');
1418 slReturnPressed
:= True;
1423 slReturnPressed
:= False;
1425 if e_KeyPressed(IK_DOWN
) or e_KeyPressed(IK_KPDOWN
) or e_KeyPressed(VK_DOWN
) or
1426 e_KeyPressed(JOY0_DOWN
) or e_KeyPressed(JOY1_DOWN
) or e_KeyPressed(JOY2_DOWN
) or e_KeyPressed(JOY3_DOWN
) then
1428 if not slDirPressed
then
1431 if slSelection
> High(ST
) then slSelection
:= 0;
1432 slDirPressed
:= True;
1436 if e_KeyPressed(IK_UP
) or e_KeyPressed(IK_KPUP
) or e_KeyPressed(VK_UP
) or
1437 e_KeyPressed(JOY0_UP
) or e_KeyPressed(JOY1_UP
) or e_KeyPressed(JOY2_UP
) or e_KeyPressed(JOY3_UP
) then
1439 if not slDirPressed
then
1441 if slSelection
= 0 then slSelection
:= Length(ST
);
1444 slDirPressed
:= True;
1448 if e_KeyPressed(IK_RIGHT
) or e_KeyPressed(IK_KPRIGHT
) or e_KeyPressed(VK_RIGHT
) or
1449 e_KeyPressed(JOY0_RIGHT
) or e_KeyPressed(JOY1_RIGHT
) or e_KeyPressed(JOY2_RIGHT
) or e_KeyPressed(JOY3_RIGHT
) then
1451 if not slDirPressed
then
1453 Inc(ST
[slSelection
].Current
);
1454 if ST
[slSelection
].Current
> High(ST
[slSelection
].Indices
) then ST
[slSelection
].Current
:= 0;
1455 slDirPressed
:= True;
1459 if e_KeyPressed(IK_LEFT
) or e_KeyPressed(IK_KPLEFT
) or e_KeyPressed(VK_LEFT
) or
1460 e_KeyPressed(JOY0_LEFT
) or e_KeyPressed(JOY1_LEFT
) or e_KeyPressed(JOY2_LEFT
) or e_KeyPressed(JOY3_LEFT
) then
1462 if not slDirPressed
then
1464 if ST
[slSelection
].Current
= 0 then ST
[slSelection
].Current
:= Length(ST
[slSelection
].Indices
);
1465 Dec(ST
[slSelection
].Current
);
1467 slDirPressed
:= True;
1471 if (not e_KeyPressed(IK_DOWN
)) and
1472 (not e_KeyPressed(IK_UP
)) and
1473 (not e_KeyPressed(IK_RIGHT
)) and
1474 (not e_KeyPressed(IK_LEFT
)) and
1475 (not e_KeyPressed(IK_KPDOWN
)) and
1476 (not e_KeyPressed(IK_KPUP
)) and
1477 (not e_KeyPressed(IK_KPRIGHT
)) and
1478 (not e_KeyPressed(IK_KPLEFT
)) and
1479 (not e_KeyPressed(VK_DOWN
)) and
1480 (not e_KeyPressed(VK_UP
)) and
1481 (not e_KeyPressed(VK_RIGHT
)) and
1482 (not e_KeyPressed(VK_LEFT
)) and
1483 (not e_KeyPressed(JOY0_UP
)) and (not e_KeyPressed(JOY1_UP
)) and (not e_KeyPressed(JOY2_UP
)) and (not e_KeyPressed(JOY3_UP
)) and
1484 (not e_KeyPressed(JOY0_DOWN
)) and (not e_KeyPressed(JOY1_DOWN
)) and (not e_KeyPressed(JOY2_DOWN
)) and (not e_KeyPressed(JOY3_DOWN
)) and
1485 (not e_KeyPressed(JOY0_LEFT
)) and (not e_KeyPressed(JOY1_LEFT
)) and (not e_KeyPressed(JOY2_LEFT
)) and (not e_KeyPressed(JOY3_LEFT
)) and
1486 (not e_KeyPressed(JOY0_RIGHT
)) and (not e_KeyPressed(JOY1_RIGHT
)) and (not e_KeyPressed(JOY2_RIGHT
)) and (not e_KeyPressed(JOY3_RIGHT
))
1488 slDirPressed
:= False;