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
= nil;
126 slTable
: TNetServerTable
= nil;
127 slWaitStr
: AnsiString = '';
128 slReturnPressed
: Boolean = True;
130 slMOTD
: AnsiString = '';
131 slUrgent
: AnsiString = '';
133 NMASTER_FORCE_UPDATE_TIMEOUT
: Integer = 0; // fuck you, fpc, and your idiotic "diagnostics"
136 procedure g_Net_Slist_Set (list
: AnsiString);
137 function g_Net_Slist_Fetch (var SL
: TNetServerList
): Boolean;
139 // make this server private
140 procedure g_Net_Slist_Private ();
141 // make this server public
142 procedure g_Net_Slist_Public ();
144 // called while the server is running
145 procedure g_Net_Slist_ServerUpdate ();
146 // called when the server is started
147 procedure g_Net_Slist_ServerStarted ();
148 // called when the server is stopped
149 procedure g_Net_Slist_ServerClosed ();
151 // called when new netword player comes
152 procedure g_Net_Slist_ServerPlayerComes ();
153 // called when new netword player comes
154 procedure g_Net_Slist_ServerPlayerLeaves ();
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_Control (var SL
: TNetServerList
; var ST
: TNetServerTable
);
168 function GetServerFromTable (Index
: Integer; SL
: TNetServerList
; ST
: TNetServerTable
): TNetServer
;
170 function GetTimerMS (): Int64;
172 var (* private state *)
173 slSelection
: Byte = 0;
174 slReadUrgent
: Boolean = False;
180 g_gui
, g_menu
, r_render
, g_system
,
182 e_input
, e_log
, g_net
, g_console
,
183 g_map
, g_game
, g_sound
, g_options
, g_language
, g_basic
,
184 wadreader
, utils
, hashtable
;
187 // ////////////////////////////////////////////////////////////////////////// //
189 NetMHost
: pENetHost
= nil;
190 NetMEvent
: ENetEvent
;
191 mlist
: array of TMasterHost
= nil;
193 slFetched
: Boolean = False;
194 slDirPressed
: Boolean = False;
196 reportsEnabled
: Boolean = true;
199 //==========================================================================
203 //==========================================================================
204 function GetTimerMS (): Int64;
206 Result
:= GetTickCount64() {div 1000};
210 //==========================================================================
214 //==========================================================================
215 function findByPeer (peer
: pENetPeer
): Integer;
219 for f
:= 0 to High(mlist
) do if (mlist
[f
].peer
= peer
) then begin result
:= f
; exit
; end;
224 //==========================================================================
228 //==========================================================================
229 procedure g_Net_Slist_ShutdownAll ();
231 f
, sres
, idx
: Integer;
233 activeCount
: Integer = 0;
235 if (NetMHost
= nil) then exit
;
236 for f
:= 0 to High(mlist
) do
238 if (mlist
[f
].isAlive()) then
241 if (mlist
[f
].isConnected() and mlist
[f
].updateSent
) then
243 writeln('unregistering from [', mlist
[f
].hostName
, ']');
246 //mlist[f].disconnect(false);
247 enet_peer_disconnect_later(mlist
[f
].peer
, 0);
250 if (activeCount
= 0) then exit
;
252 while (activeCount
> 0) do
255 if (ct
< stt
) or (ct
-stt
>= 1500) then break
;
257 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
258 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
259 // thank you, enet. let's ignore failures altogether then.
260 sres
:= enet_host_service(NetMHost
, @NetMEvent
, 100);
261 // if (sres < 0) then break;
262 if (sres
<= 0) then continue
;
264 idx
:= findByPeer(NetMEvent
.peer
);
267 if (NetMEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then enet_packet_destroy(NetMEvent
.packet
);
271 if (NetMEvent
.kind
= ENET_EVENT_TYPE_CONNECT
) then
273 mlist
[idx
].connectedEvent();
274 //mlist[idx].disconnect(false);
275 enet_peer_disconnect(mlist
[f
].peer
, 0);
277 else if (NetMEvent
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
279 mlist
[idx
].disconnectedEvent();
282 else if (NetMEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
284 mlist
[idx
].receivedEvent(NetMEvent
.packet
);
285 enet_packet_destroy(NetMEvent
.packet
);
288 enet_host_destroy(NetMHost
);
293 //==========================================================================
297 //==========================================================================
298 procedure DisconnectAll (forced
: Boolean=false);
302 for f
:= 0 to High(mlist
) do
304 if (mlist
[f
].isAlive()) then mlist
[f
].disconnect(forced
);
309 //==========================================================================
313 //==========================================================================
314 procedure ConnectAll (sendUpdate
: Boolean);
318 // set flags; pulse will take care of the rest
319 for f
:= 0 to High(mlist
) do
322 mlist
[f
].lastDisconnectTime
:= 0;
326 mlist
[f
].NetUpdatePending
:= true;
327 mlist
[f
].lastUpdateTime
:= 0;
333 //==========================================================================
337 //==========================================================================
338 procedure UpdateAll (force
: Boolean);
342 // set flags; pulse will take care of the rest
343 for f
:= 0 to High(mlist
) do
345 if (not mlist
[f
].isAlive()) then continue
;
346 mlist
[f
].NetUpdatePending
:= true;
347 if (force
) then mlist
[f
].lastUpdateTime
:= 0;
352 //**************************************************************************
356 //**************************************************************************
358 //==========================================================================
360 // g_Net_Slist_Private
362 // make this server private
364 //==========================================================================
365 procedure g_Net_Slist_Private ();
368 reportsEnabled
:= false;
372 //==========================================================================
374 // g_Net_Slist_Public
376 // make this server public
378 //==========================================================================
379 procedure g_Net_Slist_Public ();
381 if (not reportsEnabled
) then
383 reportsEnabled
:= true;
389 //==========================================================================
391 // g_Net_Slist_ServerUpdate
393 // called while the server is running
395 //==========================================================================
396 procedure g_Net_Slist_ServerUpdate ();
402 // called when the server is started
403 procedure g_Net_Slist_ServerStarted ();
405 reportsEnabled
:= NetUseMaster
;
406 if reportsEnabled
and g_Game_IsServer() and g_Game_IsNet() then
408 writeln('*** server started; reporting to master...');
414 //==========================================================================
416 // g_Net_Slist_ServerClosed
418 // called when the server is stopped
420 //==========================================================================
421 procedure g_Net_Slist_ServerClosed ();
425 if reportsEnabled
then
427 reportsEnabled
:= false;
428 for f
:= 0 to High(mlist
) do
430 if (mlist
[f
].isConnected()) then mlist
[f
].remove();
437 //==========================================================================
439 // g_Net_Slist_ServerPlayerComes
441 // called when new netword player comes
443 //==========================================================================
444 procedure g_Net_Slist_ServerPlayerComes ();
450 //==========================================================================
452 // g_Net_Slist_ServerPlayerLeaves
454 // called when new netword player comes
456 //==========================================================================
457 procedure g_Net_Slist_ServerPlayerLeaves ();
463 //==========================================================================
465 // g_Net_Slist_ServerMapStarted
469 //==========================================================================
470 procedure g_Net_Slist_ServerMapStarted ();
476 //==========================================================================
478 // g_Net_Slist_ServerRenamed
480 // this server renamed (or password mode changed, or other params changed)
482 //==========================================================================
483 procedure g_Net_Slist_ServerRenamed ();
489 //**************************************************************************
493 //**************************************************************************
495 //==========================================================================
497 // TMasterHost.Create
499 //==========================================================================
500 constructor TMasterHost
.Create (var ea
: ENetAddress
);
503 NetHostConnected
:= false;
504 NetHostConReqTime
:= 0;
505 NetUpdatePending
:= false;
506 lastDisconnectTime
:= 0;
510 ZeroMemory(@enetAddr
, sizeof(enetAddr
));
511 SetLength(srvAnswer
, 0);
515 slReadUrgent
:= true;
518 netmsg
.Alloc(NET_BUFSIZE
);
523 //==========================================================================
527 //==========================================================================
528 procedure TMasterHost
.clear ();
530 updateSent
:= false; // do not send 'remove'
534 SetLength(srvAnswer
, 0);
538 slReadUrgent
:= true;
539 ZeroMemory(@enetAddr
, sizeof(enetAddr
));
543 //==========================================================================
545 // TMasterHost.setAddress
547 //==========================================================================
548 function TMasterHost
.setAddress (var ea
: ENetAddress
; hostStr
: AnsiString): Boolean;
551 SetLength(srvAnswer
, 0);
555 slReadUrgent
:= true;
556 updateSent
:= false; // do not send 'remove'
560 if (not g_Net_IsNetworkAvailable()) then exit
;
563 if (enetAddr
.host
= 0) or (enetAddr
.port
= 0) then exit
;
565 if (length(hostStr
) > 0) then hostName
:= hostStr
else hostName
:= IntToStr(enetAddr
.host
)+':'+IntToStr(ea
.port
);
571 //==========================================================================
573 // TMasterHost.isValid
575 //==========================================================================
576 function TMasterHost
.isValid (): Boolean;
578 result
:= (enetAddr
.host
<> 0) and (enetAddr
.port
<> 0);
582 //==========================================================================
584 // TMasterHost.isAlive
588 //==========================================================================
589 function TMasterHost
.isAlive (): Boolean;
591 result
:= (NetMHost
<> nil) and (peer
<> nil);
595 //==========================================================================
597 // TMasterHost.isConnecting
599 // is connection in progress?
601 //==========================================================================
602 function TMasterHost
.isConnecting (): Boolean;
604 result
:= isAlive() and (not NetHostConnected
) and (NetHostConReqTime
<> -1);
608 //==========================================================================
610 // TMasterHost.isConnected
612 //==========================================================================
613 function TMasterHost
.isConnected (): Boolean;
615 result
:= isAlive() and (NetHostConnected
) and (NetHostConReqTime
<> -1);
619 //==========================================================================
621 // TMasterHost.connectedEvent
623 //==========================================================================
624 procedure TMasterHost
.connectedEvent ();
626 if not isAlive() then exit
;
627 if NetHostConnected
then exit
;
628 NetHostConnected
:= true;
629 NetHostConReqTime
:= 0; // just in case
630 e_LogWritefln('connected to master at [%s]', [hostName
], TMsgType
.Notify
);
631 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_CONN], [mlist[f].hostName]));
635 //==========================================================================
637 // TMasterHost.disconnectedEvent
639 //==========================================================================
640 procedure TMasterHost
.disconnectedEvent ();
642 if not isAlive() then exit
;
643 e_LogWritefln('disconnected from master at [%s]', [hostName
], TMsgType
.Notify
);
645 //if (spamConsole) then g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_DISC], [hostName]));
649 //==========================================================================
651 // TMasterHost.receivedEvent
653 // `pkt` is never `nil`
655 //==========================================================================
656 procedure TMasterHost
.receivedEvent (pkt
: pENetPacket
);
664 e_LogWritefln('received packed from master at [%s]', [hostName
], TMsgType
.Notify
);
665 if not msg
.Init(pkt
^.data
, pkt
^.dataLength
, True) then exit
;
667 MID
:= msg
.ReadByte();
668 if (MID
<> NET_MMSG_GET
) then exit
;
669 e_LogWritefln('received list packet from master at [%s]', [hostName
], TMsgType
.Notify
);
670 SetLength(srvAnswer
, 0);
671 if (srvAnswered
> 0) then Inc(srvAnswered
);
674 slReadUrgent
:= true;
676 Cnt
:= msg
.ReadByte();
677 //g_Console_Add(_lc[I_NET_MSG]+Format(_lc[I_NET_SLIST_RETRIEVED], [Cnt, hostName]), True);
678 e_LogWritefln('got %u server(s) from master at [%s]', [Cnt
, hostName
], TMsgType
.Notify
);
681 SetLength(srvAnswer
, Cnt
);
682 for f
:= 0 to Cnt
-1 do
684 srvAnswer
[f
].Number
:= f
;
685 srvAnswer
[f
].IP
:= msg
.ReadString();
686 srvAnswer
[f
].Port
:= msg
.ReadWord();
687 srvAnswer
[f
].Name
:= msg
.ReadString();
688 srvAnswer
[f
].Map
:= msg
.ReadString();
689 srvAnswer
[f
].GameMode
:= msg
.ReadByte();
690 srvAnswer
[f
].Players
:= msg
.ReadByte();
691 srvAnswer
[f
].MaxPlayers
:= msg
.ReadByte();
692 srvAnswer
[f
].Protocol
:= msg
.ReadByte();
693 srvAnswer
[f
].Password
:= msg
.ReadByte() = 1;
694 enet_address_set_host(Addr(srvAnswer
[f
].PingAddr
), PChar(Addr(srvAnswer
[f
].IP
[1])));
695 srvAnswer
[f
].Ping
:= -1;
696 srvAnswer
[f
].PingAddr
.port
:= NET_PING_PORT
;
700 if (msg
.ReadCount
< msg
.CurSize
) then
702 // new master, supports version reports
703 s
:= msg
.ReadString();
704 if (s
<> {MyVer}GAME_VERSION
) then
707 g_Console_Add('!!! UpdVer = `'+s
+'`');
709 // even newer master, supports extra info
710 if (msg
.ReadCount
< msg
.CurSize
) then
712 slMOTD
:= b_Text_Format(msg
.ReadString());
713 if (slMOTD
<> '') then e_LogWritefln('got MOTD from master at [%s]: %s', [hostName
, slMOTD
], TMsgType
.Notify
);
714 s
:= b_Text_Format(msg
.ReadString());
715 // check if the message has updated and the user has to read it again
716 if (slUrgent
<> s
) then slReadUrgent
:= false;
718 if (s
<> '') then e_LogWritefln('got urgent from master at [%s]: %s', [hostName
, s
], TMsgType
.Notify
);
724 //==========================================================================
726 // TMasterHost.disconnect
728 //==========================================================================
729 procedure TMasterHost
.disconnect (forced
: Boolean);
733 lastDisconnectTime
:= GetTimerMS();
734 if forced
or (not NetHostConnected
) or (NetHostConReqTime
= -1) then
736 enet_peer_reset(peer
);
738 NetHostConReqTime
:= 0;
743 enet_peer_disconnect_later(peer
, 0);
744 // main pulse will take care of the rest
745 NetHostConReqTime
:= -1;
751 NetHostConReqTime
:= 0;
755 NetHostConnected
:= false;
756 NetUpdatePending
:= false;
758 //if (spamConsole) then g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_DISC], [hostName]));
762 //==========================================================================
764 // TMasterHost.connect
766 //==========================================================================
767 function TMasterHost
.connect (): Boolean;
770 if not isValid() then exit
;
771 if (NetHostConReqTime
= -1) then
774 if (NetHostConReqTime
= -1) then e_LogWritefln('ketmar broke master [%s] logic! (000)', [hostName
], TMsgType
.Notify
);
775 if (isAlive()) then e_LogWritefln('ketmar broke master [%s] logic! (001)', [hostName
], TMsgType
.Notify
);
779 if isAlive() then begin result
:= true; exit
; end;
782 lastDisconnectTime
:= GetTimerMS(); // why not?
783 SetLength(srvAnswer
, 0);
785 NetHostConnected
:= false;
786 NetHostConReqTime
:= 0;
787 NetUpdatePending
:= false;
792 peer
:= enet_host_connect(NetMHost
, @enetAddr
, NET_MCHANS
, 0);
795 g_Console_Add(_lc
[I_NET_MSG_ERROR
]+_lc
[I_NET_ERR_CLIENT
], true);
799 NetHostConReqTime
:= lastDisconnectTime
;
800 e_LogWritefln('connecting to master at [%s]', [hostName
], TMsgType
.Notify
);
804 //==========================================================================
806 // TMasterHost.writeInfo
808 //==========================================================================
809 class procedure TMasterHost
.writeInfo (var msg
: TMsg
);
811 wad
, map
: AnsiString;
813 wad
:= g_ExtractWadNameNoPath(gMapInfo
.Map
);
814 map
:= g_ExtractFileName(gMapInfo
.Map
);
816 msg
.Write(NetServerName
);
818 msg
.Write(wad
+':/'+map
);
819 msg
.Write(gGameSettings
.GameMode
);
821 msg
.Write(Byte(NetClientCount
));
823 msg
.Write(NetMaxClients
);
825 msg
.Write(Byte(NET_PROTOCOL_VER
));
826 msg
.Write(Byte(NetPassword
<> ''));
830 //==========================================================================
832 // TMasterHost.update
834 //==========================================================================
835 procedure TMasterHost
.update ();
839 if not isAlive() then exit
;
840 if not isConnected() then
842 NetUpdatePending
:= isConnecting();
848 if reportsEnabled
and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster
then
851 netmsg
.Write(Byte(NET_MMSG_UPD
));
852 netmsg
.Write(NetAddr
.port
);
853 //writeln(formatstrf('%08x', [NetAddr.host]), ' : ', NetAddr.host);
857 pkt
:= enet_packet_create(netmsg
.Data
, netmsg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
858 if assigned(pkt
) then
860 if (enet_peer_send(peer
, NET_MCHAN_UPD
, pkt
) = 0) then
862 e_LogWritefln('sent update to master at [%s]', [hostName
], TMsgType
.Notify
);
863 NetUpdatePending
:= false;
873 NetUpdatePending
:= false;
878 //==========================================================================
880 // TMasterHost.remove
882 //==========================================================================
883 procedure TMasterHost
.remove ();
887 NetUpdatePending
:= false;
890 if not isAlive() then exit
;
891 if not isConnected() then exit
;
895 netmsg
.Write(Byte(NET_MMSG_DEL
));
896 netmsg
.Write(NetAddr
.port
);
898 pkt
:= enet_packet_create(netmsg
.Data
, netmsg
.CurSize
, ENET_PACKET_FLAG_RELIABLE
);
899 if assigned(pkt
) then
901 enet_peer_send(peer
, NET_MCHAN_MAIN
, pkt
);
909 //==========================================================================
913 // this performs various scheduled tasks, if necessary
915 //==========================================================================
916 procedure TMasterHost
.pulse ();
921 if not isAlive() then exit
;
922 if (NetHostConReqTime
= -1) then exit
; // waiting for shutdown (disconnect in progress)
924 // process pending connection timeout
925 if (not NetHostConnected
) then
927 if (ct
< NetHostConReqTime
) or (ct
-NetHostConReqTime
>= 1000*NMASTER_TIMEOUT_CONNECT
) then
929 e_LogWritefln('failed to connect to master at [%s]', [hostName
], TMsgType
.Notify
);
930 // do not spam with error messages, it looks like the master is down
931 //g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR], True);
936 // send update, if necessary
937 if (NetUpdatePending
) then
939 mrate
:= NetMasterRate
;
940 if (mrate
< 10000) then mrate
:= 10000
941 else if (mrate
> 1000*60*10) then mrate
:= 1000*60*10;
942 if (NMASTER_FORCE_UPDATE_TIMEOUT
> 0) then mrate
:= NMASTER_FORCE_UPDATE_TIMEOUT
*1000;
943 if (lastUpdateTime
= 0) or (ct
< lastUpdateTime
) or (ct
-lastUpdateTime
>= mrate
) then
945 //e_LogWritefln('update timeout: %d', [Integer(mrate)], TMsgType.Notify);
946 lastUpdateTime
:= ct
;
953 //**************************************************************************
957 //**************************************************************************
959 THashStrDWord
= specialize THashBase
<AnsiString, LongWord, THashKeyStrAnsiCI
>;
962 knownHosts
: THashStrDWord
= nil;
965 //==========================================================================
969 //==========================================================================
970 function parseAddressPort (var ea
: ENetAddress
; hostandport
: AnsiString): Boolean;
973 hostName
: AnsiString;
977 if (not g_Net_IsNetworkAvailable()) then exit
;
979 hostandport
:= Trim(hostandport
);
980 if (length(hostandport
) = 0) then exit
;
982 hostName
:= hostandport
;
985 cp
:= Pos(':', hostandport
);
988 hostName
:= Trim(Copy(hostandport
, 1, cp
-1));
989 Delete(hostandport
, 1, cp
);
990 hostandport
:= Trim(hostandport
);
991 if (length(hostandport
) > 0) then
994 port
:= StrToInt(hostandport
);
1001 if (length(hostName
) = 0) then exit
;
1002 if (port
< 1) or (port
> 65535) then exit
;
1004 if not assigned(knownHosts
) then knownHosts
:= THashStrDWord
.Create();
1006 if knownHosts
.get(hostName
, ip
) then
1012 if (enet_address_set_host(@ea
, PChar(Addr(hostName
[1]))) <> 0) then
1014 knownHosts
.put(hostName
, 0);
1017 knownHosts
.put(hostName
, ea
.host
);
1024 //==========================================================================
1028 //==========================================================================
1029 procedure addMasterRecord (var ea
: ENetAddress
; sa
: AnsiString);
1035 for f
:= 0 to High(mlist
) do
1037 if (mlist
[f
].enetAddr
.host
= ea
.host
) and (mlist
[f
].enetAddr
.port
= ea
.port
) then
1039 mlist
[f
].justAdded
:= true;
1042 if (freeIdx
< 0) and (not mlist
[f
].isValid()) then freeIdx
:= f
;
1044 if (freeIdx
< 0) then
1046 freeIdx
:= length(mlist
);
1047 SetLength(mlist
, freeIdx
+1);
1048 mlist
[freeIdx
].Create(ea
);
1050 mlist
[freeIdx
].justAdded
:= true;
1051 mlist
[freeIdx
].setAddress(ea
, sa
);
1052 e_LogWritefln('added masterserver with address [%s]', [sa
], TMsgType
.Notify
);
1056 //==========================================================================
1060 //==========================================================================
1061 procedure g_Net_Slist_Set (list
: AnsiString);
1068 if (not g_Net_IsNetworkAvailable()) then exit
;
1070 for f
:= 0 to High(mlist
) do mlist
[f
].justAdded
:= false;
1073 //writeln('list=[', list, ']');
1074 while (length(list
) > 0) do
1076 pp
:= Pos(',', list
);
1077 if (pp
< 1) then pp
:= length(list
)+1;
1078 sa
:= Trim(Copy(list
, 1, pp
-1));
1079 Delete(list
, 1, pp
);
1080 //writeln(' sa=[', sa, ']');
1081 if (length(sa
) > 0) and parseAddressPort(ea
, sa
) then addMasterRecord(ea
, sa
);
1084 // remove unknown master servers
1086 for f
:= 0 to High(mlist
) do
1088 if (not mlist
[f
].justAdded
) then mlist
[f
].clear();
1089 if (mlist
[f
].isValid()) then
1091 if (dest
<> f
) then mlist
[dest
] := mlist
[f
];
1095 if (dest
<> length(mlist
)) then SetLength(mlist
, dest
);
1099 //**************************************************************************
1103 //**************************************************************************
1105 //==========================================================================
1107 // isMasterReportsEnabled
1109 //==========================================================================
1110 function isMasterReportsEnabled (): Boolean;
1112 result
:= (reportsEnabled
and g_Game_IsServer() and g_Game_IsNet() and NetUseMaster
);
1116 //==========================================================================
1118 // g_Net_Slist_Pulse
1120 // non-zero timeout ignores current status (used to fetch server list)
1122 //==========================================================================
1123 procedure g_Net_Slist_Pulse (timeout
: Integer=0);
1129 isListQuery
: Boolean;
1132 if (not g_Net_IsNetworkAvailable()) then exit
;
1134 if (length(mlist
) = 0) then
1136 if (NetMHost
<> nil) then
1138 enet_host_destroy(NetMHost
);
1144 if (NetMHost
= nil) then
1146 NetMHost
:= enet_host_create(nil, 64, NET_MCHANS
, 1024*1024, 1024*1024);
1147 if (NetMHost
= nil) then
1149 e_LogWriteln(_lc
[I_NET_MSG_ERROR
] + _lc
[I_NET_ERR_CLIENT
] + ' (host_create)', TMsgType
.Notify
);
1150 for f
:= 0 to High(mlist
) do mlist
[f
].clear();
1151 SetLength(mlist
, 0);
1156 isListQuery
:= (timeout
> 0);
1158 // reconnect/disconnect/pulse for each master
1159 for f
:= 0 to High(mlist
) do
1161 if (not mlist
[f
].isValid()) then continue
;
1162 if (not mlist
[f
].isAlive()) then
1164 // not connected; try to reconnect if we're asking for a host list, or we are in netgame, and we are the host
1165 if (not isListQuery
) and isMasterReportsEnabled() then
1167 if (mlist
[f
].lastDisconnectTime
= 0) or (ct
< mlist
[f
].lastDisconnectTime
) or (ct
-mlist
[f
].lastDisconnectTime
>= 1000*NMASTER_TIMEOUT_RECONNECT
) then
1169 e_LogWritefln('reconnecting to master [%s]', [mlist
[f
].hostName
], TMsgType
.Notify
);
1174 //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);
1180 // if we're not in slist query, and not in netgame (or not a host), disconnect
1181 if (not isListQuery
) and (not isMasterReportsEnabled()) then
1183 if (mlist
[f
].isConnected()) and (mlist
[f
].updateSent
) then
1185 e_LogWritefln('removing from master [%s]', [mlist
[f
].hostName
], TMsgType
.Notify
);
1188 e_LogWritefln('disconnecting from master [%s]', [mlist
[f
].hostName
], TMsgType
.Notify
);
1189 mlist
[f
].disconnect(false);
1195 // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
1196 // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
1197 // thank you, enet. let's ignore failures altogether then.
1198 count
:= 10; // no more than ten events in a row
1199 sres
:= enet_host_service(NetMHost
, @NetMEvent
, timeout
);
1205 e_LogWriteln(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT] + ' (host_service)', TMsgType.Notify);
1206 for f := 0 to High(mlist) do mlist[f].clear();
1207 SetLength(mlist, 0);
1208 enet_host_destroy(NetMHost);
1214 idx
:= findByPeer(NetMEvent
.peer
);
1217 e_LogWriteln('network event from unknown master host. ignored.', TMsgType
.Warning
);
1218 if (NetMEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then enet_packet_destroy(NetMEvent
.packet
);
1222 if (NetMEvent
.kind
= ENET_EVENT_TYPE_CONNECT
) then
1224 mlist
[idx
].connectedEvent();
1226 else if (NetMEvent
.kind
= ENET_EVENT_TYPE_DISCONNECT
) then
1228 mlist
[idx
].disconnectedEvent();
1230 else if (NetMEvent
.kind
= ENET_EVENT_TYPE_RECEIVE
) then
1232 mlist
[idx
].receivedEvent(NetMEvent
.packet
);
1233 enet_packet_destroy(NetMEvent
.packet
);
1238 if (count
= 0) then break
;
1239 sres
:= enet_host_service(NetMHost
, @NetMEvent
, 0);
1244 //**************************************************************************
1246 // gui and server list
1248 //**************************************************************************
1250 //==========================================================================
1254 //==========================================================================
1255 procedure PingServer (var S
: TNetServer
; Sock
: ENetSocket
);
1258 Ping
: array [0..9] of Byte;
1261 ClTime
:= GetTimerMS();
1263 Buf
.data
:= Addr(Ping
[0]);
1264 Buf
.dataLength
:= 2+8;
1266 Ping
[0] := Ord('D');
1267 Ping
[1] := Ord('F');
1268 Int64(Addr(Ping
[2])^) := ClTime
;
1270 enet_socket_send(Sock
, Addr(S
.PingAddr
), @Buf
, 1);
1274 //==========================================================================
1278 //==========================================================================
1279 procedure PingBcast (Sock
: ENetSocket
);
1283 S
.IP
:= '255.255.255.255';
1284 S
.Port
:= NET_PING_PORT
;
1285 enet_address_set_host(Addr(S
.PingAddr
), PChar(Addr(S
.IP
[1])));
1287 S
.PingAddr
.port
:= S
.Port
;
1288 PingServer(S
, Sock
);
1292 //==========================================================================
1294 // g_Net_Slist_Fetch
1296 //==========================================================================
1297 function g_Net_Slist_Fetch (var SL
: TNetServerList
): Boolean;
1306 SvAddr
: ENetAddress
;
1310 procedure ProcessLocal ();
1313 SetLength(SL
, I
+ 1);
1316 IP
:= DecodeIPV4(SvAddr
.host
);
1317 Port
:= InMsg
.ReadWord();
1318 Ping
:= InMsg
.ReadInt64();
1319 Ping
:= GetTimerMS() - Ping
;
1320 Name
:= InMsg
.ReadString();
1321 Map
:= InMsg
.ReadString();
1322 GameMode
:= InMsg
.ReadByte();
1323 Players
:= InMsg
.ReadByte();
1324 MaxPlayers
:= InMsg
.ReadByte();
1325 Protocol
:= InMsg
.ReadByte();
1326 Password
:= InMsg
.ReadByte() = 1;
1327 LocalPl
:= InMsg
.ReadByte();
1328 Bots
:= InMsg
.ReadWord();
1332 procedure CheckLocalServers ();
1336 Sock
:= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
);
1337 if Sock
= ENET_SOCKET_NULL
then Exit
;
1338 enet_socket_set_option(Sock
, ENET_SOCKOPT_NONBLOCK
, 1);
1339 enet_socket_set_option(Sock
, ENET_SOCKOPT_BROADCAST
, 1);
1344 InMsg
.Alloc(NET_BUFSIZE
);
1345 Buf
.data
:= InMsg
.Data
;
1346 Buf
.dataLength
:= InMsg
.MaxSize
;
1347 while GetTimerMS() - T
<= 500 do
1351 RX
:= enet_socket_receive(Sock
, @SvAddr
, @Buf
, 1);
1352 if RX
<= 0 then continue
;
1353 InMsg
.CurSize
:= RX
;
1355 InMsg
.BeginReading();
1357 if InMsg
.ReadChar() <> 'D' then continue
;
1358 if InMsg
.ReadChar() <> 'F' then continue
;
1364 enet_socket_destroy(Sock
);
1366 if Length(SL
) = 0 then SL
:= nil;
1370 f
, c
, n
, pos
: Integer;
1371 aliveCount
: Integer;
1372 hasUnanswered
: Boolean;
1379 if (not g_Net_IsNetworkAvailable()) then
1385 g_Net_Slist_Pulse(); // this will create mhost
1387 DisconnectAll(true); // forced disconnect
1389 for f
:= 0 to High(mlist
) do
1391 mlist
[f
].connectCount
:= 0;
1392 mlist
[f
].srvAnswered
:= 0;
1396 NetOut
.Write(Byte(NET_MMSG_GET
));
1398 // TODO: what should we identify the build with?
1399 MyVer
:= GAME_VERSION
;
1400 NetOut
.Write(MyVer
);
1403 e_WriteLog('Fetching serverlist...', TMsgType
.Notify
);
1404 g_Console_Add(_lc
[I_NET_MSG
]+_lc
[I_NET_SLIST_FETCH
]);
1406 // wait until all servers connected and answered
1407 stt
:= GetTimerMS();
1411 hasUnanswered
:= false;
1412 for f
:= 0 to High(mlist
) do
1415 e_LogWritefln(' master #%d: [%s] valid=%d; alive=%d; connected=%d; connecting=%d',
1416 [f, mlist[f].hostName, Integer(mlist[f].isValid()), Integer(mlist[f].isAlive()),
1417 Integer(mlist[f].isConnected()), Integer(mlist[f].isConnecting())], TMsgType.Notify);
1419 if (not mlist
[f
].isValid()) then continue
;
1420 if (not mlist
[f
].isAlive()) then
1422 if (mlist
[f
].connectCount
= 0) then
1425 if (mlist
[f
].isAlive()) then
1427 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_WCONN], [mlist[f].hostName]));
1428 hasUnanswered
:= true;
1429 stt
:= GetTimerMS();
1432 else if (mlist
[f
].srvAnswered
> 1) then
1437 else if (mlist
[f
].isConnected()) then
1439 //g_Console_Add(Format(_lc[I_NET_MSG]+_lc[I_NET_SLIST_CONN], [mlist[f].hostName]));
1440 if (mlist
[f
].srvAnswered
= 0) then
1442 pkt
:= enet_packet_create(NetOut
.Data
, NetOut
.CurSize
, Cardinal(ENET_PACKET_FLAG_RELIABLE
));
1443 if assigned(pkt
) then
1445 if (enet_peer_send(mlist
[f
].peer
, NET_MCHAN_MAIN
, pkt
) = 0) then
1447 hasUnanswered
:= true;
1448 mlist
[f
].srvAnswered
:= 1;
1449 stt
:= GetTimerMS();
1453 else if (mlist
[f
].srvAnswered
= 1) then
1455 hasUnanswered
:= true;
1457 else if (mlist
[f
].srvAnswered
> 1) then
1460 mlist
[f
].disconnect(false); // not forced
1463 else if (mlist
[f
].isConnecting()) then
1465 hasUnanswered
:= true;
1468 if (not hasUnanswered
) then break
;
1469 // check for timeout
1471 if (ct
< stt
) or (ct
-stt
> 4000) then break
;
1472 g_Net_Slist_Pulse(300);
1475 if (aliveCount
= 0) then
1478 CheckLocalServers();
1486 slReadUrgent := true;
1490 for f
:= 0 to High(mlist
) do
1492 if (mlist
[f
].srvAnswered
< 2) then continue
;
1493 for n
:= 0 to High(mlist
[f
].srvAnswer
) do
1496 for c
:= 0 to High(SL
) do
1498 if (SL
[c
].IP
= mlist
[f
].srvAnswer
[n
].IP
) and (SL
[c
].Port
= mlist
[f
].srvAnswer
[n
].Port
) then
1507 SetLength(SL
, pos
+1);
1508 SL
[pos
] := mlist
[f
].srvAnswer
[n
];
1509 SL
[pos
].Number
:= pos
;
1512 if (not mlist
[f
].slReadUrgent
) and (mlist
[f
].slUrgent
<> '') then
1514 if (mlist
[f
].slUrgent
<> slUrgent
) then
1516 slUrgent
:= mlist
[f
].slUrgent
;
1517 slReadUrgent
:= false;
1520 if (slMOTD
<> '') and (mlist
[f
].slMOTD
<> '') then
1522 slMOTD
:= mlist
[f
].slMOTD
;
1528 if (length(SL
) = 0) then
1530 CheckLocalServers();
1534 Sock
:= enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM
);
1535 if Sock
= ENET_SOCKET_NULL
then Exit
;
1536 enet_socket_set_option(Sock
, ENET_SOCKOPT_NONBLOCK
, 1);
1538 for I
:= Low(SL
) to High(SL
) do PingServer(SL
[I
], Sock
);
1540 enet_socket_set_option(Sock
, ENET_SOCKOPT_BROADCAST
, 1);
1545 InMsg
.Alloc(NET_BUFSIZE
);
1546 Buf
.data
:= InMsg
.Data
;
1547 Buf
.dataLength
:= InMsg
.MaxSize
;
1549 while GetTimerMS() - T
<= 500 do
1553 RX
:= enet_socket_receive(Sock
, @SvAddr
, @Buf
, 1);
1554 if RX
<= 0 then continue
;
1555 InMsg
.CurSize
:= RX
;
1557 InMsg
.BeginReading();
1559 if InMsg
.ReadChar() <> 'D' then continue
;
1560 if InMsg
.ReadChar() <> 'F' then continue
;
1564 Port
:= InMsg
.ReadWord();
1565 Ping
:= InMsg
.ReadInt64();
1566 Ping
:= GetTimerMS() - Ping
;
1567 Name
:= InMsg
.ReadString();
1568 Map
:= InMsg
.ReadString();
1569 GameMode
:= InMsg
.ReadByte();
1570 Players
:= InMsg
.ReadByte();
1571 MaxPlayers
:= InMsg
.ReadByte();
1572 Protocol
:= InMsg
.ReadByte();
1573 Password
:= InMsg
.ReadByte() = 1;
1574 LocalPl
:= InMsg
.ReadByte();
1575 Bots
:= InMsg
.ReadWord();
1580 for I
:= Low(SL
) to High(SL
) do
1581 if (SL
[I
].PingAddr
.host
= SvAddr
.host
) and
1582 (SL
[I
].PingAddr
.port
= SvAddr
.port
) and
1583 (SL
[I
].Port
= tmpsv
.Port
) and
1584 (SL
[I
].Name
= tmpsv
.Name
) then
1586 tmpsv
.IP
:= SL
[I
].IP
;
1596 SetLength(SL
, I
+ 1);
1597 tmpsv
.IP
:= DecodeIPV4(SvAddr
.host
);
1603 enet_socket_destroy(Sock
);
1610 //==========================================================================
1612 // GetServerFromTable
1614 //==========================================================================
1615 function GetServerFromTable (Index
: Integer; SL
: TNetServerList
; ST
: TNetServerTable
): TNetServer
;
1618 Result
.Protocol
:= 0;
1623 Result
.Players
:= 0;
1624 Result
.MaxPlayers
:= 0;
1625 Result
.LocalPl
:= 0;
1628 Result
.GameMode
:= 0;
1629 Result
.Password
:= false;
1630 FillChar(Result
.PingAddr
, SizeOf(ENetAddress
), 0);
1633 if (Index
< 0) or (Index
>= Length(ST
)) then
1635 Result
:= SL
[ST
[Index
].Indices
[ST
[Index
].Current
]];
1639 //==========================================================================
1641 // g_Serverlist_GenerateTable
1643 //==========================================================================
1644 procedure g_Serverlist_GenerateTable (SL
: TNetServerList
; var ST
: TNetServerTable
);
1648 function FindServerInTable(Name
: AnsiString; Port
: Word): Integer;
1655 for i
:= Low(ST
) to High(ST
) do
1657 if Length(ST
[i
].Indices
) = 0 then
1659 if (SL
[ST
[i
].Indices
[0]].Name
= Name
) and (SL
[ST
[i
].Indices
[0]].Port
= Port
) then
1666 function ComparePing(i1
, i2
: Integer): Boolean;
1672 if (p1
< 0) then p1
:= 999;
1673 if (p2
< 0) then p2
:= 999;
1676 procedure SortIndices(var ind
: Array of Integer);
1681 for I
:= High(ind
) downto Low(ind
) do
1682 for J
:= Low(ind
) to High(ind
) - 1 do
1683 if ComparePing(ind
[j
], ind
[j
+1]) then
1690 procedure SortRows();
1695 for I
:= High(ST
) downto Low(ST
) do
1696 for J
:= Low(ST
) to High(ST
) - 1 do
1697 if ComparePing(ST
[j
].Indices
[0], ST
[j
+1].Indices
[0]) then
1709 for i
:= Low(SL
) to High(SL
) do
1711 j
:= FindServerInTable(SL
[i
].Name
, SL
[i
].Port
);
1715 SetLength(ST
, j
+ 1);
1717 SetLength(ST
[j
].Indices
, 1);
1718 ST
[j
].Indices
[0] := i
;
1722 SetLength(ST
[j
].Indices
, Length(ST
[j
].Indices
) + 1);
1723 ST
[j
].Indices
[High(ST
[j
].Indices
)] := i
;
1727 for i
:= Low(ST
) to High(ST
) do
1728 SortIndices(ST
[i
].Indices
);
1734 //==========================================================================
1736 // g_Serverlist_Control
1738 //==========================================================================
1739 procedure g_Serverlist_Control (var SL
: TNetServerList
; var ST
: TNetServerTable
);
1744 g_Net_Slist_Pulse();
1746 if gConsoleShow
or gChatShow
then
1752 qm
:= sys_HandleInput(); // this updates kbd
1755 if qm
or e_KeyPressed(IK_ESCAPE
) or e_KeyPressed(VK_ESCAPE
) or
1756 e_KeyPressed(JOY0_JUMP
) or e_KeyPressed(JOY1_JUMP
) or
1757 e_KeyPressed(JOY2_JUMP
) or e_KeyPressed(JOY3_JUMP
) then
1761 gState
:= STATE_MENU
;
1763 g_GUI_ShowWindow('MainMenu');
1764 g_GUI_ShowWindow('NetGameMenu');
1765 g_GUI_ShowWindow('NetClientMenu');
1766 g_Sound_PlayEx(WINDOW_CLOSESOUND
);
1771 // if there's a message on the screen,
1772 if not slReadUrgent
and (slUrgent
<> '') then
1774 if e_KeyPressed(IK_RETURN
) or e_KeyPressed(IK_KPRETURN
) or e_KeyPressed(VK_FIRE
) or e_KeyPressed(VK_OPEN
) or
1775 e_KeyPressed(JOY0_ATTACK
) or e_KeyPressed(JOY1_ATTACK
) or e_KeyPressed(JOY2_ATTACK
) or e_KeyPressed(JOY3_ATTACK
) then
1776 slReadUrgent
:= True;
1780 if e_KeyPressed(IK_SPACE
) or e_KeyPressed(VK_JUMP
) or
1781 e_KeyPressed(JOY0_ACTIVATE
) or e_KeyPressed(JOY1_ACTIVATE
) or e_KeyPressed(JOY2_ACTIVATE
) or e_KeyPressed(JOY3_ACTIVATE
) then
1783 if not slFetched
then
1785 slWaitStr
:= _lc
[I_NET_SLIST_WAIT
];
1792 if g_Net_Slist_Fetch(SL
) then
1795 slWaitStr
:= _lc
[I_NET_SLIST_NOSERVERS
];
1799 slWaitStr
:= _lc
[I_NET_SLIST_ERROR
];
1802 g_Serverlist_GenerateTable(SL
, ST
);
1808 if SL
= nil then Exit
;
1810 if e_KeyPressed(IK_RETURN
) or e_KeyPressed(IK_KPRETURN
) or e_KeyPressed(VK_FIRE
) or e_KeyPressed(VK_OPEN
) or
1811 e_KeyPressed(JOY0_ATTACK
) or e_KeyPressed(JOY1_ATTACK
) or e_KeyPressed(JOY2_ATTACK
) or e_KeyPressed(JOY3_ATTACK
) then
1813 if not slReturnPressed
then
1815 Srv
:= GetServerFromTable(slSelection
, SL
, ST
);
1816 if Srv
.Password
then
1820 PromptPort
:= Srv
.Port
;
1822 gState
:= STATE_MENU
;
1824 g_GUI_ShowWindow('ClientPasswordMenu');
1828 slReturnPressed
:= True;
1832 g_Game_StartClient(Srv
.IP
, Srv
.Port
, '');
1835 slReturnPressed
:= True;
1840 slReturnPressed
:= False;
1842 if e_KeyPressed(IK_DOWN
) or e_KeyPressed(IK_KPDOWN
) or e_KeyPressed(VK_DOWN
) or
1843 e_KeyPressed(JOY0_DOWN
) or e_KeyPressed(JOY1_DOWN
) or e_KeyPressed(JOY2_DOWN
) or e_KeyPressed(JOY3_DOWN
) then
1845 if not slDirPressed
then
1848 if slSelection
> High(ST
) then slSelection
:= 0;
1849 slDirPressed
:= True;
1853 if e_KeyPressed(IK_UP
) or e_KeyPressed(IK_KPUP
) or e_KeyPressed(VK_UP
) or
1854 e_KeyPressed(JOY0_UP
) or e_KeyPressed(JOY1_UP
) or e_KeyPressed(JOY2_UP
) or e_KeyPressed(JOY3_UP
) then
1856 if not slDirPressed
then
1858 if slSelection
= 0 then slSelection
:= Length(ST
);
1861 slDirPressed
:= True;
1865 if e_KeyPressed(IK_RIGHT
) or e_KeyPressed(IK_KPRIGHT
) or e_KeyPressed(VK_RIGHT
) or
1866 e_KeyPressed(JOY0_RIGHT
) or e_KeyPressed(JOY1_RIGHT
) or e_KeyPressed(JOY2_RIGHT
) or e_KeyPressed(JOY3_RIGHT
) then
1868 if not slDirPressed
then
1870 Inc(ST
[slSelection
].Current
);
1871 if ST
[slSelection
].Current
> High(ST
[slSelection
].Indices
) then ST
[slSelection
].Current
:= 0;
1872 slDirPressed
:= True;
1876 if e_KeyPressed(IK_LEFT
) or e_KeyPressed(IK_KPLEFT
) or e_KeyPressed(VK_LEFT
) or
1877 e_KeyPressed(JOY0_LEFT
) or e_KeyPressed(JOY1_LEFT
) or e_KeyPressed(JOY2_LEFT
) or e_KeyPressed(JOY3_LEFT
) then
1879 if not slDirPressed
then
1881 if ST
[slSelection
].Current
= 0 then ST
[slSelection
].Current
:= Length(ST
[slSelection
].Indices
);
1882 Dec(ST
[slSelection
].Current
);
1884 slDirPressed
:= True;
1888 if (not e_KeyPressed(IK_DOWN
)) and
1889 (not e_KeyPressed(IK_UP
)) and
1890 (not e_KeyPressed(IK_RIGHT
)) and
1891 (not e_KeyPressed(IK_LEFT
)) and
1892 (not e_KeyPressed(IK_KPDOWN
)) and
1893 (not e_KeyPressed(IK_KPUP
)) and
1894 (not e_KeyPressed(IK_KPRIGHT
)) and
1895 (not e_KeyPressed(IK_KPLEFT
)) and
1896 (not e_KeyPressed(VK_DOWN
)) and
1897 (not e_KeyPressed(VK_UP
)) and
1898 (not e_KeyPressed(VK_RIGHT
)) and
1899 (not e_KeyPressed(VK_LEFT
)) and
1900 (not e_KeyPressed(JOY0_UP
)) and (not e_KeyPressed(JOY1_UP
)) and (not e_KeyPressed(JOY2_UP
)) and (not e_KeyPressed(JOY3_UP
)) and
1901 (not e_KeyPressed(JOY0_DOWN
)) and (not e_KeyPressed(JOY1_DOWN
)) and (not e_KeyPressed(JOY2_DOWN
)) and (not e_KeyPressed(JOY3_DOWN
)) and
1902 (not e_KeyPressed(JOY0_LEFT
)) and (not e_KeyPressed(JOY1_LEFT
)) and (not e_KeyPressed(JOY2_LEFT
)) and (not e_KeyPressed(JOY3_LEFT
)) and
1903 (not e_KeyPressed(JOY0_RIGHT
)) and (not e_KeyPressed(JOY1_RIGHT
)) and (not e_KeyPressed(JOY2_RIGHT
)) and (not e_KeyPressed(JOY3_RIGHT
))
1905 slDirPressed
:= False;