DEADSOFTWARE

net: more master-comm code mutilation; very primitive support for multiple masters...
[d2df-sdl.git] / src / game / g_netmaster.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_netmaster;
18 interface
20 uses
21 ENet, SysUtils, e_msg;
23 const
24 NET_MCHANS = 2;
26 NET_MCHAN_MAIN = 0;
27 NET_MCHAN_UPD = 1;
29 NET_MMSG_UPD = 200;
30 NET_MMSG_DEL = 201;
31 NET_MMSG_GET = 202;
33 type
34 TNetServer = record
35 Number: Byte;
36 Protocol: Byte;
37 Name: AnsiString;
38 IP: AnsiString;
39 Port: Word;
40 Map: AnsiString;
41 Players, MaxPlayers, LocalPl, Bots: Byte;
42 Ping: Int64;
43 GameMode: Byte;
44 Password: Boolean;
45 PingAddr: ENetAddress;
46 end;
47 pTNetServer = ^TNetServer;
48 TNetServerRow = record
49 Indices: Array of Integer;
50 Current: Integer;
51 end;
53 TNetServerList = array of TNetServer;
54 pTNetServerList = ^TNetServerList;
55 TNetServerTable = array of TNetServerRow;
57 type
58 TMasterHost = record
59 public
60 hostName: AnsiString;
61 hostPort: Word;
63 public
64 peer: pENetPeer;
65 enetAddr: ENetAddress;
66 // inside the game, calling `connect()` is disasterous, as it is blocking.
67 // so we'll use this variable to indicate if "connected" event is received.
68 NetHostConnected: Boolean;
69 NetHostConReqTime: Int64; // to timeout `connect`; -1 means "waiting for shutdown"
70 NetUpdatePending: Boolean; // should we send an update after connection completes?
71 updateSent: Boolean;
72 lastUpdateTime: Int64;
73 addressInited: Boolean;
74 // server list request working flags
75 srvAnswered: Integer;
76 srvAnswer: array of TNetServer;
77 slMOTD: AnsiString;
78 slUrgent: AnsiString;
79 slReadUrgent: Boolean;
81 private
82 netmsg: TMsg;
84 public
85 constructor Create (hostandport: AnsiString);
87 procedure clear ();
89 function setAddress (hostandport: AnsiString): Boolean;
91 function isValid (): Boolean;
92 function isAlive (): Boolean; // not disconnected
93 function isConnecting (): Boolean; // is connection in progress?
94 function isConnected (): Boolean;
96 // call as often as you want, the object will do the rest
97 // but try to call this at least once in 100 msecs
98 procedure pulse ();
100 procedure disconnect ();
101 function connect (): Boolean;
103 procedure update ();
104 procedure remove ();
106 class procedure writeInfo (var msg: TMsg); static;
108 procedure connectedEvent ();
109 procedure disconnectedEvent ();
110 procedure receivedEvent (pkt: pENetPacket); // `pkt` is never `nil`
111 end;
114 var
115 slCurrent: TNetServerList = nil;
116 slTable: TNetServerTable = nil;
117 slWaitStr: AnsiString = '';
118 slReturnPressed: Boolean = True;
120 slMOTD: AnsiString = '';
121 slUrgent: AnsiString = '';
124 procedure g_Net_Slist_Set (IP: AnsiString; Port: Word);
125 function g_Net_Slist_Fetch (var SL: TNetServerList): Boolean;
127 // make this server private
128 procedure g_Net_Slist_Private ();
129 // make this server public
130 procedure g_Net_Slist_Public ();
132 // called on network mode init
133 procedure g_Net_Slist_NetworkStarted ();
134 // called on network mode shutdown
135 procedure g_Net_Slist_NetworkStopped ();
137 procedure g_Net_Slist_Pulse (timeout: Integer=0);
139 procedure g_Serverlist_GenerateTable (SL: TNetServerList; var ST: TNetServerTable);
140 procedure g_Serverlist_Draw (var SL: TNetServerList; var ST: TNetServerTable);
141 procedure g_Serverlist_Control (var SL: TNetServerList; var ST: TNetServerTable);
143 function GetTimerMS (): Int64;
146 implementation
148 uses
149 e_input, e_graphics, e_log, g_window, g_net, g_console,
150 g_map, g_game, g_sound, g_gui, g_menu, g_options, g_language, g_basic,
151 wadreader, g_system, utils;
153 // make this server private
154 procedure g_Net_Slist_Private ();
155 begin
156 end;
159 // make this server public
160 procedure g_Net_Slist_Public ();
161 begin
162 end;
165 // called on network mode init
166 procedure g_Net_Slist_NetworkStarted ();
167 begin
168 end;
170 // called on network mode shutdown
171 procedure g_Net_Slist_NetworkStopped ();
172 begin
173 end;
176 var
177 NetMHost: pENetHost = nil;
178 NetMEvent: ENetEvent;
179 mlist: array of TMasterHost = nil;
181 slSelection: Byte = 0;
182 slFetched: Boolean = False;
183 slDirPressed: Boolean = False;
184 slReadUrgent: Boolean = False;
187 //==========================================================================
188 //
189 // GetTimerMS
190 //
191 //==========================================================================
192 function GetTimerMS (): Int64;
193 begin
194 Result := sys_GetTicks() {div 1000};
195 end;
198 //==========================================================================
199 //
200 // findByPeer
201 //
202 //==========================================================================
203 function findByPeer (peer: pENetPeer): Integer;
204 var
205 f: Integer;
206 begin
207 for f := 0 to High(mlist) do if (mlist[f].peer = peer) then begin result := f; exit; end;
208 result := -1;
209 end;
212 //==========================================================================
213 //
214 // TMasterHost.Create
215 //
216 //==========================================================================
217 constructor TMasterHost.Create (hostandport: AnsiString);
218 begin
219 peer := nil;
220 NetHostConnected := false;
221 NetHostConReqTime := 0;
222 NetUpdatePending := false;
223 updateSent := false;
224 hostName := '';
225 hostPort := 25665;
226 SetLength(srvAnswer, 0);
227 srvAnswered := 0;
228 slMOTD := '';
229 slUrgent := '';
230 slReadUrgent := true;
231 netmsg.Alloc(NET_BUFSIZE);
232 setAddress(hostandport);
233 end;
236 //==========================================================================
237 //
238 // TMasterHost.clear
239 //
240 //==========================================================================
241 procedure TMasterHost.clear ();
242 begin
243 updateSent := false; // do not send 'remove'
244 disconnect();
245 hostName := '';
246 hostPort := 25665;
247 netmsg.Free();
248 SetLength(srvAnswer, 0);
249 srvAnswered := 0;
250 slMOTD := '';
251 slUrgent := '';
252 slReadUrgent := true;
253 end;
256 //==========================================================================
257 //
258 // TMasterHost.setAddress
259 //
260 //==========================================================================
261 function TMasterHost.setAddress (hostandport: AnsiString): Boolean;
262 var
263 cp, pp: Integer;
264 begin
265 result := false;
266 SetLength(srvAnswer, 0);
267 srvAnswered := 0;
268 slMOTD := '';
269 slUrgent := '';
270 slReadUrgent := true;
271 updateSent := false; // do not send 'remove'
272 disconnect();
273 addressInited := false;
274 hostName := '';
275 hostPort := 25665;
276 hostandport := Trim(hostandport);
277 if (length(hostandport) > 0) then
278 begin
279 hostName := hostandport;
280 cp := Pos(':', hostandport);
281 if (cp > 0) then
282 begin
283 hostName := Copy(hostandport, 1, cp-1);
284 Delete(hostandport, 1, cp);
285 if (length(hostandport) > 0) then
286 begin
287 try
288 pp := StrToInt(hostandport);
289 except
290 pp := -1;
291 end;
292 if (pp > 0) and (pp < 65536) then hostPort := pp else hostPort := 0;
293 end;
294 end;
295 end;
297 if not isValid() then exit;
298 if (NetInitDone) then
299 begin
300 if (enet_address_set_host(@enetAddr, PChar(Addr(hostName[1]))) <> 0) then
301 begin
302 hostName := '';
303 hostPort := 0;
304 end;
305 enetAddr.Port := hostPort;
306 end;
308 result := isValid();
309 end;
312 //==========================================================================
313 //
314 // TMasterHost.isValid
315 //
316 //==========================================================================
317 function TMasterHost.isValid (): Boolean;
318 begin
319 result := (length(hostName) > 0) and (hostPort > 0);
320 end;
323 //==========================================================================
324 //
325 // TMasterHost.isAlive
326 //
327 // not disconnected
328 //
329 //==========================================================================
330 function TMasterHost.isAlive (): Boolean;
331 begin
332 result := (NetMHost <> nil) and (peer <> nil);
333 end;
336 //==========================================================================
337 //
338 // TMasterHost.isConnecting
339 //
340 // is connection in progress?
341 //
342 //==========================================================================
343 function TMasterHost.isConnecting (): Boolean;
344 begin
345 result := isAlive() and (not NetHostConnected) and (NetHostConReqTime <> -1);
346 end;
349 //==========================================================================
350 //
351 // TMasterHost.isConnected
352 //
353 //==========================================================================
354 function TMasterHost.isConnected (): Boolean;
355 begin
356 result := isAlive() and (NetHostConnected) and (NetHostConReqTime <> -1);
357 end;
360 //==========================================================================
361 //
362 // TMasterHost.connectedEvent
363 //
364 //==========================================================================
365 procedure TMasterHost.connectedEvent ();
366 begin
367 if not isAlive() then exit;
368 if NetHostConnected then exit;
369 NetHostConnected := true;
370 e_LogWritefln('connected to master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
371 end;
374 //==========================================================================
375 //
376 // TMasterHost.disconnectedEvent
377 //
378 //==========================================================================
379 procedure TMasterHost.disconnectedEvent ();
380 begin
381 if not isAlive() then exit;
382 e_LogWritefln('disconnected from master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
383 enet_peer_reset(peer);
384 peer := nil;
385 NetHostConnected := False;
386 NetHostConReqTime := 0;
387 NetUpdatePending := false;
388 updateSent := false;
389 //if (spamConsole) then g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_DISC]);
390 end;
393 //==========================================================================
394 //
395 // TMasterHost.receivedEvent
396 //
397 // `pkt` is never `nil`
398 //
399 //==========================================================================
400 procedure TMasterHost.receivedEvent (pkt: pENetPacket);
401 var
402 msg: TMsg;
403 MID: Byte;
404 Cnt: Byte;
405 f: Integer;
406 s: AnsiString;
408 I, RX: Integer;
409 T: Int64;
410 Sock: ENetSocket;
411 Buf: ENetBuffer;
412 InMsg: TMsg;
413 SvAddr: ENetAddress;
414 FromSL: Boolean;
415 MyVer, Str: AnsiString;
417 begin
418 e_LogWritefln('received packed from master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
419 if not msg.Init(pkt^.data, pkt^.dataLength, True) then exit;
420 // packet type
421 MID := msg.ReadByte();
422 if (MID <> NET_MMSG_GET) then exit;
423 e_LogWritefln('received list packet from master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
424 SetLength(srvAnswer, 0);
425 if (srvAnswered > 0) then Inc(srvAnswered);
426 slMOTD := '';
427 //slUrgent := '';
428 slReadUrgent := true;
429 // number of items
430 Cnt := msg.ReadByte();
431 g_Console_Add(_lc[I_NET_MSG]+Format(_lc[I_NET_SLIST_RETRIEVED], [Cnt]), True);
432 if (Cnt > 0) then
433 begin
434 SetLength(srvAnswer, Cnt);
435 for f := 0 to Cnt-1 do
436 begin
437 srvAnswer[f].Number := f;
438 srvAnswer[f].IP := msg.ReadString();
439 srvAnswer[f].Port := msg.ReadWord();
440 srvAnswer[f].Name := msg.ReadString();
441 srvAnswer[f].Map := msg.ReadString();
442 srvAnswer[f].GameMode := msg.ReadByte();
443 srvAnswer[f].Players := msg.ReadByte();
444 srvAnswer[f].MaxPlayers := msg.ReadByte();
445 srvAnswer[f].Protocol := msg.ReadByte();
446 srvAnswer[f].Password := msg.ReadByte() = 1;
447 enet_address_set_host(Addr(srvAnswer[f].PingAddr), PChar(Addr(srvAnswer[f].IP[1])));
448 srvAnswer[f].Ping := -1;
449 srvAnswer[f].PingAddr.port := NET_PING_PORT;
450 end;
451 end;
453 if (msg.ReadCount < msg.CurSize) then
454 begin
455 // new master, supports version reports
456 s := msg.ReadString();
457 if (s <> {MyVer}GAME_VERSION) then
458 begin
459 { TODO }
460 g_Console_Add('!!! UpdVer = `'+s+'`');
461 end;
462 // even newer master, supports extra info
463 if (msg.ReadCount < msg.CurSize) then
464 begin
465 slMOTD := b_Text_Format(msg.ReadString());
466 s := b_Text_Format(msg.ReadString());
467 // check if the message has updated and the user has to read it again
468 if (slUrgent <> s) then slReadUrgent := false;
469 slUrgent := s;
470 end;
471 end;
472 end;
475 //==========================================================================
476 //
477 // TMasterHost.pulse
478 //
479 // this performs various scheduled tasks, if necessary
480 //
481 //==========================================================================
482 procedure TMasterHost.pulse ();
483 var
484 ct: Int64;
485 begin
486 if not isAlive() then exit;
487 if (NetHostConReqTime = -1) then exit; // waiting for shutdown (disconnect in progress)
488 // process pending connection timeout
489 if (not NetHostConnected) then
490 begin
491 ct := GetTimerMS();
492 if (ct < NetHostConReqTime) or (ct-NetHostConReqTime >= 3000) then
493 begin
494 e_LogWritefln('failed to connect to master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
495 // do not spam with error messages, it looks like the master is down
496 //g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR], True);
497 enet_peer_disconnect(peer, 0);
498 // main pulse will take care of the rest
499 end;
500 exit;
501 end;
502 end;
505 //==========================================================================
506 //
507 // TMasterHost.disconnect
508 //
509 //==========================================================================
510 procedure TMasterHost.disconnect ();
511 begin
512 if not isAlive() then exit;
513 //if (NetMode = NET_SERVER) and isConnected() and updateSent then remove();
515 enet_peer_disconnect_later(peer, 0);
516 // main pulse will take care of the rest
517 NetHostConnected := false;
518 NetHostConReqTime := -1;
519 NetUpdatePending := false;
520 updateSent := false;
522 //if (spamConsole) then g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_DISC]);
523 end;
526 //==========================================================================
527 //
528 // TMasterHost.connect
529 //
530 //==========================================================================
531 function TMasterHost.connect (): Boolean;
532 begin
533 result := false;
534 if not isValid() or (NetHostConReqTime = -1) then exit;
535 if isAlive() then begin result := true; exit; end;
537 SetLength(srvAnswer, 0);
538 srvAnswered := 0;
539 NetHostConnected := false;
540 NetHostConReqTime := 0;
541 NetUpdatePending := false;
542 updateSent := false;
543 if (not NetInitDone) then exit;
545 if (not addressInited) then
546 begin
547 if (enet_address_set_host(@enetAddr, PChar(Addr(hostName[1]))) <> 0) then
548 begin
549 hostName := '';
550 hostPort := 0;
551 exit;
552 end;
553 enetAddr.Port := hostPort;
554 addressInited := true;
555 end;
557 peer := enet_host_connect(NetMHost, @enetAddr, NET_MCHANS, 0);
558 if (peer = nil) then
559 begin
560 g_Console_Add(_lc[I_NET_MSG_ERROR]+_lc[I_NET_ERR_CLIENT], true);
561 exit;
562 end;
564 NetHostConReqTime := GetTimerMS();
565 e_LogWritefln('connecting to master at [%s:%u]', [hostName, hostPort], TMsgType.Notify);
566 end;
569 //==========================================================================
570 //
571 // TMasterHost.writeInfo
572 //
573 //==========================================================================
574 class procedure TMasterHost.writeInfo (var msg: TMsg);
575 var
576 wad, map: AnsiString;
577 begin
578 wad := g_ExtractWadNameNoPath(gMapInfo.Map);
579 map := g_ExtractFileName(gMapInfo.Map);
581 msg.Write(NetServerName);
583 msg.Write(wad+':/'+map);
584 msg.Write(gGameSettings.GameMode);
586 msg.Write(Byte(NetClientCount));
588 msg.Write(NetMaxClients);
590 msg.Write(Byte(NET_PROTOCOL_VER));
591 msg.Write(Byte(NetPassword <> ''));
592 end;
595 //==========================================================================
596 //
597 // TMasterHost.update
598 //
599 //==========================================================================
600 procedure TMasterHost.update ();
601 var
602 pkt: pENetPacket;
603 begin
604 if not isAlive() then exit;
605 if not isConnected() then
606 begin
607 NetUpdatePending := isConnecting();
608 exit;
609 end;
611 netmsg.Clear();
612 try
613 netmsg.Write(Byte(NET_MMSG_UPD));
614 netmsg.Write(NetAddr.port);
616 writeInfo(netmsg);
618 pkt := enet_packet_create(netmsg.Data, netmsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
619 if assigned(pkt) then
620 begin
621 if (enet_peer_send(peer, NET_MCHAN_UPD, pkt) = 0) then NetUpdatePending := false;
622 end;
623 finally
624 netmsg.Clear();
625 end;
626 end;
629 //==========================================================================
630 //
631 // TMasterHost.remove
632 //
633 //==========================================================================
634 procedure TMasterHost.remove ();
635 var
636 pkt: pENetPacket;
637 begin
638 NetUpdatePending := false;
639 if not isAlive() then exit;
640 if not isConnected() then exit;
642 netmsg.Clear();
643 try
644 netmsg.Write(Byte(NET_MMSG_DEL));
645 netmsg.Write(NetAddr.port);
647 pkt := enet_packet_create(netmsg.Data, netmsg.CurSize, ENET_PACKET_FLAG_RELIABLE);
648 if assigned(pkt) then
649 begin
650 enet_peer_send(peer, NET_MCHAN_MAIN, pkt);
651 end;
652 finally
653 netmsg.Clear();
654 end;
655 end;
658 //**************************************************************************
659 //
660 // other functions
661 //
662 //**************************************************************************
664 procedure g_Net_Slist_Set (IP: AnsiString; Port: Word);
665 begin
666 if (length(mlist) = 0) then
667 begin
668 SetLength(mlist, 1);
669 mlist[0].Create(ip+':'+IntToStr(Port));
670 end
671 else
672 begin
673 mlist[0].setAddress(ip+':'+IntToStr(Port));
674 end;
675 e_LogWritefln('Masterserver address set to [%s:%u]', [IP, Port], TMsgType.Notify);
677 if NetInitDone then
678 begin
679 enet_address_set_host(@NetSlistAddr, PChar(Addr(IP[1])));
680 NetSlistAddr.Port := Port;
681 e_WriteLog('Masterserver address set to ' + IP + ':' + IntToStr(Port), TMsgType.Notify);
682 end;
684 end;
687 //**************************************************************************
688 //
689 // main pulse
690 //
691 //**************************************************************************
692 procedure g_Net_Slist_Pulse (timeout: Integer=0);
693 var
694 f: Integer;
695 sres: Integer;
696 idx: Integer;
697 begin
698 if (length(mlist) = 0) then
699 begin
700 if (NetMHost <> nil) then
701 begin
702 enet_host_destroy(NetMHost);
703 NetMHost := nil;
704 exit;
705 end;
706 end;
708 if (NetMHost = nil) then
709 begin
710 NetMHost := enet_host_create(nil, 1, NET_MCHANS, 0, 0);
711 if (NetMHost = nil) then
712 begin
713 g_Console_Add(_lc[I_NET_MSG_ERROR]+_lc[I_NET_ERR_CLIENT], True);
714 for f := 0 to High(mlist) do mlist[f].clear();
715 SetLength(mlist, 0);
716 Exit;
717 end;
718 end;
720 for f := 0 to High(mlist) do mlist[f].pulse();
722 while true do
723 begin
724 sres := enet_host_service(NetMHost, @NetMEvent, timeout);
725 if (sres < 0) then
726 begin
727 g_Console_Add(_lc[I_NET_MSG_ERROR]+_lc[I_NET_ERR_CLIENT], True);
728 for f := 0 to High(mlist) do mlist[f].clear();
729 SetLength(mlist, 0);
730 enet_host_destroy(NetMHost);
731 NetMHost := nil;
732 exit;
733 end;
735 if (sres = 0) then break;
736 idx := findByPeer(NetMEvent.peer);
737 if (idx < 0) then
738 begin
739 e_LogWriteln('network event from unknown master host. ignored.', TMsgType.Warning);
740 if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then enet_packet_destroy(NetMEvent.packet);
741 continue;
742 end;
744 if (NetMEvent.kind = ENET_EVENT_TYPE_CONNECT) then
745 begin
746 mlist[idx].connectedEvent();
747 end
748 else if (NetMEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
749 begin
750 mlist[idx].disconnectedEvent();
751 end
752 else if (NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
753 begin
754 mlist[idx].receivedEvent(NetMEvent.packet);
755 enet_packet_destroy(NetMEvent.packet);
756 end;
757 end;
758 end;
761 //**************************************************************************
762 //
763 // gui and server list
764 //
765 //**************************************************************************
767 //==========================================================================
768 //
769 // PingServer
770 //
771 //==========================================================================
772 procedure PingServer (var S: TNetServer; Sock: ENetSocket);
773 var
774 Buf: ENetBuffer;
775 Ping: array [0..9] of Byte;
776 ClTime: Int64;
777 begin
778 ClTime := GetTimerMS();
780 Buf.data := Addr(Ping[0]);
781 Buf.dataLength := 2+8;
783 Ping[0] := Ord('D');
784 Ping[1] := Ord('F');
785 Int64(Addr(Ping[2])^) := ClTime;
787 enet_socket_send(Sock, Addr(S.PingAddr), @Buf, 1);
788 end;
791 //==========================================================================
792 //
793 // PingBcast
794 //
795 //==========================================================================
796 procedure PingBcast (Sock: ENetSocket);
797 var
798 S: TNetServer;
799 begin
800 S.IP := '255.255.255.255';
801 S.Port := NET_PING_PORT;
802 enet_address_set_host(Addr(S.PingAddr), PChar(Addr(S.IP[1])));
803 S.Ping := -1;
804 S.PingAddr.port := S.Port;
805 PingServer(S, Sock);
806 end;
809 //==========================================================================
810 //
811 // g_Net_Slist_Fetch
812 //
813 //==========================================================================
814 function g_Net_Slist_Fetch (var SL: TNetServerList): Boolean;
815 var
816 Cnt: Byte;
817 pkt: pENetPacket;
818 I, RX: Integer;
819 T: Int64;
820 Sock: ENetSocket;
821 Buf: ENetBuffer;
822 InMsg: TMsg;
823 SvAddr: ENetAddress;
824 FromSL: Boolean;
825 MyVer: AnsiString;
827 procedure DisconnectAll ();
828 var
829 f: Integer;
830 begin
831 for f := 0 to High(mlist) do
832 begin
833 if (mlist[f].isAlive()) then mlist[f].disconnect();
834 end;
835 end;
837 procedure ProcessLocal ();
838 begin
839 I := Length(SL);
840 SetLength(SL, I + 1);
841 with SL[I] do
842 begin
843 IP := DecodeIPV4(SvAddr.host);
844 Port := InMsg.ReadWord();
845 Ping := InMsg.ReadInt64();
846 Ping := GetTimerMS() - Ping;
847 Name := InMsg.ReadString();
848 Map := InMsg.ReadString();
849 GameMode := InMsg.ReadByte();
850 Players := InMsg.ReadByte();
851 MaxPlayers := InMsg.ReadByte();
852 Protocol := InMsg.ReadByte();
853 Password := InMsg.ReadByte() = 1;
854 LocalPl := InMsg.ReadByte();
855 Bots := InMsg.ReadWord();
856 end;
857 end;
859 procedure CheckLocalServers ();
860 begin
861 SetLength(SL, 0);
863 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
864 if Sock = ENET_SOCKET_NULL then Exit;
865 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
866 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
867 PingBcast(Sock);
869 T := GetTimerMS();
871 InMsg.Alloc(NET_BUFSIZE);
872 Buf.data := InMsg.Data;
873 Buf.dataLength := InMsg.MaxSize;
874 while GetTimerMS() - T <= 500 do
875 begin
876 InMsg.Clear();
878 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
879 if RX <= 0 then continue;
880 InMsg.CurSize := RX;
882 InMsg.BeginReading();
884 if InMsg.ReadChar() <> 'D' then continue;
885 if InMsg.ReadChar() <> 'F' then continue;
887 ProcessLocal();
888 end;
890 InMsg.Free();
891 enet_socket_destroy(Sock);
893 if Length(SL) = 0 then SL := nil;
894 end;
896 var
897 f, c, n, pos: Integer;
898 aliveCount: Integer;
899 hasUnanswered: Boolean;
900 stt, ct: Int64;
901 begin
902 result := false;
903 SL := nil;
905 g_Net_Slist_Pulse(); // this will create mhost
907 NetOut.Clear();
908 NetOut.Write(Byte(NET_MMSG_GET));
910 // TODO: what should we identify the build with?
911 MyVer := GAME_VERSION;
912 NetOut.Write(MyVer);
914 try
915 aliveCount := 0;
916 for f := 0 to High(mlist) do
917 begin
918 mlist[f].srvAnswered := 0;
919 if (not mlist[f].isValid()) then continue;
920 if (not mlist[f].isConnected()) then mlist[f].connect();
921 if (not mlist[f].isAlive()) then continue;
922 if (mlist[f].isConnected()) then
923 begin
924 pkt := enet_packet_create(NetOut.Data, NetOut.CurSize, Cardinal(ENET_PACKET_FLAG_RELIABLE));
925 if assigned(pkt) then
926 begin
927 if (enet_peer_send(mlist[f].peer, NET_MCHAN_MAIN, pkt) = 0) then
928 begin
929 Inc(aliveCount);
930 mlist[f].srvAnswered := 1;
931 end;
932 end;
933 end
934 else if (mlist[f].isConnecting()) then
935 begin
936 Inc(aliveCount);
937 end;
938 end;
940 if (aliveCount = 0) then
941 begin
942 DisconnectAll();
943 CheckLocalServers();
944 exit;
945 end;
947 e_WriteLog('Fetching serverlist...', TMsgType.Notify);
948 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_FETCH]);
950 // wait until all servers connected and answered
951 stt := GetTimerMS();
952 while true do
953 begin
954 g_Net_Slist_Pulse(300);
955 aliveCount := 0;
956 hasUnanswered := false;
957 for f := 0 to High(mlist) do
958 begin
959 if (not mlist[f].isValid()) then continue;
960 if (mlist[f].isConnected()) then
961 begin
962 if (mlist[f].srvAnswered = 0) then
963 begin
964 pkt := enet_packet_create(NetOut.Data, NetOut.CurSize, Cardinal(ENET_PACKET_FLAG_RELIABLE));
965 if assigned(pkt) then
966 begin
967 if (enet_peer_send(mlist[f].peer, NET_MCHAN_MAIN, pkt) = 0) then
968 begin
969 hasUnanswered := true;
970 mlist[f].srvAnswered := 1;
971 end;
972 end;
973 end
974 else if (mlist[f].srvAnswered = 1) then
975 begin
976 hasUnanswered := true;
977 end
978 else if (mlist[f].srvAnswered > 1) then
979 begin
980 Inc(aliveCount);
981 end;
982 end
983 else if (mlist[f].isConnecting()) then
984 begin
985 hasUnanswered := true;
986 end;
987 end;
988 if (not hasUnanswered) then break;
989 // check for timeout
990 ct := GetTimerMS();
991 if (ct < stt) or (ct-stt > 4000) then break;
992 end;
994 if (aliveCount = 0) then
995 begin
996 DisconnectAll();
997 CheckLocalServers();
998 exit;
999 end;
1001 slMOTD := '';
1003 slUrgent := '';
1004 slReadUrgent := true;
1007 SetLength(SL, 0);
1008 for f := 0 to High(mlist) do
1009 begin
1010 if (mlist[f].srvAnswered < 2) then continue;
1011 for n := 0 to High(mlist[f].srvAnswer) do
1012 begin
1013 pos := -1;
1014 for c := 0 to High(SL) do
1015 begin
1016 if (SL[c].IP = mlist[f].srvAnswer[n].IP) and (SL[c].Port = mlist[f].srvAnswer[n].Port) then
1017 begin
1018 pos := c;
1019 break;
1020 end;
1021 end;
1022 if (pos < 0) then
1023 begin
1024 pos := length(SL);
1025 SetLength(SL, pos+1);
1026 SL[pos] := mlist[f].srvAnswer[n];
1027 SL[pos].Number := pos;
1028 end;
1029 end;
1030 if (not mlist[f].slReadUrgent) and (mlist[f].slUrgent <> '') then
1031 begin
1032 if (mlist[f].slUrgent <> slUrgent) then
1033 begin
1034 slUrgent := mlist[f].slUrgent;
1035 slReadUrgent := false;
1036 end;
1037 end;
1038 if (slMOTD <> '') and (mlist[f].slMOTD <> '') then
1039 begin
1040 slMOTD := mlist[f].slMOTD;
1041 end;
1042 end;
1044 DisconnectAll();
1046 if (length(SL) = 0) then
1047 begin
1048 CheckLocalServers();
1049 exit;
1050 end;
1052 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
1053 if Sock = ENET_SOCKET_NULL then Exit;
1054 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
1056 for I := Low(SL) to High(SL) do PingServer(SL[I], Sock);
1058 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
1059 PingBcast(Sock);
1061 T := GetTimerMS();
1063 InMsg.Alloc(NET_BUFSIZE);
1064 Buf.data := InMsg.Data;
1065 Buf.dataLength := InMsg.MaxSize;
1066 Cnt := 0;
1067 while GetTimerMS() - T <= 500 do
1068 begin
1069 InMsg.Clear();
1071 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
1072 if RX <= 0 then continue;
1073 InMsg.CurSize := RX;
1075 InMsg.BeginReading();
1077 if InMsg.ReadChar() <> 'D' then continue;
1078 if InMsg.ReadChar() <> 'F' then continue;
1080 FromSL := False;
1081 for I := Low(SL) to High(SL) do
1082 if (SL[I].PingAddr.host = SvAddr.host) and
1083 (SL[I].PingAddr.port = SvAddr.port) then
1084 begin
1085 with SL[I] do
1086 begin
1087 Port := InMsg.ReadWord();
1088 Ping := InMsg.ReadInt64();
1089 Ping := GetTimerMS() - Ping;
1090 Name := InMsg.ReadString();
1091 Map := InMsg.ReadString();
1092 GameMode := InMsg.ReadByte();
1093 Players := InMsg.ReadByte();
1094 MaxPlayers := InMsg.ReadByte();
1095 Protocol := InMsg.ReadByte();
1096 Password := InMsg.ReadByte() = 1;
1097 LocalPl := InMsg.ReadByte();
1098 Bots := InMsg.ReadWord();
1099 end;
1100 FromSL := True;
1101 Inc(Cnt);
1102 break;
1103 end;
1104 if not FromSL then
1105 ProcessLocal();
1106 end;
1108 InMsg.Free();
1109 enet_socket_destroy(Sock);
1110 finally
1111 NetOut.Clear();
1112 end;
1113 end;
1116 //==========================================================================
1117 //
1118 // GetServerFromTable
1119 //
1120 //==========================================================================
1121 function GetServerFromTable (Index: Integer; SL: TNetServerList; ST: TNetServerTable): TNetServer;
1122 begin
1123 Result.Number := 0;
1124 Result.Protocol := 0;
1125 Result.Name := '';
1126 Result.IP := '';
1127 Result.Port := 0;
1128 Result.Map := '';
1129 Result.Players := 0;
1130 Result.MaxPlayers := 0;
1131 Result.LocalPl := 0;
1132 Result.Bots := 0;
1133 Result.Ping := 0;
1134 Result.GameMode := 0;
1135 Result.Password := false;
1136 FillChar(Result.PingAddr, SizeOf(ENetAddress), 0);
1137 if ST = nil then
1138 Exit;
1139 if (Index < 0) or (Index >= Length(ST)) then
1140 Exit;
1141 Result := SL[ST[Index].Indices[ST[Index].Current]];
1142 end;
1145 //==========================================================================
1146 //
1147 // g_Serverlist_Draw
1148 //
1149 //==========================================================================
1150 procedure g_Serverlist_Draw (var SL: TNetServerList; var ST: TNetServerTable);
1151 var
1152 Srv: TNetServer;
1153 sy, i, y, mw, mx, l, motdh: Integer;
1154 cw: Byte = 0;
1155 ch: Byte = 0;
1156 ww: Word = 0;
1157 hh: Word = 0;
1158 ip: AnsiString;
1159 begin
1160 ip := '';
1161 sy := 0;
1163 e_CharFont_GetSize(gMenuFont, _lc[I_NET_SLIST], ww, hh);
1164 e_CharFont_Print(gMenuFont, (gScreenWidth div 2) - (ww div 2), 16, _lc[I_NET_SLIST]);
1166 e_TextureFontGetSize(gStdFont, cw, ch);
1168 ip := _lc[I_NET_SLIST_HELP];
1169 mw := (Length(ip) * cw) div 2;
1171 motdh := gScreenHeight - 49 - ch * b_Text_LineCount(slMOTD);
1173 e_DrawFillQuad(16, 64, gScreenWidth-16, motdh, 64, 64, 64, 110);
1174 e_DrawQuad(16, 64, gScreenWidth-16, motdh, 255, 127, 0);
1176 e_TextureFontPrintEx(gScreenWidth div 2 - mw, gScreenHeight-24, ip, gStdFont, 225, 225, 225, 1);
1178 // MOTD
1179 if slMOTD <> '' then
1180 begin
1181 e_DrawFillQuad(16, motdh, gScreenWidth-16, gScreenHeight-44, 64, 64, 64, 110);
1182 e_DrawQuad(16, motdh, gScreenWidth-16, gScreenHeight-44, 255, 127, 0);
1183 e_TextureFontPrintFmt(20, motdh + 3, slMOTD, gStdFont, False, True);
1184 end;
1186 // Urgent message
1187 if not slReadUrgent and (slUrgent <> '') then
1188 begin
1189 e_DrawFillQuad(17, 65, gScreenWidth-17, motdh-1, 64, 64, 64, 128);
1190 e_DrawFillQuad(gScreenWidth div 2 - 256, gScreenHeight div 2 - 60,
1191 gScreenWidth div 2 + 256, gScreenHeight div 2 + 60, 64, 64, 64, 128);
1192 e_DrawQuad(gScreenWidth div 2 - 256, gScreenHeight div 2 - 60,
1193 gScreenWidth div 2 + 256, gScreenHeight div 2 + 60, 255, 127, 0);
1194 e_DrawLine(1, gScreenWidth div 2 - 256, gScreenHeight div 2 - 40,
1195 gScreenWidth div 2 + 256, gScreenHeight div 2 - 40, 255, 127, 0);
1196 l := Length(_lc[I_NET_SLIST_URGENT]) div 2;
1197 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 - 58,
1198 _lc[I_NET_SLIST_URGENT], gStdFont);
1199 l := Length(slUrgent) div 2;
1200 e_TextureFontPrintFmt(gScreenWidth div 2 - 253, gScreenHeight div 2 - 38,
1201 slUrgent, gStdFont, False, True);
1202 l := Length(_lc[I_NET_SLIST_URGENT_CONT]) div 2;
1203 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 + 41,
1204 _lc[I_NET_SLIST_URGENT_CONT], gStdFont);
1205 e_DrawLine(1, gScreenWidth div 2 - 256, gScreenHeight div 2 + 40,
1206 gScreenWidth div 2 + 256, gScreenHeight div 2 + 40, 255, 127, 0);
1207 Exit;
1208 end;
1210 if SL = nil then
1211 begin
1212 l := Length(slWaitStr) div 2;
1213 e_DrawFillQuad(17, 65, gScreenWidth-17, motdh-1, 64, 64, 64, 128);
1214 e_DrawQuad(gScreenWidth div 2 - 192, gScreenHeight div 2 - 10,
1215 gScreenWidth div 2 + 192, gScreenHeight div 2 + 11, 255, 127, 0);
1216 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 - ch div 2,
1217 slWaitStr, gStdFont);
1218 Exit;
1219 end;
1221 y := 90;
1222 if (slSelection < Length(ST)) then
1223 begin
1224 I := slSelection;
1225 sy := y + 42 * I - 4;
1226 Srv := GetServerFromTable(I, SL, ST);
1227 ip := _lc[I_NET_ADDRESS] + ' ' + Srv.IP + ':' + IntToStr(Srv.Port);
1228 if Srv.Password then
1229 ip := ip + ' ' + _lc[I_NET_SERVER_PASSWORD] + ' ' + _lc[I_MENU_YES]
1230 else
1231 ip := ip + ' ' + _lc[I_NET_SERVER_PASSWORD] + ' ' + _lc[I_MENU_NO];
1232 end else
1233 if Length(ST) > 0 then
1234 slSelection := 0;
1236 mw := (gScreenWidth - 188);
1237 mx := 16 + mw;
1239 e_DrawFillQuad(16 + 1, sy, gScreenWidth - 16 - 1, sy + 40, 64, 64, 64, 0);
1240 e_DrawLine(1, 16 + 1, sy, gScreenWidth - 16 - 1, sy, 205, 205, 205);
1241 e_DrawLine(1, 16 + 1, sy + 41, gScreenWidth - 16 - 1, sy + 41, 255, 255, 255);
1243 e_DrawLine(1, 16, 85, gScreenWidth - 16, 85, 255, 127, 0);
1244 e_DrawLine(1, 16, motdh-20, gScreenWidth-16, motdh-20, 255, 127, 0);
1246 e_DrawLine(1, mx - 70, 64, mx - 70, motdh, 255, 127, 0);
1247 e_DrawLine(1, mx, 64, mx, motdh-20, 255, 127, 0);
1248 e_DrawLine(1, mx + 52, 64, mx + 52, motdh-20, 255, 127, 0);
1249 e_DrawLine(1, mx + 104, 64, mx + 104, motdh-20, 255, 127, 0);
1251 e_TextureFontPrintEx(18, 68, 'NAME/MAP', gStdFont, 255, 127, 0, 1);
1252 e_TextureFontPrintEx(mx - 68, 68, 'PING', gStdFont, 255, 127, 0, 1);
1253 e_TextureFontPrintEx(mx + 2, 68, 'MODE', gStdFont, 255, 127, 0, 1);
1254 e_TextureFontPrintEx(mx + 54, 68, 'PLRS', gStdFont, 255, 127, 0, 1);
1255 e_TextureFontPrintEx(mx + 106, 68, 'VER', gStdFont, 255, 127, 0, 1);
1257 y := 90;
1258 for I := 0 to High(ST) do
1259 begin
1260 Srv := GetServerFromTable(I, SL, ST);
1261 // Name and map
1262 e_TextureFontPrintEx(18, y, Srv.Name, gStdFont, 255, 255, 255, 1);
1263 e_TextureFontPrintEx(18, y + 16, Srv.Map, gStdFont, 210, 210, 210, 1);
1265 // Ping and similar count
1266 if (Srv.Ping < 0) or (Srv.Ping > 999) then
1267 e_TextureFontPrintEx(mx - 68, y, _lc[I_NET_SLIST_NO_ACCESS], gStdFont, 255, 0, 0, 1)
1268 else
1269 if Srv.Ping = 0 then
1270 e_TextureFontPrintEx(mx - 68, y, '<1' + _lc[I_NET_SLIST_PING_MS], gStdFont, 255, 255, 255, 1)
1271 else
1272 e_TextureFontPrintEx(mx - 68, y, IntToStr(Srv.Ping) + _lc[I_NET_SLIST_PING_MS], gStdFont, 255, 255, 255, 1);
1274 if Length(ST[I].Indices) > 1 then
1275 e_TextureFontPrintEx(mx - 68, y + 16, '< ' + IntToStr(Length(ST[I].Indices)) + ' >', gStdFont, 210, 210, 210, 1);
1277 // Game mode
1278 e_TextureFontPrintEx(mx + 2, y, g_Game_ModeToText(Srv.GameMode), gStdFont, 255, 255, 255, 1);
1280 // Players
1281 e_TextureFontPrintEx(mx + 54, y, IntToStr(Srv.Players) + '/' + IntToStr(Srv.MaxPlayers), gStdFont, 255, 255, 255, 1);
1282 e_TextureFontPrintEx(mx + 54, y + 16, IntToStr(Srv.LocalPl) + '+' + IntToStr(Srv.Bots), gStdFont, 210, 210, 210, 1);
1284 // Version
1285 e_TextureFontPrintEx(mx + 106, y, IntToStr(Srv.Protocol), gStdFont, 255, 255, 255, 1);
1287 y := y + 42;
1288 end;
1290 e_TextureFontPrintEx(20, motdh-20+3, ip, gStdFont, 205, 205, 205, 1);
1291 ip := IntToStr(Length(ST)) + _lc[I_NET_SLIST_SERVERS];
1292 e_TextureFontPrintEx(gScreenWidth - 48 - (Length(ip) + 1)*cw,
1293 motdh-20+3, ip, gStdFont, 205, 205, 205, 1);
1294 end;
1297 //==========================================================================
1298 //
1299 // g_Serverlist_GenerateTable
1300 //
1301 //==========================================================================
1302 procedure g_Serverlist_GenerateTable (SL: TNetServerList; var ST: TNetServerTable);
1303 var
1304 i, j: Integer;
1306 function FindServerInTable(Name: AnsiString): Integer;
1307 var
1308 i: Integer;
1309 begin
1310 Result := -1;
1311 if ST = nil then
1312 Exit;
1313 for i := Low(ST) to High(ST) do
1314 begin
1315 if Length(ST[i].Indices) = 0 then
1316 continue;
1317 if SL[ST[i].Indices[0]].Name = Name then
1318 begin
1319 Result := i;
1320 Exit;
1321 end;
1322 end;
1323 end;
1324 function ComparePing(i1, i2: Integer): Boolean;
1325 var
1326 p1, p2: Int64;
1327 begin
1328 p1 := SL[i1].Ping;
1329 p2 := SL[i2].Ping;
1330 if (p1 < 0) then p1 := 999;
1331 if (p2 < 0) then p2 := 999;
1332 Result := p1 > p2;
1333 end;
1334 procedure SortIndices(var ind: Array of Integer);
1335 var
1336 I, J: Integer;
1337 T: Integer;
1338 begin
1339 for I := High(ind) downto Low(ind) do
1340 for J := Low(ind) to High(ind) - 1 do
1341 if ComparePing(ind[j], ind[j+1]) then
1342 begin
1343 T := ind[j];
1344 ind[j] := ind[j+1];
1345 ind[j+1] := T;
1346 end;
1347 end;
1348 procedure SortRows();
1349 var
1350 I, J: Integer;
1351 T: TNetServerRow;
1352 begin
1353 for I := High(ST) downto Low(ST) do
1354 for J := Low(ST) to High(ST) - 1 do
1355 if ComparePing(ST[j].Indices[0], ST[j+1].Indices[0]) then
1356 begin
1357 T := ST[j];
1358 ST[j] := ST[j+1];
1359 ST[j+1] := T;
1360 end;
1361 end;
1362 begin
1363 ST := nil;
1364 if SL = nil then
1365 Exit;
1366 for i := Low(SL) to High(SL) do
1367 begin
1368 j := FindServerInTable(SL[i].Name);
1369 if j = -1 then
1370 begin
1371 j := Length(ST);
1372 SetLength(ST, j + 1);
1373 ST[j].Current := 0;
1374 SetLength(ST[j].Indices, 1);
1375 ST[j].Indices[0] := i;
1376 end
1377 else
1378 begin
1379 SetLength(ST[j].Indices, Length(ST[j].Indices) + 1);
1380 ST[j].Indices[High(ST[j].Indices)] := i;
1381 end;
1382 end;
1384 for i := Low(ST) to High(ST) do
1385 SortIndices(ST[i].Indices);
1387 SortRows();
1388 end;
1391 //==========================================================================
1392 //
1393 // g_Serverlist_Control
1394 //
1395 //==========================================================================
1396 procedure g_Serverlist_Control (var SL: TNetServerList; var ST: TNetServerTable);
1397 var
1398 qm: Boolean;
1399 Srv: TNetServer;
1400 begin
1401 if gConsoleShow or gChatShow then
1402 Exit;
1404 qm := sys_HandleInput(); // this updates kbd
1406 if qm or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
1407 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or
1408 e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
1409 begin
1410 SL := nil;
1411 ST := nil;
1412 gState := STATE_MENU;
1413 g_GUI_ShowWindow('MainMenu');
1414 g_GUI_ShowWindow('NetGameMenu');
1415 g_GUI_ShowWindow('NetClientMenu');
1416 g_Sound_PlayEx(WINDOW_CLOSESOUND);
1417 Exit;
1418 end;
1420 // if there's a message on the screen,
1421 if not slReadUrgent and (slUrgent <> '') then
1422 begin
1423 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1424 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
1425 slReadUrgent := True;
1426 Exit;
1427 end;
1429 if e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_JUMP) or
1430 e_KeyPressed(JOY0_ACTIVATE) or e_KeyPressed(JOY1_ACTIVATE) or e_KeyPressed(JOY2_ACTIVATE) or e_KeyPressed(JOY3_ACTIVATE) then
1431 begin
1432 if not slFetched then
1433 begin
1434 slWaitStr := _lc[I_NET_SLIST_WAIT];
1436 g_Game_Draw;
1437 sys_Repaint;
1439 if g_Net_Slist_Fetch(SL) then
1440 begin
1441 if SL = nil then
1442 slWaitStr := _lc[I_NET_SLIST_NOSERVERS];
1443 end
1444 else
1445 if SL = nil then
1446 slWaitStr := _lc[I_NET_SLIST_ERROR];
1447 slFetched := True;
1448 slSelection := 0;
1449 g_Serverlist_GenerateTable(SL, ST);
1450 end;
1451 end
1452 else
1453 slFetched := False;
1455 if SL = nil then Exit;
1457 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
1458 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
1459 begin
1460 if not slReturnPressed then
1461 begin
1462 Srv := GetServerFromTable(slSelection, SL, ST);
1463 if Srv.Password then
1464 begin
1465 PromptIP := Srv.IP;
1466 PromptPort := Srv.Port;
1467 gState := STATE_MENU;
1468 g_GUI_ShowWindow('ClientPasswordMenu');
1469 SL := nil;
1470 ST := nil;
1471 slReturnPressed := True;
1472 Exit;
1473 end
1474 else
1475 g_Game_StartClient(Srv.IP, Srv.Port, '');
1476 SL := nil;
1477 ST := nil;
1478 slReturnPressed := True;
1479 Exit;
1480 end;
1481 end
1482 else
1483 slReturnPressed := False;
1485 if e_KeyPressed(IK_DOWN) or e_KeyPressed(IK_KPDOWN) or e_KeyPressed(VK_DOWN) or
1486 e_KeyPressed(JOY0_DOWN) or e_KeyPressed(JOY1_DOWN) or e_KeyPressed(JOY2_DOWN) or e_KeyPressed(JOY3_DOWN) then
1487 begin
1488 if not slDirPressed then
1489 begin
1490 Inc(slSelection);
1491 if slSelection > High(ST) then slSelection := 0;
1492 slDirPressed := True;
1493 end;
1494 end;
1496 if e_KeyPressed(IK_UP) or e_KeyPressed(IK_KPUP) or e_KeyPressed(VK_UP) or
1497 e_KeyPressed(JOY0_UP) or e_KeyPressed(JOY1_UP) or e_KeyPressed(JOY2_UP) or e_KeyPressed(JOY3_UP) then
1498 begin
1499 if not slDirPressed then
1500 begin
1501 if slSelection = 0 then slSelection := Length(ST);
1502 Dec(slSelection);
1504 slDirPressed := True;
1505 end;
1506 end;
1508 if e_KeyPressed(IK_RIGHT) or e_KeyPressed(IK_KPRIGHT) or e_KeyPressed(VK_RIGHT) or
1509 e_KeyPressed(JOY0_RIGHT) or e_KeyPressed(JOY1_RIGHT) or e_KeyPressed(JOY2_RIGHT) or e_KeyPressed(JOY3_RIGHT) then
1510 begin
1511 if not slDirPressed then
1512 begin
1513 Inc(ST[slSelection].Current);
1514 if ST[slSelection].Current > High(ST[slSelection].Indices) then ST[slSelection].Current := 0;
1515 slDirPressed := True;
1516 end;
1517 end;
1519 if e_KeyPressed(IK_LEFT) or e_KeyPressed(IK_KPLEFT) or e_KeyPressed(VK_LEFT) or
1520 e_KeyPressed(JOY0_LEFT) or e_KeyPressed(JOY1_LEFT) or e_KeyPressed(JOY2_LEFT) or e_KeyPressed(JOY3_LEFT) then
1521 begin
1522 if not slDirPressed then
1523 begin
1524 if ST[slSelection].Current = 0 then ST[slSelection].Current := Length(ST[slSelection].Indices);
1525 Dec(ST[slSelection].Current);
1527 slDirPressed := True;
1528 end;
1529 end;
1531 if (not e_KeyPressed(IK_DOWN)) and
1532 (not e_KeyPressed(IK_UP)) and
1533 (not e_KeyPressed(IK_RIGHT)) and
1534 (not e_KeyPressed(IK_LEFT)) and
1535 (not e_KeyPressed(IK_KPDOWN)) and
1536 (not e_KeyPressed(IK_KPUP)) and
1537 (not e_KeyPressed(IK_KPRIGHT)) and
1538 (not e_KeyPressed(IK_KPLEFT)) and
1539 (not e_KeyPressed(VK_DOWN)) and
1540 (not e_KeyPressed(VK_UP)) and
1541 (not e_KeyPressed(VK_RIGHT)) and
1542 (not e_KeyPressed(VK_LEFT)) and
1543 (not e_KeyPressed(JOY0_UP)) and (not e_KeyPressed(JOY1_UP)) and (not e_KeyPressed(JOY2_UP)) and (not e_KeyPressed(JOY3_UP)) and
1544 (not e_KeyPressed(JOY0_DOWN)) and (not e_KeyPressed(JOY1_DOWN)) and (not e_KeyPressed(JOY2_DOWN)) and (not e_KeyPressed(JOY3_DOWN)) and
1545 (not e_KeyPressed(JOY0_LEFT)) and (not e_KeyPressed(JOY1_LEFT)) and (not e_KeyPressed(JOY2_LEFT)) and (not e_KeyPressed(JOY3_LEFT)) and
1546 (not e_KeyPressed(JOY0_RIGHT)) and (not e_KeyPressed(JOY1_RIGHT)) and (not e_KeyPressed(JOY2_RIGHT)) and (not e_KeyPressed(JOY3_RIGHT))
1547 then
1548 slDirPressed := False;
1549 end;
1552 end.