DEADSOFTWARE

move NilThreadId to utils
[d2df-sdl.git] / src / game / g_net.pas
index 4deca1e4c2f65356a7a2ebfcb886a0480bfb9aef..fbc85eec75c06e797d0a006a452f9c33f9e16cbe 100644 (file)
@@ -18,10 +18,10 @@ unit g_net;
 interface
 
 uses
-  e_log, e_msg, ENet, Classes, md5, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF}
+  e_log, e_msg, utils, ENet, Classes, md5, MAPDEF{$IFDEF USE_MINIUPNPC}, miniupnpc;{$ELSE};{$ENDIF}
 
 const
-  NET_PROTOCOL_VER = 182;
+  NET_PROTOCOL_VER = 185;
 
   NET_MAXCLIENTS = 24;
   NET_CHANS = 12;
@@ -72,12 +72,6 @@ const
   BANLIST_FILENAME = 'banlist.txt';
   NETDUMP_FILENAME = 'netdump';
 
-  {$IFDEF FREEBSD}
-    NilThreadId = nil;
-  {$ELSE}
-    NilThreadId = 0;
-  {$ENDIF}
-
 type
   TNetMapResourceInfo = record
     wadName: AnsiString; // wad file name, without a path
@@ -108,6 +102,7 @@ type
     Peer:     pENetPeer;
     Player:   Word;
     RequestedFullUpdate: Boolean;
+    WaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
     RCONAuth: Boolean;
     Voted:    Boolean;
     Transfer: TNetFileTransfer; // only one transfer may be active
@@ -146,9 +141,7 @@ var
   NetPongSock:   ENetSocket = ENET_SOCKET_NULL;
 
   NetUseMaster: Boolean = True;
-  NetSlistAddr: ENetAddress;
-  NetSlistIP:   string = 'mpms.doom2d.org';
-  NetSlistPort: Word = 25665;
+  NetMasterList: string = 'mpms.doom2d.org:25665, deadsoftware.ru:25665';
 
   NetClientIP:   string = '127.0.0.1';
   NetClientPort: Word   = 25666;
@@ -243,23 +236,56 @@ function g_Net_RequestResFileInfo (resIndex: LongInt; out tf: TNetFileTransfer):
 function g_Net_AbortResTransfer (var tf: TNetFileTransfer): Boolean;
 function g_Net_ReceiveResourceFile (resIndex: LongInt; var tf: TNetFileTransfer; strm: TStream): Integer;
 
+function g_Net_IsNetworkAvailable (): Boolean;
+procedure g_Net_InitLowLevel ();
+procedure g_Net_DeinitLowLevel ();
+
+procedure NetServerCVars(P: SSArray);
+
 
 implementation
 
+// *enet_host_service()*
+// fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
+// tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
+//        thank you, enet. let's ignore failures altogether then.
+
 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, ctypes,
-  g_map;
+  e_input, e_res,
+  g_nethandler, g_netmsg, g_netmaster, g_player, g_window, g_console,
+  g_main, g_game, g_language, g_weapons, ctypes, g_system, g_map;
 
 const
   FILE_CHUNK_SIZE = 8192;
 
 var
+  enet_init_success: Boolean = false;
   g_Net_DownloadTimeout: Single;
   trans_omsg: TMsg;
 
 
+function g_Net_IsNetworkAvailable (): Boolean;
+begin
+  result := enet_init_success;
+end;
+
+procedure g_Net_InitLowLevel ();
+begin
+  if enet_init_success then raise Exception.Create('wuta?!');
+  enet_init_success := (enet_initialize() = 0);
+end;
+
+procedure g_Net_DeinitLowLevel ();
+begin
+  if enet_init_success then
+  begin
+    enet_deinitialize();
+    enet_init_success := false;
+  end;
+end;
+
+
 //**************************************************************************
 //
 // SERVICE FUNCTIONS
@@ -331,6 +357,7 @@ begin
   e_LogWritefln('disconnected client #%d due to file transfer error', [nc.ID], TMsgType.Warning);
   enet_peer_disconnect(nc.Peer, NET_DISC_FILE_TIMEOUT);
   clearNetClientTransfers(nc);
+  g_Net_Slist_ServerPlayerLeaves();
 end;
 
 
@@ -498,7 +525,7 @@ begin
           killClientByFT(nc^);
           exit;
         end;
-        if (ridx < 0) then fname := MapsDir+gGameSettings.WAD else fname := {GameDir+'/wads/'+}gExternalResources[ridx].diskName;
+        if (ridx < 0) then fname := gGameSettings.WAD else fname := gExternalResources[ridx].diskName;
         if (length(fname) = 0) then
         begin
           e_WriteLog('Invalid filename: '+fname, TMsgType.Warning);
@@ -506,7 +533,6 @@ begin
           exit;
         end;
         tf.diskName := findDiskWad(fname);
-        //if (length(tf.diskName) = 0) then tf.diskName := findDiskWad(GameDir+'/wads/'+fname);
         if (length(tf.diskName) = 0) then
         begin
           e_LogWritefln('NETWORK: file "%s" not found!', [fname], TMsgType.Fatal);
@@ -628,7 +654,7 @@ begin
       begin
         e_LogWritefln('client #%d requested map info', [nc.ID]);
         trans_omsg.Clear();
-        dfn := findDiskWad(MapsDir+gGameSettings.WAD);
+        dfn := findDiskWad(gGameSettings.WAD);
         if (dfn = '') then dfn := '!wad_not_found!.wad'; //FIXME
         //md5 := MD5File(dfn);
         md5 := gWADHash;
@@ -648,7 +674,7 @@ begin
         // packet type
         trans_omsg.Write(Byte(NTF_SERVER_MAP_INFO));
         // map wad name
-        trans_omsg.Write(gGameSettings.WAD);
+        trans_omsg.Write(ExtractFileName(gGameSettings.WAD));
         // map wad md5
         trans_omsg.Write(md5);
         // map wad size
@@ -739,13 +765,15 @@ begin
     ett := getNewTimeoutEnd();
     repeat
       status := enet_host_service(NetHost, @ev, 300);
+      {
       if (status < 0) then
       begin
         g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
         Result := -1;
         exit;
       end;
-      if (status = 0) then
+      }
+      if (status <= 0) then
       begin
         // check for timeout
         ct := GetTimerMS();
@@ -918,13 +946,15 @@ begin
     ett := getNewTimeoutEnd();
     repeat
       status := enet_host_service(NetHost, @ev, 300);
+      {
       if (status < 0) then
       begin
         g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
         Result := -1;
         exit;
       end;
-      if (status = 0) then
+      }
+      if (status <= 0) then
       begin
         // check for timeout
         ct := GetTimerMS();
@@ -1100,13 +1130,15 @@ begin
     repeat
       //stx := -GetTimerMS();
       status := enet_host_service(NetHost, @ev, 300);
+      {
       if (status < 0) then
       begin
         g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_ERR_CONN] + ' network error', True);
         Result := -1;
         exit;
       end;
-      if (status = 0) then
+      }
+      if (status <= 0) then
       begin
         // check for timeout
         ct := GetTimerMS();
@@ -1274,6 +1306,7 @@ begin
     NetClients[N].Used := True;
     NetClients[N].ID := N;
     NetClients[N].RequestedFullUpdate := False;
+    NetClients[N].WaitForFirstSpawn := False;
     NetClients[N].RCONAuth := False;
     NetClients[N].Voted := False;
     NetClients[N].Player := 0;
@@ -1283,11 +1316,13 @@ begin
   Result := N;
 end;
 
+
 function g_Net_Init(): Boolean;
 var
   F: TextFile;
   IPstr: string;
   IP: LongWord;
+  path: AnsiString;
 begin
   NetIn.Clear();
   NetOut.Clear();
@@ -1302,9 +1337,10 @@ begin
   NetPlrUID2 := -1;
   NetAddr.port := 25666;
   SetLength(NetBannedHosts, 0);
-  if FileExists(DataDir + BANLIST_FILENAME) then
+  path := BANLIST_FILENAME;
+  if e_FindResource(DataDirs, path) = true then
   begin
-    Assign(F, DataDir + BANLIST_FILENAME);
+    Assign(F, path);
     Reset(F);
     while not EOF(F) do
     begin
@@ -1316,7 +1352,8 @@ begin
     g_Net_SaveBanList();
   end;
 
-  Result := (enet_initialize() = 0);
+  //Result := (enet_initialize() = 0);
+  Result := enet_init_success;
 end;
 
 procedure g_Net_Flush();
@@ -1383,7 +1420,7 @@ begin
 
   NetPeer := nil;
   NetHost := nil;
-  g_Net_Slist_Disconnect(false); // do not spam console
+  g_Net_Slist_ServerClosed();
   NetMyID := -1;
   NetPlrUID1 := -1;
   NetPlrUID2 := -1;
@@ -1411,7 +1448,7 @@ procedure g_Net_Free();
 begin
   g_Net_Cleanup();
 
-  enet_deinitialize();
+  //enet_deinitialize();
   NetInitDone := False;
 end;
 
@@ -1518,7 +1555,7 @@ begin
     end;
 
   clearNetClients(false); // don't clear array
-  if (g_Net_Slist_IsConnectionActive) then g_Net_Slist_Disconnect;
+  g_Net_Slist_ServerClosed();
   if NetPongSock <> ENET_SOCKET_NULL then
     enet_socket_destroy(NetPongSock);
 
@@ -1588,7 +1625,7 @@ begin
     NetOut.Write(Byte(Ord('F')));
     NetOut.Write(NetPort);
     NetOut.Write(ClTime);
-    g_Net_Slist_WriteInfo();
+    TMasterHost.writeInfo(NetOut);
     NPl := 0;
     if gPlayer1 <> nil then Inc(NPl);
     if gPlayer2 <> nil then Inc(NPl);
@@ -1615,8 +1652,8 @@ begin
   IP := '';
   Result := 0;
 
-  if NetUseMaster then g_Net_Slist_Check;
-  g_Net_Host_CheckPings;
+  if NetUseMaster then g_Net_Slist_Pulse();
+  g_Net_Host_CheckPings();
 
   while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
   begin
@@ -1711,6 +1748,7 @@ begin
         TC^.Peer := nil;
         TC^.Player := 0;
         TC^.RequestedFullUpdate := False;
+        TC^.WaitForFirstSpawn := False;
         TC^.NetOut[NET_UNRELIABLE].Free();
         TC^.NetOut[NET_RELIABLE].Free();
 
@@ -1719,7 +1757,7 @@ begin
         g_Console_Add(_lc[I_NET_MSG] + Format(_lc[I_NET_MSG_HOST_DISC], [ID]));
         Dec(NetClientCount);
 
-        if NetUseMaster then g_Net_Slist_Update;
+        if NetUseMaster then g_Net_Slist_ServerPlayerLeaves();
       end;
     end;
   end;
@@ -1799,7 +1837,7 @@ end;
 function g_Net_Client_Update(): enet_size_t;
 begin
   Result := 0;
-  while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
+  while (NetHost <> nil) and (enet_host_service(NetHost, @NetEvent, 0) > 0) do
   begin
     case NetEvent.kind of
       ENET_EVENT_TYPE_RECEIVE:
@@ -1896,7 +1934,7 @@ begin
   end;
 
   // предупредить что ждем слишком долго через N секунд
-  TimeoutTime := GetTimer() + NET_CONNECT_TIMEOUT;
+  TimeoutTime := sys_GetTicks() + NET_CONNECT_TIMEOUT;
 
   OuterLoop := True;
   while OuterLoop do
@@ -1917,7 +1955,7 @@ begin
       end;
     end;
 
-    T := GetTimer();
+    T := sys_GetTicks();
     if T > TimeoutTime then
     begin
       TimeoutTime := T + NET_CONNECT_TIMEOUT * 100; // одного предупреждения хватит
@@ -2141,22 +2179,28 @@ procedure g_Net_SaveBanList();
 var
   F: TextFile;
   I: Integer;
+  path: AnsiString;
 begin
-  Assign(F, DataDir + BANLIST_FILENAME);
-  Rewrite(F);
-  if NetBannedHosts <> nil then
-    for I := 0 to High(NetBannedHosts) do
-      if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then
-        Writeln(F, IpToStr(NetBannedHosts[I].IP));
-  CloseFile(F);
+  path := e_GetWriteableDir(DataDirs);
+  if path <> '' then
+  begin
+    path := e_CatPath(path, BANLIST_FILENAME);
+    Assign(F, path);
+    Rewrite(F);
+    if NetBannedHosts <> nil then
+      for I := 0 to High(NetBannedHosts) do
+        if NetBannedHosts[I].Perm and (NetBannedHosts[I].IP > 0) then
+          Writeln(F, IpToStr(NetBannedHosts[I].IP));
+    CloseFile(F)
+  end
 end;
 
 procedure g_Net_DumpStart();
 begin
   if NetMode = NET_SERVER then
-    NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_server')
+    NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_server')
   else
-    NetDumpFile := createDiskFile(NETDUMP_FILENAME + '_client');
+    NetDumpFile := e_CreateResource(LogDirs, NETDUMP_FILENAME + '_client');
 end;
 
 procedure g_Net_DumpSendBuffer();
@@ -2246,12 +2290,12 @@ begin
 
     if I <> 0 then
     begin
-      conwritefln('forwarding port %d failed: error %d', [NetPort + 1, I]);
+      conwritefln('forwarding port %d failed: error %d', [NET_PING_PORT, I]);
       NetPongForwarded := False;
     end
     else
     begin
-      conwritefln('forwarded port %d successfully', [NetPort + 1]);
+      conwritefln('forwarded port %d successfully', [NET_PING_PORT]);
       NetPongForwarded := True;
     end;
   end;
@@ -2291,12 +2335,12 @@ begin
   if NetPongForwarded then
   begin
     NetPongForwarded := False;
-    StrPort := IntToStr(NetPortForwarded + 1);
+    StrPort := IntToStr(NET_PING_PORT);
     I := UPNP_DeletePortMapping(
       PChar(NetIGDControl), Addr(NetIGDService[1]),
       PChar(StrPort), PChar('UDP'), nil
     );
-    conwritefln('  port %d: %d', [NetPortForwarded + 1, I]);
+    conwritefln('  port %d: %d', [NET_PING_PORT, I]);
   end;
 
   NetPortForwarded := 0;
@@ -2306,9 +2350,102 @@ begin
 end;
 {$ENDIF}
 
+procedure NetServerCVars(P: SSArray);
+var
+  cmd, s: string;
+  a, b: Integer;
+begin
+  cmd := LowerCase(P[0]);
+  case cmd of
+    'sv_name':
+      begin
+        if (Length(P) > 1) and (Length(P[1]) > 0) then
+        begin
+          NetServerName := P[1];
+          if Length(NetServerName) > 64 then
+            SetLength(NetServerName, 64);
+          g_Net_Slist_ServerRenamed();
+        end;
+        g_Console_Add(cmd + ' "' + NetServerName + '"');
+      end;
+    'sv_passwd':
+      begin
+        if (Length(P) > 1) and (Length(P[1]) > 0) then
+        begin
+          NetPassword := P[1];
+          if Length(NetPassword) > 24 then
+            SetLength(NetPassword, 24);
+          g_Net_Slist_ServerRenamed();
+        end;
+        g_Console_Add(cmd + ' "' + AnsiLowerCase(NetPassword) + '"');
+      end;
+    'sv_maxplrs':
+      begin
+        if (Length(P) > 1) then
+        begin
+          NetMaxClients := nclamp(StrToIntDef(P[1], NetMaxClients), 1, NET_MAXCLIENTS);
+          if g_Game_IsServer and g_Game_IsNet then
+          begin
+            b := 0;
+            for a := 0 to High(NetClients) do
+            begin
+              if NetClients[a].Used then
+              begin
+                Inc(b);
+                if b > NetMaxClients then
+                begin
+                  s := g_Player_Get(NetClients[a].Player).Name;
+                  enet_peer_disconnect(NetClients[a].Peer, NET_DISC_FULL);
+                  g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
+                  MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
+                end;
+              end;
+            end;
+            g_Net_Slist_ServerRenamed();
+          end;
+        end;
+        g_Console_Add(cmd + ' ' + IntToStr(NetMaxClients));
+      end;
+    'sv_public':
+      begin
+        if (Length(P) > 1) then
+        begin
+          NetUseMaster := StrToIntDef(P[1], Byte(NetUseMaster)) <> 0;
+          if NetUseMaster then g_Net_Slist_Public() else g_Net_Slist_Private();
+        end;
+        g_Console_Add(cmd + ' ' + IntToStr(Byte(NetUseMaster)));
+      end;
+    'sv_port':
+      begin
+        if (Length(P) > 1) then
+        begin
+          if not g_Game_IsNet then
+            NetPort := nclamp(StrToIntDef(P[1], NetPort), 0, $FFFF)
+          else
+            g_Console_Add(_lc[I_MSG_NOT_NETGAME]);
+        end;
+        g_Console_Add(cmd + ' ' + IntToStr(Ord(NetUseMaster)));
+      end;
+  end;
+end;
 
 initialization
   conRegVar('cl_downloadtimeout', @g_Net_DownloadTimeout, 0.0, 1000000.0, '', 'timeout in seconds, 0 to disable it');
+  conRegVar('cl_predictself', @NetPredictSelf, '', 'predict local player');
+  conRegVar('cl_forceplayerupdate', @NetForcePlayerUpdate, '', 'update net players on NET_MSG_PLRPOS');
+  conRegVar('cl_interp', @NetInterpLevel, '', 'net player interpolation steps');
+  conRegVar('cl_last_ip', @NetClientIP, '', 'address of the last you have connected to');
+  conRegVar('cl_last_port', @NetClientPort, '', 'port of the last server you have connected to');
+
+  conRegVar('sv_forwardports', @NetForwardPorts, '', 'forward server port using miniupnpc (requires server restart)');
+  conRegVar('sv_rcon', @NetAllowRCON, '', 'enable remote console');
+  conRegVar('sv_rcon_password', @NetRCONPassword, '', 'remote console password');
+  conRegVar('sv_update_interval', @NetUpdateRate, '', 'unreliable update interval');
+  conRegVar('sv_reliable_interval', @NetRelupdRate, '', 'reliable update interval');
+  conRegVar('sv_master_interval', @NetMasterRate, '', 'master server update interval');
+
+  conRegVar('net_master_list', @NetMasterList, '', 'list of master servers');
+
   SetLength(NetClients, 0);
   g_Net_DownloadTimeout := 60;
   NetIn.Alloc(NET_BUFSIZE);