DEADSOFTWARE

Net: Start ebin master upgrade
[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, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 unit g_netmaster;
19 interface
21 uses ENet;
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: string;
38 IP: string;
39 Port: Word;
40 Map: string;
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 var
58 NetMHost: pENetHost = nil;
59 NetMPeer: pENetPeer = nil;
61 slCurrent: TNetServerList = nil;
62 slTable: TNetServerTable = nil;
63 slWaitStr: string = '';
64 slReturnPressed: Boolean = True;
66 procedure g_Net_Slist_Set(IP: string; Port: Word);
67 function g_Net_Slist_Fetch(var SL: TNetServerList): Boolean;
68 procedure g_Net_Slist_Update();
69 procedure g_Net_Slist_Remove();
70 function g_Net_Slist_Connect(): Boolean;
71 procedure g_Net_Slist_Check();
72 procedure g_Net_Slist_Disconnect();
73 procedure g_Net_Slist_WriteInfo();
75 procedure g_Serverlist_GenerateTable(SL: TNetServerList; var ST: TNetServerTable);
76 procedure g_Serverlist_Draw(var SL: TNetServerList; var ST: TNetServerTable);
77 procedure g_Serverlist_Control(var SL: TNetServerList; var ST: TNetServerTable);
79 implementation
81 uses
82 SysUtils, e_msg, e_input, e_graphics, e_log, g_window, g_net, g_console,
83 g_map, g_game, g_sound, g_gui, g_menu, g_options, g_language, g_basic,
84 wadreader;
86 var
87 NetMEvent: ENetEvent;
88 slSelection: Byte = 0;
89 slFetched: Boolean = False;
90 slDirPressed: Boolean = False;
92 function GetTimerMS(): Int64;
93 begin
94 Result := GetTimer() {div 1000};
95 end;
97 procedure PingServer(var S: TNetServer; Sock: ENetSocket);
98 var
99 Buf: ENetBuffer;
100 Ping: array [0..9] of Byte;
101 ClTime: Int64;
102 begin
103 ClTime := GetTimerMS();
105 Buf.data := Addr(Ping[0]);
106 Buf.dataLength := 2+8;
108 Ping[0] := Ord('D');
109 Ping[1] := Ord('F');
110 Int64(Addr(Ping[2])^) := ClTime;
112 enet_socket_send(Sock, Addr(S.PingAddr), @Buf, 1);
113 end;
115 procedure PingBcast(Sock: ENetSocket);
116 var
117 S: TNetServer;
118 begin
119 S.IP := '255.255.255.255';
120 S.Port := NET_PING_PORT;
121 enet_address_set_host(Addr(S.PingAddr), PChar(Addr(S.IP[1])));
122 S.Ping := -1;
123 S.PingAddr.port := S.Port;
124 PingServer(S, Sock);
125 end;
127 function g_Net_Slist_Fetch(var SL: TNetServerList): Boolean;
128 var
129 Cnt: Byte;
130 P: pENetPacket;
131 MID: Byte;
132 I, RX: Integer;
133 T: Int64;
134 Sock: ENetSocket;
135 Buf: ENetBuffer;
136 InMsg: TMsg;
137 SvAddr: ENetAddress;
138 FromSL: Boolean;
139 UpdVer, MyVer: string;
141 procedure ProcessLocal();
142 begin
143 I := Length(SL);
144 SetLength(SL, I + 1);
145 with SL[I] do
146 begin
147 IP := DecodeIPV4(SvAddr.host);
148 Port := InMsg.ReadWord();
149 Ping := InMsg.ReadInt64();
150 Ping := GetTimerMS() - Ping;
151 Name := InMsg.ReadString();
152 Map := InMsg.ReadString();
153 GameMode := InMsg.ReadByte();
154 Players := InMsg.ReadByte();
155 MaxPlayers := InMsg.ReadByte();
156 Protocol := InMsg.ReadByte();
157 Password := InMsg.ReadByte() = 1;
158 LocalPl := InMsg.ReadByte();
159 Bots := InMsg.ReadWord();
160 end;
161 end;
162 procedure CheckLocalServers();
163 begin
164 SetLength(SL, 0);
166 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
167 if Sock = ENET_SOCKET_NULL then Exit;
168 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
169 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
170 PingBcast(Sock);
172 T := GetTimerMS();
174 InMsg.Alloc(NET_BUFSIZE);
175 Buf.data := InMsg.Data;
176 Buf.dataLength := InMsg.MaxSize;
177 while GetTimerMS() - T <= 500 do
178 begin
179 InMsg.Clear();
181 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
182 if RX <= 0 then continue;
183 InMsg.CurSize := RX;
185 InMsg.BeginReading();
187 if InMsg.ReadChar() <> 'D' then continue;
188 if InMsg.ReadChar() <> 'F' then continue;
190 ProcessLocal();
191 end;
193 InMsg.Free();
194 enet_socket_destroy(Sock);
196 if Length(SL) = 0 then SL := nil;
197 end;
198 begin
199 Result := False;
200 SL := nil;
202 if (NetMHost <> nil) or (NetMPeer <> nil) then
203 begin
204 CheckLocalServers();
205 Exit;
206 end;
208 if not g_Net_Slist_Connect then
209 begin
210 CheckLocalServers();
211 Exit;
212 end;
214 e_WriteLog('Fetching serverlist...', TMsgType.Notify);
215 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_FETCH]);
217 NetOut.Clear();
218 NetOut.Write(Byte(NET_MMSG_GET));
220 // TODO: what should we identify the build with?
221 MyVer := GAME_VERSION;
222 NetOut.Write(MyVer);
224 P := enet_packet_create(NetOut.Data, NetOut.CurSize, Cardinal(ENET_PACKET_FLAG_RELIABLE));
225 enet_peer_send(NetMPeer, NET_MCHAN_MAIN, P);
226 enet_host_flush(NetMHost);
228 while enet_host_service(NetMHost, @NetMEvent, 5000) > 0 do
229 begin
230 if NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE then
231 begin
232 if not InMsg.Init(NetMEvent.packet^.data, NetMEvent.packet^.dataLength, True) then continue;
234 MID := InMsg.ReadByte();
236 if MID <> NET_MMSG_GET then continue;
238 Cnt := InMsg.ReadByte();
239 g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_SLIST_RETRIEVED], [Cnt]), True);
241 if Cnt > 0 then
242 begin
243 SetLength(SL, Cnt);
245 for I := 0 to Cnt - 1 do
246 begin
247 SL[I].Number := I;
248 SL[I].IP := InMsg.ReadString();
249 SL[I].Port := InMsg.ReadWord();
250 SL[I].Name := InMsg.ReadString();
251 SL[I].Map := InMsg.ReadString();
252 SL[I].GameMode := InMsg.ReadByte();
253 SL[I].Players := InMsg.ReadByte();
254 SL[I].MaxPlayers := InMsg.ReadByte();
255 SL[I].Protocol := InMsg.ReadByte();
256 SL[I].Password := InMsg.ReadByte() = 1;
257 enet_address_set_host(Addr(SL[I].PingAddr), PChar(Addr(SL[I].IP[1])));
258 SL[I].Ping := -1;
259 SL[I].PingAddr.port := NET_PING_PORT;
260 end;
261 end;
263 if InMsg.ReadCount < InMsg.CurSize then
264 begin
265 // new master, supports version reports
266 UpdVer := InMsg.ReadString();
267 if (UpdVer <> MyVer) then
268 begin
269 { TODO }
270 g_Console_Add('!!! UpdVer = `' + UpdVer + '`');
271 end;
272 end;
274 Result := True;
275 break;
276 end;
277 end;
279 g_Net_Slist_Disconnect;
280 NetOut.Clear();
282 if Length(SL) = 0 then
283 begin
284 CheckLocalServers();
285 Exit;
286 end;
288 Sock := enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
289 if Sock = ENET_SOCKET_NULL then Exit;
290 enet_socket_set_option(Sock, ENET_SOCKOPT_NONBLOCK, 1);
292 for I := Low(SL) to High(SL) do
293 PingServer(SL[I], Sock);
295 enet_socket_set_option(Sock, ENET_SOCKOPT_BROADCAST, 1);
296 PingBcast(Sock);
298 T := GetTimerMS();
300 InMsg.Alloc(NET_BUFSIZE);
301 Buf.data := InMsg.Data;
302 Buf.dataLength := InMsg.MaxSize;
303 Cnt := 0;
304 while GetTimerMS() - T <= 500 do
305 begin
306 InMsg.Clear();
308 RX := enet_socket_receive(Sock, @SvAddr, @Buf, 1);
309 if RX <= 0 then continue;
310 InMsg.CurSize := RX;
312 InMsg.BeginReading();
314 if InMsg.ReadChar() <> 'D' then continue;
315 if InMsg.ReadChar() <> 'F' then continue;
317 FromSL := False;
318 for I := Low(SL) to High(SL) do
319 if (SL[I].PingAddr.host = SvAddr.host) and
320 (SL[I].PingAddr.port = SvAddr.port) then
321 begin
322 with SL[I] do
323 begin
324 Port := InMsg.ReadWord();
325 Ping := InMsg.ReadInt64();
326 Ping := GetTimerMS() - Ping;
327 Name := InMsg.ReadString();
328 Map := InMsg.ReadString();
329 GameMode := InMsg.ReadByte();
330 Players := InMsg.ReadByte();
331 MaxPlayers := InMsg.ReadByte();
332 Protocol := InMsg.ReadByte();
333 Password := InMsg.ReadByte() = 1;
334 LocalPl := InMsg.ReadByte();
335 Bots := InMsg.ReadWord();
336 end;
337 FromSL := True;
338 Inc(Cnt);
339 break;
340 end;
341 if not FromSL then
342 ProcessLocal();
343 end;
345 InMsg.Free();
346 enet_socket_destroy(Sock);
347 end;
349 procedure g_Net_Slist_WriteInfo();
350 var
351 Wad, Map: string;
352 Cli: Byte;
353 begin
354 Wad := g_ExtractWadNameNoPath(gMapInfo.Map);
355 Map := g_ExtractFileName(gMapInfo.Map);
357 NetOut.Write(NetServerName);
359 NetOut.Write(Wad + ':\' + Map);
360 NetOut.Write(gGameSettings.GameMode);
362 Cli := NetClientCount;
363 NetOut.Write(Cli);
365 NetOut.Write(NetMaxClients);
367 NetOut.Write(Byte(NET_PROTOCOL_VER));
368 NetOut.Write(Byte(NetPassword <> ''));
369 end;
371 procedure g_Net_Slist_Update;
372 var
374 P: pENetPacket;
376 begin
377 if (NetMHost = nil) or (NetMPeer = nil) then Exit;
379 NetOut.Clear();
380 NetOut.Write(Byte(NET_MMSG_UPD));
381 NetOut.Write(NetAddr.port);
383 g_Net_Slist_WriteInfo();
385 P := enet_packet_create(NetOut.Data, NetOut.CurSize, Cardinal(ENET_PACKET_FLAG_RELIABLE));
386 enet_peer_send(NetMPeer, NET_MCHAN_UPD, P);
388 enet_host_flush(NetMHost);
389 NetOut.Clear();
390 end;
392 procedure g_Net_Slist_Remove;
393 var
394 P: pENetPacket;
395 begin
396 if (NetMHost = nil) or (NetMPeer = nil) then Exit;
397 NetOut.Clear();
398 NetOut.Write(Byte(NET_MMSG_DEL));
399 NetOut.Write(NetAddr.port);
401 P := enet_packet_create(NetOut.Data, NetOut.CurSize, Cardinal(ENET_PACKET_FLAG_RELIABLE));
402 enet_peer_send(NetMPeer, NET_MCHAN_MAIN, P);
404 enet_host_flush(NetMHost);
405 NetOut.Clear();
406 end;
408 function g_Net_Slist_Connect: Boolean;
409 begin
410 Result := False;
412 NetMHost := enet_host_create(nil, 1, NET_MCHANS, 0, 0);
413 if (NetMHost = nil) then
414 begin
415 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
416 Exit;
417 end;
419 NetMPeer := enet_host_connect(NetMHost, @NetSlistAddr, NET_MCHANS, 0);
420 if (NetMPeer = nil) then
421 begin
422 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CLIENT], True);
423 enet_host_destroy(NetMHost);
424 NetMHost := nil;
425 Exit;
426 end;
428 if (enet_host_service(NetMHost, @NetMEvent, 3000) > 0) then
429 if NetMEvent.kind = ENET_EVENT_TYPE_CONNECT then
430 begin
431 Result := True;
432 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_CONN]);
433 Exit;
434 end
435 else
436 if NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE then
437 enet_packet_destroy(NetMEvent.packet);
439 g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR], True);
441 NetMHost := nil;
442 NetMPeer := nil;
443 end;
445 procedure g_Net_Slist_Disconnect;
446 begin
447 if (NetMHost = nil) and (NetMPeer = nil) then Exit;
449 if NetMode = NET_SERVER then g_Net_Slist_Remove;
451 enet_peer_disconnect(NetMPeer, 0);
452 enet_host_flush(NetMHost);
454 enet_peer_reset(NetMPeer);
455 enet_host_destroy(NetMHost);
457 NetMPeer := nil;
458 NetMHost := nil;
460 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_DISC]);
461 end;
463 procedure g_Net_Slist_Check;
464 begin
465 if (NetMHost = nil) or (NetMPeer = nil) then Exit;
467 while (enet_host_service(NetMHost, @NetMEvent, 0) > 0) do
468 begin
469 if NetMEvent.kind = ENET_EVENT_TYPE_DISCONNECT then
470 begin
471 g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_SLIST_LOST], True);
472 if NetMPeer <> nil then enet_peer_reset(NetMPeer);
473 if NetMHost <> nil then enet_host_destroy(NetMHost);
474 NetMPeer := nil;
475 NetMHost := nil;
476 Break;
477 end
478 else
479 if NetMEvent.kind = ENET_EVENT_TYPE_RECEIVE then
480 enet_packet_destroy(NetMEvent.packet);
481 end;
482 end;
484 procedure g_Net_Slist_Set(IP: string; Port: Word);
485 begin
486 if NetInitDone then
487 begin
488 enet_address_set_host(@NetSlistAddr, PChar(Addr(IP[1])));
489 NetSlistAddr.Port := Port;
490 e_WriteLog('Masterserver address set to ' + IP + ':' + IntToStr(Port), TMsgType.Notify);
491 end;
492 end;
494 function GetServerFromTable(Index: Integer; SL: TNetServerList; ST: TNetServerTable): TNetServer;
495 begin
496 Result.Number := 0;
497 Result.Protocol := 0;
498 Result.Name := '';
499 Result.IP := '';
500 Result.Port := 0;
501 Result.Map := '';
502 Result.Players := 0;
503 Result.MaxPlayers := 0;
504 Result.LocalPl := 0;
505 Result.Bots := 0;
506 Result.Ping := 0;
507 Result.GameMode := 0;
508 Result.Password := false;
509 FillChar(Result.PingAddr, SizeOf(ENetAddress), 0);
510 if ST = nil then
511 Exit;
512 if (Index < 0) or (Index >= Length(ST)) then
513 Exit;
514 Result := SL[ST[Index].Indices[ST[Index].Current]];
515 end;
517 procedure g_Serverlist_Draw(var SL: TNetServerList; var ST: TNetServerTable);
518 var
519 Srv: TNetServer;
520 sy, i, y, mw, mx, l: Integer;
521 cw: Byte = 0;
522 ch: Byte = 0;
523 ww: Word = 0;
524 hh: Word = 0;
525 ip: string;
526 begin
527 ip := '';
528 sy := 0;
530 e_CharFont_GetSize(gMenuFont, _lc[I_NET_SLIST], ww, hh);
531 e_CharFont_Print(gMenuFont, (gScreenWidth div 2) - (ww div 2), 16, _lc[I_NET_SLIST]);
533 e_TextureFontGetSize(gStdFont, cw, ch);
535 ip := _lc[I_NET_SLIST_HELP];
536 mw := (Length(ip) * cw) div 2;
538 e_DrawFillQuad(16, 64, gScreenWidth-16, gScreenHeight-44, 64, 64, 64, 110);
539 e_DrawQuad(16, 64, gScreenWidth-16, gScreenHeight-44, 255, 127, 0);
541 e_TextureFontPrintEx(gScreenWidth div 2 - mw, gScreenHeight-24, ip, gStdFont, 225, 225, 225, 1);
543 if SL = nil then
544 begin
545 l := Length(slWaitStr) div 2;
546 e_DrawFillQuad(16, 64, gScreenWidth-16, gScreenHeight-44, 64, 64, 64, 128);
547 e_DrawQuad(gScreenWidth div 2 - 192, gScreenHeight div 2 - 10,
548 gScreenWidth div 2 + 192, gScreenHeight div 2 + 11, 255, 127, 0);
549 e_TextureFontPrint(gScreenWidth div 2 - cw * l, gScreenHeight div 2 - ch div 2,
550 slWaitStr, gStdFont);
551 Exit;
552 end;
554 y := 90;
555 if (slSelection < Length(ST)) then
556 begin
557 I := slSelection;
558 sy := y + 42 * I - 4;
559 Srv := GetServerFromTable(I, SL, ST);
560 ip := _lc[I_NET_ADDRESS] + ' ' + Srv.IP + ':' + IntToStr(Srv.Port);
561 if Srv.Password then
562 ip := ip + ' ' + _lc[I_NET_SERVER_PASSWORD] + ' ' + _lc[I_MENU_YES]
563 else
564 ip := ip + ' ' + _lc[I_NET_SERVER_PASSWORD] + ' ' + _lc[I_MENU_NO];
565 end else
566 if Length(ST) > 0 then
567 slSelection := 0;
569 mw := (gScreenWidth - 188);
570 mx := 16 + mw;
572 e_DrawFillQuad(16 + 1, sy, gScreenWidth - 16 - 1, sy + 40, 64, 64, 64, 0);
573 e_DrawLine(1, 16 + 1, sy, gScreenWidth - 16 - 1, sy, 205, 205, 205);
574 e_DrawLine(1, 16 + 1, sy + 41, gScreenWidth - 16 - 1, sy + 41, 255, 255, 255);
576 e_DrawLine(1, 16, 85, gScreenWidth - 16, 85, 255, 127, 0);
577 e_DrawLine(1, 16, gScreenHeight-64, gScreenWidth-16, gScreenHeight-64, 255, 127, 0);
579 e_DrawLine(1, mx - 70, 64, mx - 70, gScreenHeight-44, 255, 127, 0);
580 e_DrawLine(1, mx, 64, mx, gScreenHeight-64, 255, 127, 0);
581 e_DrawLine(1, mx + 52, 64, mx + 52, gScreenHeight-64, 255, 127, 0);
582 e_DrawLine(1, mx + 104, 64, mx + 104, gScreenHeight-64, 255, 127, 0);
584 e_TextureFontPrintEx(18, 68, 'NAME/MAP', gStdFont, 255, 127, 0, 1);
585 e_TextureFontPrintEx(mx - 68, 68, 'PING', gStdFont, 255, 127, 0, 1);
586 e_TextureFontPrintEx(mx + 2, 68, 'MODE', gStdFont, 255, 127, 0, 1);
587 e_TextureFontPrintEx(mx + 54, 68, 'PLRS', gStdFont, 255, 127, 0, 1);
588 e_TextureFontPrintEx(mx + 106, 68, 'VER', gStdFont, 255, 127, 0, 1);
590 y := 90;
591 for I := 0 to High(ST) do
592 begin
593 Srv := GetServerFromTable(I, SL, ST);
594 // Name and map
595 e_TextureFontPrintEx(18, y, Srv.Name, gStdFont, 255, 255, 255, 1);
596 e_TextureFontPrintEx(18, y + 16, Srv.Map, gStdFont, 210, 210, 210, 1);
598 // Ping and similar count
599 if (Srv.Ping < 0) or (Srv.Ping > 999) then
600 e_TextureFontPrintEx(mx - 68, y, _lc[I_NET_SLIST_NO_ACCESS], gStdFont, 255, 0, 0, 1)
601 else
602 if Srv.Ping = 0 then
603 e_TextureFontPrintEx(mx - 68, y, '<1' + _lc[I_NET_SLIST_PING_MS], gStdFont, 255, 255, 255, 1)
604 else
605 e_TextureFontPrintEx(mx - 68, y, IntToStr(Srv.Ping) + _lc[I_NET_SLIST_PING_MS], gStdFont, 255, 255, 255, 1);
607 if Length(ST[I].Indices) > 1 then
608 e_TextureFontPrintEx(mx - 68, y + 16, '< ' + IntToStr(Length(ST[I].Indices)) + ' >', gStdFont, 210, 210, 210, 1);
610 // Game mode
611 e_TextureFontPrintEx(mx + 2, y, g_Game_ModeToText(Srv.GameMode), gStdFont, 255, 255, 255, 1);
613 // Players
614 e_TextureFontPrintEx(mx + 54, y, IntToStr(Srv.Players) + '/' + IntToStr(Srv.MaxPlayers), gStdFont, 255, 255, 255, 1);
615 e_TextureFontPrintEx(mx + 54, y + 16, IntToStr(Srv.LocalPl) + '+' + IntToStr(Srv.Bots), gStdFont, 210, 210, 210, 1);
617 // Version
618 e_TextureFontPrintEx(mx + 106, y, IntToStr(Srv.Protocol), gStdFont, 255, 255, 255, 1);
620 y := y + 42;
621 end;
623 e_TextureFontPrintEx(20, gScreenHeight-61, ip, gStdFont, 205, 205, 205, 1);
624 ip := IntToStr(Length(ST)) + _lc[I_NET_SLIST_SERVERS];
625 e_TextureFontPrintEx(gScreenWidth - 48 - (Length(ip) + 1)*cw,
626 gScreenHeight-61, ip, gStdFont, 205, 205, 205, 1);
627 end;
629 procedure g_Serverlist_GenerateTable(SL: TNetServerList; var ST: TNetServerTable);
630 var
631 i, j: Integer;
633 function FindServerInTable(Name: string): Integer;
634 var
635 i: Integer;
636 begin
637 Result := -1;
638 if ST = nil then
639 Exit;
640 for i := Low(ST) to High(ST) do
641 begin
642 if Length(ST[i].Indices) = 0 then
643 continue;
644 if SL[ST[i].Indices[0]].Name = Name then
645 begin
646 Result := i;
647 Exit;
648 end;
649 end;
650 end;
651 function ComparePing(i1, i2: Integer): Boolean;
652 var
653 p1, p2: Int64;
654 begin
655 p1 := SL[i1].Ping;
656 p2 := SL[i2].Ping;
657 if (p1 < 0) then p1 := 999;
658 if (p2 < 0) then p2 := 999;
659 Result := p1 > p2;
660 end;
661 procedure SortIndices(var ind: Array of Integer);
662 var
663 I, J: Integer;
664 T: Integer;
665 begin
666 for I := High(ind) downto Low(ind) do
667 for J := Low(ind) to High(ind) - 1 do
668 if ComparePing(ind[j], ind[j+1]) then
669 begin
670 T := ind[j];
671 ind[j] := ind[j+1];
672 ind[j+1] := T;
673 end;
674 end;
675 procedure SortRows();
676 var
677 I, J: Integer;
678 T: TNetServerRow;
679 begin
680 for I := High(ST) downto Low(ST) do
681 for J := Low(ST) to High(ST) - 1 do
682 if ComparePing(ST[j].Indices[0], ST[j+1].Indices[0]) then
683 begin
684 T := ST[j];
685 ST[j] := ST[j+1];
686 ST[j+1] := T;
687 end;
688 end;
689 begin
690 ST := nil;
691 if SL = nil then
692 Exit;
693 for i := Low(SL) to High(SL) do
694 begin
695 j := FindServerInTable(SL[i].Name);
696 if j = -1 then
697 begin
698 j := Length(ST);
699 SetLength(ST, j + 1);
700 ST[j].Current := 0;
701 SetLength(ST[j].Indices, 1);
702 ST[j].Indices[0] := i;
703 end
704 else
705 begin
706 SetLength(ST[j].Indices, Length(ST[j].Indices) + 1);
707 ST[j].Indices[High(ST[j].Indices)] := i;
708 end;
709 end;
711 for i := Low(ST) to High(ST) do
712 SortIndices(ST[i].Indices);
714 SortRows();
715 end;
717 procedure g_Serverlist_Control(var SL: TNetServerList; var ST: TNetServerTable);
718 var
719 qm: Boolean;
720 Srv: TNetServer;
721 begin
722 if gConsoleShow or gChatShow then
723 Exit;
725 qm := g_ProcessMessages(); // this updates kbd
727 if qm or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
728 e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or
729 e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
730 begin
731 SL := nil;
732 ST := nil;
733 gState := STATE_MENU;
734 g_GUI_ShowWindow('MainMenu');
735 g_GUI_ShowWindow('NetGameMenu');
736 g_GUI_ShowWindow('NetClientMenu');
737 g_Sound_PlayEx(WINDOW_CLOSESOUND);
738 Exit;
739 end;
741 if e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_JUMP) or
742 e_KeyPressed(JOY0_ACTIVATE) or e_KeyPressed(JOY1_ACTIVATE) or e_KeyPressed(JOY2_ACTIVATE) or e_KeyPressed(JOY3_ACTIVATE) then
743 begin
744 if not slFetched then
745 begin
746 slWaitStr := _lc[I_NET_SLIST_WAIT];
748 g_Game_Draw;
749 g_window.ReDrawWindow;
751 if g_Net_Slist_Fetch(SL) then
752 begin
753 if SL = nil then
754 slWaitStr := _lc[I_NET_SLIST_NOSERVERS];
755 end
756 else
757 if SL = nil then
758 slWaitStr := _lc[I_NET_SLIST_ERROR];
759 slFetched := True;
760 slSelection := 0;
761 g_Serverlist_GenerateTable(SL, ST);
762 end;
763 end
764 else
765 slFetched := False;
767 if SL = nil then Exit;
769 if e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
770 e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK) then
771 begin
772 if not slReturnPressed then
773 begin
774 Srv := GetServerFromTable(slSelection, SL, ST);
775 if Srv.Password then
776 begin
777 PromptIP := Srv.IP;
778 PromptPort := Srv.Port;
779 gState := STATE_MENU;
780 g_GUI_ShowWindow('ClientPasswordMenu');
781 SL := nil;
782 ST := nil;
783 slReturnPressed := True;
784 Exit;
785 end
786 else
787 g_Game_StartClient(Srv.IP, Srv.Port, '');
788 SL := nil;
789 ST := nil;
790 slReturnPressed := True;
791 Exit;
792 end;
793 end
794 else
795 slReturnPressed := False;
797 if e_KeyPressed(IK_DOWN) or e_KeyPressed(IK_KPDOWN) or e_KeyPressed(VK_DOWN) or
798 e_KeyPressed(JOY0_DOWN) or e_KeyPressed(JOY1_DOWN) or e_KeyPressed(JOY2_DOWN) or e_KeyPressed(JOY3_DOWN) then
799 begin
800 if not slDirPressed then
801 begin
802 Inc(slSelection);
803 if slSelection > High(ST) then slSelection := 0;
804 slDirPressed := True;
805 end;
806 end;
808 if e_KeyPressed(IK_UP) or e_KeyPressed(IK_KPUP) or e_KeyPressed(VK_UP) or
809 e_KeyPressed(JOY0_UP) or e_KeyPressed(JOY1_UP) or e_KeyPressed(JOY2_UP) or e_KeyPressed(JOY3_UP) then
810 begin
811 if not slDirPressed then
812 begin
813 if slSelection = 0 then slSelection := Length(ST);
814 Dec(slSelection);
816 slDirPressed := True;
817 end;
818 end;
820 if e_KeyPressed(IK_RIGHT) or e_KeyPressed(IK_KPRIGHT) or e_KeyPressed(VK_RIGHT) or
821 e_KeyPressed(JOY0_RIGHT) or e_KeyPressed(JOY1_RIGHT) or e_KeyPressed(JOY2_RIGHT) or e_KeyPressed(JOY3_RIGHT) then
822 begin
823 if not slDirPressed then
824 begin
825 Inc(ST[slSelection].Current);
826 if ST[slSelection].Current > High(ST[slSelection].Indices) then ST[slSelection].Current := 0;
827 slDirPressed := True;
828 end;
829 end;
831 if e_KeyPressed(IK_LEFT) or e_KeyPressed(IK_KPLEFT) or e_KeyPressed(VK_LEFT) or
832 e_KeyPressed(JOY0_LEFT) or e_KeyPressed(JOY1_LEFT) or e_KeyPressed(JOY2_LEFT) or e_KeyPressed(JOY3_LEFT) then
833 begin
834 if not slDirPressed then
835 begin
836 if ST[slSelection].Current = 0 then ST[slSelection].Current := Length(ST[slSelection].Indices);
837 Dec(ST[slSelection].Current);
839 slDirPressed := True;
840 end;
841 end;
843 if (not e_KeyPressed(IK_DOWN)) and
844 (not e_KeyPressed(IK_UP)) and
845 (not e_KeyPressed(IK_RIGHT)) and
846 (not e_KeyPressed(IK_LEFT)) and
847 (not e_KeyPressed(IK_KPDOWN)) and
848 (not e_KeyPressed(IK_KPUP)) and
849 (not e_KeyPressed(IK_KPRIGHT)) and
850 (not e_KeyPressed(IK_KPLEFT)) and
851 (not e_KeyPressed(VK_DOWN)) and
852 (not e_KeyPressed(VK_UP)) and
853 (not e_KeyPressed(VK_RIGHT)) and
854 (not e_KeyPressed(VK_LEFT)) and
855 (not e_KeyPressed(JOY0_UP)) and (not e_KeyPressed(JOY1_UP)) and (not e_KeyPressed(JOY2_UP)) and (not e_KeyPressed(JOY3_UP)) and
856 (not e_KeyPressed(JOY0_DOWN)) and (not e_KeyPressed(JOY1_DOWN)) and (not e_KeyPressed(JOY2_DOWN)) and (not e_KeyPressed(JOY3_DOWN)) and
857 (not e_KeyPressed(JOY0_LEFT)) and (not e_KeyPressed(JOY1_LEFT)) and (not e_KeyPressed(JOY2_LEFT)) and (not e_KeyPressed(JOY3_LEFT)) and
858 (not e_KeyPressed(JOY0_RIGHT)) and (not e_KeyPressed(JOY1_RIGHT)) and (not e_KeyPressed(JOY2_RIGHT)) and (not e_KeyPressed(JOY3_RIGHT))
859 then
860 slDirPressed := False;
861 end;
863 end.