diff --git a/src/game/g_net.pas b/src/game/g_net.pas
index 7c21ea5a156bb1ed7fcfff2064b718c7f606e1c2..83c8398991ff1ed0429f97212a1f80df69f55440 100644 (file)
--- a/src/game/g_net.pas
+++ b/src/game/g_net.pas
e_log, e_msg, ENet, Classes, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF}
const
- NET_PROTOCOL_VER = 179;
+ NET_PROTOCOL_VER = 181;
NET_MAXCLIENTS = 24;
NET_CHANS = 11;
NET_EVERYONE = -1;
+ NET_UNRELIABLE = 0;
+ NET_RELIABLE = 1;
+
NET_DISC_NONE: enet_uint32 = 0;
NET_DISC_PROTOCOL: enet_uint32 = 1;
NET_DISC_VERSION: enet_uint32 = 2;
NET_STATE_AUTH = 1;
NET_STATE_GAME = 2;
+ NET_CONNECT_TIMEOUT = 1000 * 10;
+
BANLIST_FILENAME = 'banlist.txt';
NETDUMP_FILENAME = 'netdump';
type
TNetClient = record
- ID: Byte;
- Used: Boolean;
- State: Byte;
- Peer: pENetPeer;
- Player: Word;
+ ID: Byte;
+ Used: Boolean;
+ State: Byte;
+ Peer: pENetPeer;
+ Player: Word;
RequestedFullUpdate: Boolean;
RCONAuth: Boolean;
Voted: Boolean;
+ NetOut: array [0..1] of TMsg;
end;
TBanRecord = record
IP: LongWord;
NetClientPort: Word = 25666;
NetIn, NetOut: TMsg;
+ NetBuf: array [0..1] of TMsg;
NetClients: array of TNetClient;
NetClientCount: Byte = 0;
uses
SysUtils,
e_input, g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console,
- g_main, g_game, g_language, g_weapons, utils;
+ g_main, g_game, g_language, g_weapons, utils, ctypes;
+
+var
+ g_Net_DownloadTimeout: Single;
{ /// SERVICE FUNCTIONS /// }
begin
NetIn.Clear();
NetOut.Clear();
+ NetBuf[NET_UNRELIABLE].Clear();
+ NetBuf[NET_RELIABLE].Clear();
SetLength(NetClients, 0);
NetPeer := nil;
NetHost := nil;
end;
procedure g_Net_Flush();
+var
+ T: Integer;
+ P: pENetPacket;
+ F, Chan: enet_uint32;
+ I: Integer;
begin
- enet_host_flush(NetHost);
+ F := 0;
+ Chan := NET_CHAN_GAME;
+
+ if NetMode = NET_SERVER then
+ for T := NET_UNRELIABLE to NET_RELIABLE do
+ begin
+ if NetBuf[T].CurSize > 0 then
+ begin
+ P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
+ if not Assigned(P) then continue;
+ enet_host_broadcast(NetHost, Chan, P);
+ NetBuf[T].Clear();
+ end;
+
+ for I := Low(NetClients) to High(NetClients) do
+ begin
+ if not NetClients[I].Used then continue;
+ if NetClients[I].NetOut[T].CurSize <= 0 then continue;
+ P := enet_packet_create(NetClients[I].NetOut[T].Data, NetClients[I].NetOut[T].CurSize, F);
+ if not Assigned(P) then continue;
+ enet_peer_send(NetClients[I].Peer, Chan, P);
+ NetClients[I].NetOut[T].Clear();
+ end;
+
+ // next and last iteration is always RELIABLE
+ F := LongWord(ENET_PACKET_FLAG_RELIABLE);
+ Chan := NET_CHAN_IMPORTANT;
+ end
+ else if NetMode = NET_CLIENT then
+ for T := NET_UNRELIABLE to NET_RELIABLE do
+ begin
+ if NetBuf[T].CurSize > 0 then
+ begin
+ P := enet_packet_create(NetBuf[T].Data, NetBuf[T].CurSize, F);
+ if not Assigned(P) then continue;
+ enet_peer_send(NetPeer, Chan, P);
+ NetBuf[T].Clear();
+ end;
+ // next and last iteration is always RELIABLE
+ F := LongWord(ENET_PACKET_FLAG_RELIABLE);
+ Chan := NET_CHAN_IMPORTANT;
+ end;
end;
procedure g_Net_Cleanup();
begin
NetIn.Clear();
NetOut.Clear();
+ NetBuf[NET_UNRELIABLE].Clear();
+ NetBuf[NET_RELIABLE].Clear();
SetLength(NetClients, 0);
NetClientCount := 0;
NetMode := NET_SERVER;
NetOut.Clear();
+ NetBuf[NET_UNRELIABLE].Clear();
+ NetBuf[NET_RELIABLE].Clear();
if NetDump then
g_Net_DumpStart();
enet_peer_reset(NetClients[I].Peer);
NetClients[I].Peer := nil;
NetClients[I].Used := False;
+ NetClients[I].NetOut[NET_UNRELIABLE].Free();
+ NetClients[I].NetOut[NET_RELIABLE].Free();
end;
if (NetMPeer <> nil) and (NetMHost <> nil) then g_Net_Slist_Disconnect;
procedure g_Net_Host_Send(ID: Integer; Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
var
- P: pENetPacket;
- F: enet_uint32;
+ T: Integer;
begin
if (Reliable) then
- F := LongWord(ENET_PACKET_FLAG_RELIABLE)
+ T := NET_RELIABLE
else
- F := 0;
+ T := NET_UNRELIABLE;
if (ID >= 0) then
begin
if ID > High(NetClients) then Exit;
if NetClients[ID].Peer = nil then Exit;
-
- P := enet_packet_create(NetOut.Data, NetOut.CurSize, F);
- if not Assigned(P) then Exit;
-
- enet_peer_send(NetClients[ID].Peer, Chan, P);
+ // write size first
+ NetClients[ID].NetOut[T].Write(Integer(NetOut.CurSize));
+ NetClients[ID].NetOut[T].Write(NetOut);
end
else
begin
- P := enet_packet_create(NetOut.Data, NetOut.CurSize, F);
- if not Assigned(P) then Exit;
-
- enet_host_broadcast(NetHost, Chan, P);
+ // write size first
+ NetBuf[T].Write(Integer(NetOut.CurSize));
+ NetBuf[T].Write(NetOut);
end;
if NetDump then g_Net_DumpSendBuffer();
- g_Net_Flush();
NetOut.Clear();
end;
Byte(NetClients[ID].Peer^.data^) := ID;
NetClients[ID].State := NET_STATE_AUTH;
NetClients[ID].RCONAuth := False;
+ NetClients[ID].NetOut[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
+ NetClients[ID].NetOut[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
enet_peer_timeout(NetEvent.peer, ENET_PEER_TIMEOUT_LIMIT * 2, ENET_PEER_TIMEOUT_MINIMUM * 2, ENET_PEER_TIMEOUT_MAXIMUM * 2);
TC := @NetClients[ID];
if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
- g_Net_HostMsgHandler(TC, NetEvent.packet);
+ g_Net_Host_HandlePacket(TC, NetEvent.packet, g_Net_HostMsgHandler);
end;
ENET_EVENT_TYPE_DISCONNECT:
TC^.Peer := nil;
TC^.Player := 0;
TC^.RequestedFullUpdate := False;
+ TC^.NetOut[NET_UNRELIABLE].Free();
+ TC^.NetOut[NET_RELIABLE].Free();
FreeMemory(NetEvent.peer^.data);
NetEvent.peer^.data := nil;
procedure g_Net_Client_Send(Reliable: Boolean; Chan: Byte = NET_CHAN_GAME);
var
- P: pENetPacket;
- F: enet_uint32;
+ T: Integer;
begin
if (Reliable) then
- F := LongWord(ENET_PACKET_FLAG_RELIABLE)
+ T := NET_RELIABLE
else
- F := 0;
+ T := NET_UNRELIABLE;
- P := enet_packet_create(NetOut.Data, NetOut.CurSize, F);
- if not Assigned(P) then Exit;
+ // write size first
+ NetBuf[T].Write(Integer(NetOut.CurSize));
+ NetBuf[T].Write(NetOut);
- enet_peer_send(NetPeer, Chan, P);
if NetDump then g_Net_DumpSendBuffer();
- g_Net_Flush();
NetOut.Clear();
+ g_Net_Flush(); // FIXME: for now, send immediately
end;
function g_Net_Client_Update(): enet_size_t;
ENET_EVENT_TYPE_RECEIVE:
begin
if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
- g_Net_ClientMsgHandler(NetEvent.packet);
+ g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientMsgHandler);
end;
ENET_EVENT_TYPE_DISCONNECT:
ENET_EVENT_TYPE_RECEIVE:
begin
if NetDump then g_Net_DumpRecvBuffer(NetEvent.packet^.data, NetEvent.packet^.dataLength);
- g_Net_ClientLightMsgHandler(NetEvent.packet);
+ g_Net_Client_HandlePacket(NetEvent.packet, g_Net_ClientLightMsgHandler);
end;
ENET_EVENT_TYPE_DISCONNECT:
function g_Net_Connect(IP: string; Port: enet_uint16): Boolean;
var
OuterLoop: Boolean;
+ TimeoutTime, T: Int64;
begin
if NetMode <> NET_NONE then
begin
Exit;
end;
+ // предупредить что ждем слишком долго через N секунд
+ TimeoutTime := GetTimer() + NET_CONNECT_TIMEOUT;
+
OuterLoop := True;
while OuterLoop do
begin
end;
end;
+ T := GetTimer();
+ if T > TimeoutTime then
+ begin
+ TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
+ g_Console_Add(_lc[I_NET_MSG_TIMEOUT_WARN], True);
+ g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
+ end;
+
ProcessLoading(true);
- if e_KeyPressed(IK_ESCAPE) or e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_ESCAPE) then
+ if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
+ e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
OuterLoop := False;
end;
g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_TIMEOUT], True);
+ g_Console_Add(Format(_lc[I_NET_MSG_PORTS], [Integer(Port), Integer(NET_PING_PORT)]), True);
if NetPeer <> nil then enet_peer_reset(NetPeer);
if NetHost <> nil then
begin
enet_host_flush(NetHost);
end;
+function UserRequestExit: Boolean;
+begin
+ Result := e_KeyPressed(IK_SPACE) or
+ e_KeyPressed(IK_ESCAPE) or
+ e_KeyPressed(VK_ESCAPE) or
+ e_KeyPressed(JOY0_JUMP) or
+ e_KeyPressed(JOY1_JUMP) or
+ e_KeyPressed(JOY2_JUMP) or
+ e_KeyPressed(JOY3_JUMP)
+end;
+
function g_Net_Wait_Event(msgId: Word): TMemoryStream;
-var
- downloadEvent: ENetEvent;
- OuterLoop: Boolean;
- MID: Byte;
- Ptr: Pointer;
- msgStream: TMemoryStream;
+ var
+ ev: ENetEvent;
+ rMsgId: Byte;
+ Ptr: Pointer;
+ stream: TMemoryStream;
+ status: cint;
begin
- FillChar(downloadEvent, SizeOf(downloadEvent), 0);
- msgStream := nil;
- OuterLoop := True;
- while OuterLoop do
- begin
- while (enet_host_service(NetHost, @downloadEvent, 0) > 0) do
+ FillChar(ev, SizeOf(ev), 0);
+ stream := nil;
+ repeat
+ status := enet_host_service(NetHost, @ev, Trunc(g_Net_DownloadTimeout * 1000));
+ if status > 0 then
begin
- if (downloadEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
- begin
- Ptr := downloadEvent.packet^.data;
-
- MID := Byte(Ptr^);
-
- if (MID = msgId) then
+ case ev.kind of
+ ENET_EVENT_TYPE_RECEIVE:
+ begin
+ Ptr := ev.packet^.data;
+ rMsgId := Byte(Ptr^);
+ if rMsgId = msgId then
+ begin
+ stream := TMemoryStream.Create;
+ stream.SetSize(ev.packet^.dataLength);
+ stream.WriteBuffer(Ptr^, ev.packet^.dataLength);
+ stream.Seek(0, soFromBeginning);
+ status := 1 (* received *)
+ end
+ else
+ begin
+ (* looks that game state always received, so ignore it *)
+ e_LogWritefln('g_Net_Wait_Event(%s): skip message %s', [msgId, rMsgId]);
+ status := 2 (* continue *)
+ end
+ end;
+ ENET_EVENT_TYPE_DISCONNECT:
begin
- msgStream := TMemoryStream.Create;
- msgStream.SetSize(downloadEvent.packet^.dataLength);
- msgStream.WriteBuffer(Ptr^, downloadEvent.packet^.dataLength);
- msgStream.Seek(0, soFromBeginning);
-
- OuterLoop := False;
- enet_packet_destroy(downloadEvent.packet);
- break;
- end
- else begin
- enet_packet_destroy(downloadEvent.packet);
+ if (ev.data <= NET_DISC_MAX) then
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' + _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + ev.data)], True);
+ status := -2 (* error: disconnected *)
end;
- end
else
- if (downloadEvent.kind = ENET_EVENT_TYPE_DISCONNECT) then
- begin
- if (downloadEvent.data <= NET_DISC_MAX) then
- g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' ' +
- _lc[TStrings_Locale(Cardinal(I_NET_DISC_NONE) + downloadEvent.data)], True);
- OuterLoop := False;
- Break;
- end;
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' unknown ENet event ' + IntToStr(Ord(ev.kind)), True);
+ status := -3 (* error: unknown event *)
+ end;
+ enet_packet_destroy(ev.packet)
+ end
+ else
+ begin
+ g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' timeout reached', True);
+ status := 0 (* error: timeout *)
end;
-
ProcessLoading(true);
-
- if e_KeyPressed(IK_ESCAPE) or e_KeyPressed(IK_SPACE) or e_KeyPressed(VK_ESCAPE) then
- break;
- end;
- Result := msgStream;
+ until (status <> 2) or UserRequestExit();
+ Result := stream
end;
function g_Net_IsHostBanned(IP: LongWord; Perm: Boolean = False): Boolean;
{$ENDIF}
initialization
-
+ conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
+ g_Net_DownloadTimeout := 60;
NetIn.Alloc(NET_BUFSIZE);
NetOut.Alloc(NET_BUFSIZE);
-
+ NetBuf[NET_UNRELIABLE].Alloc(NET_BUFSIZE*2);
+ NetBuf[NET_RELIABLE].Alloc(NET_BUFSIZE*2);
finalization
-
NetIn.Free();
NetOut.Free();
-
+ NetBuf[NET_UNRELIABLE].Free();
+ NetBuf[NET_RELIABLE].Free();
end.