DEADSOFTWARE

net: started master-server communication rewrite (phase 1: master i/o moved to separa...
[d2df-sdl.git] / src / game / g_game.pas
index d8aab5c9eb7b30a89f8152d38561c733f9d54b8b..7ad130f20a6b52c6a3029bb81de27ab5cab4c020 100644 (file)
@@ -2,8 +2,7 @@
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, version 3 of the License ONLY.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -102,7 +101,7 @@ procedure g_Game_StartClient(Addr: String; Port: Word; PW: String);
 procedure g_Game_Restart();
 procedure g_Game_RestartLevel();
 procedure g_Game_RestartRound(NoMapRestart: Boolean = False);
-procedure g_Game_ClientWAD(NewWAD: String; WHash: TMD5Digest);
+procedure g_Game_ClientWAD(NewWAD: String; const WHash: TMD5Digest);
 procedure g_Game_SaveOptions();
 function  g_Game_StartMap(Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
 procedure g_Game_ChangeMap(const MapPath: String);
@@ -142,6 +141,7 @@ procedure g_Game_StepLoading(Value: Integer = -1);
 procedure g_Game_ClearLoading();
 procedure g_Game_SetDebugMode();
 procedure DrawLoadingStat();
+procedure DrawMenuBackground(tex: AnsiString);
 
 { procedure SetWinPause(Enable: Boolean); }
 
@@ -210,12 +210,17 @@ const
   ANNOUNCE_ALL    = 3;
 
   CONFIG_FILENAME = 'Doom2DF.cfg';
-  LOG_FILENAME = 'Doom2DF.log';
 
   TEST_MAP_NAME = '$$$_TEST_$$$';
 
   STD_PLAYER_MODEL = 'Doomer';
 
+{$IFDEF HEADLESS}
+  DEFAULT_PLAYERS = 0;
+{$ELSE}
+  DEFAULT_PLAYERS = 1;
+{$ENDIF}
+
 var
   gStdFont: DWORD;
   gGameSettings: TGameSettings;
@@ -250,7 +255,7 @@ var
   gTotalMonsters: Integer = 0;
   gPauseMain: Boolean = false;
   gPauseHolmes: Boolean = false;
-  gShowTime: Boolean = True;
+  gShowTime: Boolean = False;
   gShowFPS: Boolean = False;
   gShowGoals: Boolean = True;
   gShowStat: Boolean = True;
@@ -326,6 +331,7 @@ var
   gUseChatSounds: Boolean = True;
   gChatSounds: Array of TChatSound;
   gSelectWeapon: Array [0..1, WP_FIRST..WP_LAST] of Boolean; // [player, weapon]
+  gInterReadyCount: Integer = 0;
 
   g_dbg_ignore_bounds: Boolean = false;
   r_smallmap_h: Integer = 0; // 0: left; 1: center; 2: right
@@ -377,7 +383,7 @@ uses
   g_triggers, g_monsters, e_sound, CONFIG,
   g_language, g_net,
   ENet, e_msg, g_netmsg, g_netmaster,
-  sfs, wadreader;
+  sfs, wadreader, g_system;
 
 
 var
@@ -578,6 +584,7 @@ var
   MessageLineLength: Integer = 80;
   MapList: SSArray = nil;
   MapIndex: Integer = -1;
+  InterReadyTime: Integer = -1;
   MegaWAD: record
     info: TMegaWADInfo;
     endpic: String;
@@ -806,7 +813,9 @@ begin
   begin
     s := g_ExtractWadName(MegaWAD.endpic);
     if s = '' then s := MapsDir+WAD else s := GameDir+'/wads/';
+    TEXTUREFILTER := GL_LINEAR;
     g_Texture_CreateWADEx('TEXTURE_endpic', s+MegaWAD.endpic);
+    TEXTUREFILTER := GL_NEAREST;
   end;
   MegaWAD.endmus := cfg.ReadStr('megawad', 'endmus', 'Standart.wad:D2DMUS\ÊÎÍÅÖ');
   if MegaWAD.endmus <> '' then
@@ -885,7 +894,7 @@ begin
   gDelayedEvents[n].DENum := Num;
   gDelayedEvents[n].DEStr := Str;
   if DEType = DE_GLOBEVENT then
-    gDelayedEvents[n].Time := (GetTimer() {div 1000}) + Time
+    gDelayedEvents[n].Time := (sys_GetTicks() {div 1000}) + Time
   else
     gDelayedEvents[n].Time := gTime + Time;
   Result := n;
@@ -978,6 +987,7 @@ begin
               SetLength(CustomStat.PlayerStat, Length(CustomStat.PlayerStat)+1);
               with CustomStat.PlayerStat[High(CustomStat.PlayerStat)] do
               begin
+                Num := a;
                 Name := gPlayers[a].Name;
                 Frags := gPlayers[a].Frags;
                 Deaths := gPlayers[a].Death;
@@ -992,6 +1002,8 @@ begin
         end;
 
         g_Game_ExecuteEvent('onmapend');
+        if not g_Game_IsClient then g_Player_ResetReady;
+        gInterReadyCount := 0;
 
       // Çàòóõàþùèé ýêðàí:
         EndingGameCounter := 255;
@@ -1271,10 +1283,12 @@ begin
   sfsGCDisable(); // temporary disable removing of temporary volumes
 
   try
+    TEXTUREFILTER := GL_LINEAR;
     g_Texture_CreateWADEx('MENU_BACKGROUND', GameWAD+':TEXTURES\TITLE');
     g_Texture_CreateWADEx('INTER', GameWAD+':TEXTURES\INTER');
     g_Texture_CreateWADEx('ENDGAME_EN', GameWAD+':TEXTURES\ENDGAME_EN');
     g_Texture_CreateWADEx('ENDGAME_RU', GameWAD+':TEXTURES\ENDGAME_RU');
+    TEXTUREFILTER := GL_NEAREST;
 
     LoadStdFont('STDTXT', 'STDFONT', gStdFont);
     LoadFont('MENUTXT', 'MENUFONT', gMenuFont);
@@ -1324,11 +1338,13 @@ begin
     g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
     g_Sound_CreateWADEx('MUSIC_INTERMUS', GameWAD+':MUSIC\INTERMUS', True);
     g_Sound_CreateWADEx('MUSIC_MENU', GameWAD+':MUSIC\MENU', True);
-    g_Sound_CreateWADEx('MUSIC_ROUNDMUS', GameWAD+':MUSIC\ROUNDMUS', True);
+    g_Sound_CreateWADEx('MUSIC_ROUNDMUS', GameWAD+':MUSIC\ROUNDMUS', True, True);
     g_Sound_CreateWADEx('MUSIC_STDENDMUS', GameWAD+':MUSIC\ENDMUS', True);
 
+{$IFNDEF HEADLESS}
     g_Game_SetLoadingText(_lc[I_LOAD_MENUS], 0, False);
     g_Menu_Init();
+{$ENDIF}
 
     gMusic := TMusic.Create();
     gMusic.SetByName('MUSIC_MENU');
@@ -1590,6 +1606,14 @@ begin
   if gwin_has_stencil and g_playerLight then g_AddDynLight(plr.GameX+32, plr.GameY+40, 128, 1, 1, 0, 0.6);
 end;
 
+// HACK: don't have a "key was pressed" function
+procedure InterReady();
+begin
+  if InterReadyTime > gTime then Exit;
+  InterReadyTime := gTime + 3000;
+  MC_SEND_CheatRequest(NET_CHEAT_READY);
+end;
+
 procedure g_Game_Update();
 var
   Msg: g_gui.TMessage;
@@ -1682,7 +1706,7 @@ begin
             and (not gJustChatted) and (not gConsoleShow) and (not gChatShow)
             and (g_ActiveWindow = nil)
           )
-          or (g_Game_IsNet and (gInterTime > gInterEndTime))
+          or (g_Game_IsNet and ((gInterTime > gInterEndTime) or (gInterReadyCount >= NetClientCount)))
         )
         then
         begin // Íàæàëè <Enter>/<Ïðîáåë> èëè ïðîøëî äîñòàòî÷íî âðåìåíè:
@@ -1717,6 +1741,22 @@ begin
             end;
 
           Exit;
+        end
+        else if g_Game_IsClient and
+        (
+          (
+            e_KeyPressed(IK_RETURN) or e_KeyPressed(IK_KPRETURN) or e_KeyPressed(IK_SPACE) or
+            e_KeyPressed(VK_FIRE) or e_KeyPressed(VK_OPEN) or
+            e_KeyPressed(JOY0_ATTACK) or e_KeyPressed(JOY1_ATTACK) or
+            e_KeyPressed(JOY2_ATTACK) or e_KeyPressed(JOY3_ATTACK)
+          )
+          and (not gJustChatted) and (not gConsoleShow) and (not gChatShow)
+          and (g_ActiveWindow = nil)
+        )
+        then
+        begin
+          // ready / unready
+          InterReady();
         end;
 
         if gState = STATE_INTERTEXT then
@@ -1731,6 +1771,7 @@ begin
           // Çàêîí÷èëñÿ óðîâåíü â Ñâîåé èãðå:
             if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
               begin
+                InterReadyTime := -1;
                 if gLastMap and (gGameSettings.GameMode = GM_COOP) then
                 begin
                   g_Game_ExecuteEvent('onwadend');
@@ -2085,16 +2126,15 @@ begin
 
       if NetUseMaster then
       begin
-        if gTime >= NetTimeToMaster then
+        {
+        if (gTime >= NetTimeToMaster) or g_Net_Slist_IsConnectionInProgress then
         begin
-          if (NetMHost = nil) or (NetMPeer = nil) then
-          begin
-            if not g_Net_Slist_Connect then g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR]);
-          end;
-
+          if (not g_Net_Slist_IsConnectionActive) then g_Net_Slist_Connect(false); // non-blocking connection to the master
           g_Net_Slist_Update;
           NetTimeToMaster := gTime + NetMasterRate;
         end;
+        }
+        g_Net_Slist_Pulse();
       end;
     end
     else if (NetMode = NET_CLIENT) then
@@ -2134,7 +2174,9 @@ begin
       //e_WriteLog('Read language file', MSG_NOTIFY);
       //g_Language_Load(DataDir + gLanguage + '.txt');
       g_Language_Set(gLanguage);
+{$IFNDEF HEADLESS}
       g_Menu_Reset();
+{$ENDIF}
       gLanguageChange := False;
     end;
   end;
@@ -2148,7 +2190,7 @@ begin
     KeyPress(IK_F10);
   end;
 
-  Time := GetTimer() {div 1000};
+  Time := sys_GetTicks() {div 1000};
 
 // Îáðàáîòêà îòëîæåííûõ ñîáûòèé:
   if gDelayedEvents <> nil then
@@ -2504,7 +2546,7 @@ var
 begin
   e_TextureFontGetSize(gStdFont, ww2, hh2);
 
-  g_ProcessMessages();
+  sys_HandleInput;
 
   if g_Console_Action(ACTION_SCORES) then
   begin
@@ -2692,9 +2734,12 @@ begin
               gg := g;
               bb := b;
             end;
-            e_TextureFontPrintEx(x+8, _y, Name, gStdFont, rr, gg, bb, 1);
-            e_TextureFontPrintEx(x+w1+8, _y, IntToStr(Frags), gStdFont, rr, gg, bb, 1);
-            e_TextureFontPrintEx(x+w1+w2+8, _y, IntToStr(Deaths), gStdFont, rr, gg, bb, 1);
+            if (gPlayers[Num] <> nil) and (gPlayers[Num].FReady) then
+              e_TextureFontPrintEx(x+16, _y, Name + ' *', gStdFont, rr, gg, bb, 1)
+            else
+              e_TextureFontPrintEx(x+16, _y, Name, gStdFont, rr, gg, bb, 1);
+            e_TextureFontPrintEx(x+w1+16, _y, IntToStr(Frags), gStdFont, rr, gg, bb, 1);
+            e_TextureFontPrintEx(x+w1+w2+16, _y, IntToStr(Deaths), gStdFont, rr, gg, bb, 1);
             _y := _y+24;
           end;
 
@@ -2719,7 +2764,10 @@ begin
         else
           r := 255;
 
-        e_TextureFontPrintEx(x+8+16+8, _y+4, Name, gStdFont, r, r, r, 1, True);
+        if (gPlayers[Num] <> nil) and (gPlayers[Num].FReady) then
+          e_TextureFontPrintEx(x+8+16+8, _y+4, Name + ' *', gStdFont, r, r, r, 1, True)
+        else
+          e_TextureFontPrintEx(x+8+16+8, _y+4, Name, gStdFont, r, r, r, 1, True);
         e_TextureFontPrintEx(x+w1+8+16+8, _y+4, IntToStr(Frags), gStdFont, r, r, r, 1, True);
         e_TextureFontPrintEx(x+w1+w2+8+16+8, _y+4, IntToStr(Deaths), gStdFont, r, r, r, 1, True);
         _y := _y+24;
@@ -2966,6 +3014,25 @@ begin
   end;
 end;
 
+procedure DrawMenuBackground(tex: AnsiString);
+var
+  w, h: Word;
+  ID: DWord;
+
+begin
+  if g_Texture_Get(tex, ID) then
+  begin
+    e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
+    e_GetTextureSize(ID, @w, @h);
+    if w = h then
+      w := round(w * 1.333 * (gScreenHeight / h))
+    else
+      w := trunc(w * (gScreenHeight / h));
+    e_DrawSize(ID, (gScreenWidth - w) div 2, 0, 0, False, False, w, gScreenHeight);
+  end
+  else e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
+end;
+
 procedure DrawMinimap(p: TPlayer; RenderRect: e_graphics.TRect);
 var
   a, aX, aY, aX2, aY2, Scale, ScaleSz: Integer;
@@ -3443,7 +3510,7 @@ end;
 
 procedure DrawPlayer(p: TPlayer);
 var
-  px, py, a, b, c, d: Integer;
+  px, py, a, b, c, d, i: Integer;
   //R: TRect;
 begin
   if (p = nil) or (p.FDummy) then
@@ -3598,8 +3665,21 @@ begin
 
   renderMapInternal(-c, -d, true);
 
-  if (gGameSettings.GameMode <> GM_SINGLE) and gPlayerIndicator then
-    p.DrawIndicator();
+  if (gGameSettings.GameMode <> GM_SINGLE) and (gPlayerIndicator > 0) then
+    case gPlayerIndicator of
+      1:
+        p.DrawIndicator(_RGB(255, 255, 255));
+
+      2:
+        for i := 0 to High(gPlayers) do
+          if gPlayers[i] <> nil then
+            if gPlayers[i] = p then p.DrawIndicator(_RGB(255, 255, 255))
+            else if (gPlayers[i].Team = p.Team) and (gPlayers[i].Team <> TEAM_NONE) then
+              if gPlayerIndicatorStyle = 1 then
+                gPlayers[i].DrawIndicator(_RGB(192, 192, 192))
+              else gPlayers[i].DrawIndicator(gPlayers[i].GetColor);
+    end;
+
   if p.FSpectator then
     e_TextureFontPrintEx(p.GameX + PLAYER_RECT_CX - 4,
                          p.GameY + PLAYER_RECT_CY - 4,
@@ -3655,7 +3735,7 @@ var
 begin
   if gExit = EXIT_QUIT then Exit;
 
-  Time := GetTimer() {div 1000};
+  Time := sys_GetTicks() {div 1000};
   FPSCounter := FPSCounter+1;
   if Time - FPSTime >= 1000 then
   begin
@@ -3738,15 +3818,15 @@ begin
       if plView1 <> nil then
       begin
         gHearPoint1.Active := True;
-        gHearPoint1.Coords.X := plView1.GameX;
-        gHearPoint1.Coords.Y := plView1.GameY;
+        gHearPoint1.Coords.X := plView1.GameX + PLAYER_RECT.Width;
+        gHearPoint1.Coords.Y := plView1.GameY + PLAYER_RECT.Height DIV 2;
       end else
         gHearPoint1.Active := False;
       if plView2 <> nil then
       begin
         gHearPoint2.Active := True;
-        gHearPoint2.Coords.X := plView2.GameX;
-        gHearPoint2.Coords.Y := plView2.GameY;
+        gHearPoint2.Coords.X := plView2.GameX + PLAYER_RECT.Width;
+        gHearPoint2.Coords.Y := plView2.GameY + PLAYER_RECT.Height DIV 2;
       end else
         gHearPoint2.Active := False;
 
@@ -3868,11 +3948,7 @@ begin
   begin
     if (gState = STATE_MENU) then
     begin
-      if (g_ActiveWindow = nil) or (g_ActiveWindow.BackTexture = '') then
-      begin
-        if g_Texture_Get('MENU_BACKGROUND', ID) then e_DrawSize(ID, 0, 0, 0, False, False, gScreenWidth, gScreenHeight)
-        else e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
-      end;
+      if (g_ActiveWindow = nil) or (g_ActiveWindow.BackTexture = '') then DrawMenuBackground('MENU_BACKGROUND');
       // F3 at menu will show game loading dialog
       if e_KeyPressed(IK_F3) then g_Menu_Show_LoadMenu(true);
       if (g_ActiveWindow <> nil) then
@@ -3907,10 +3983,7 @@ begin
       else
         back := 'INTER';
 
-      if g_Texture_Get(back, ID) then
-        e_DrawSize(ID, 0, 0, 0, False, False, gScreenWidth, gScreenHeight)
-      else
-        e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
+      DrawMenuBackground(back);
 
       DrawCustomStat();
 
@@ -3931,10 +4004,7 @@ begin
       begin
         back := 'INTER';
 
-        if g_Texture_Get(back, ID) then
-          e_DrawSize(ID, 0, 0, 0, False, False, gScreenWidth, gScreenHeight)
-        else
-          e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
+        DrawMenuBackground(back);
 
         DrawSingleStat();
 
@@ -3949,13 +4019,8 @@ begin
     if gState = STATE_ENDPIC then
     begin
       ID := DWORD(-1);
-      if not g_Texture_Get('TEXTURE_endpic', ID) then
-        g_Texture_Get(_lc[I_TEXTURE_ENDPIC], ID);
-
-      if ID <> DWORD(-1) then
-        e_DrawSize(ID, 0, 0, 0, False, False, gScreenWidth, gScreenHeight)
-      else
-        e_Clear(GL_COLOR_BUFFER_BIT, 0, 0, 0);
+      if g_Texture_Get('TEXTURE_endpic', ID) then DrawMenuBackground('TEXTURE_endpic')
+      else DrawMenuBackground(_lc[I_TEXTURE_ENDPIC]);
 
       if g_ActiveWindow <> nil then
       begin
@@ -3966,12 +4031,13 @@ begin
 
     if gState = STATE_SLIST then
     begin
-      if g_Texture_Get('MENU_BACKGROUND', ID) then
-      begin
-        e_DrawSize(ID, 0, 0, 0, False, False, gScreenWidth, gScreenHeight);
-        //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
-        e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
-      end;
+//      if g_Texture_Get('MENU_BACKGROUND', ID) then
+//      begin
+//        e_DrawSize(ID, 0, 0, 0, False, False, gScreenWidth, gScreenHeight);
+//        //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
+//      end;
+      DrawMenuBackground('MENU_BACKGROUND');
+      e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
       g_Serverlist_Draw(slCurrent, slTable);
     end;
   end;
@@ -3986,7 +4052,9 @@ begin
     g_ActiveWindow.Draw();
   end;
 
+{$IFNDEF HEADLESS}
   g_Console_Draw();
+{$ENDIF}
 
   if g_debug_Sounds and gGameOn then
   begin
@@ -4001,7 +4069,7 @@ begin
     e_TextureFontPrint(0, 16, Format('UPS: %d', [UPS]), gStdFont);
   end;
 
-  if gGameOn and gShowTime and (gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT]) then
+  if gGameOn and gShowTime then
     drawTime(gScreenWidth-72, gScreenHeight-16);
 
   if gGameOn then drawProfilers();
@@ -4022,7 +4090,9 @@ begin
   g_PlayerModel_FreeData();
   g_Texture_DeleteAll();
   g_Frames_DeleteAll();
+{$IFNDEF HEADLESS}
   //g_Menu_Free(); //k8: this segfaults after resolution change; who cares?
+{$ENDIF}
 
   if NetInitDone then g_Net_Free;
 
@@ -4031,7 +4101,7 @@ begin
     g_Game_DeleteTestMap();
 
   gExit := EXIT_QUIT;
-  PushExitEvent();
+  sys_RequestQuit;
 end;
 
 procedure g_FatalError(Text: String);
@@ -4089,7 +4159,7 @@ end;
 
 procedure g_Game_ChangeResolution(newWidth, newHeight: Word; nowFull, nowMax: Boolean);
 begin
-  g_Window_SetSize(newWidth, newHeight, nowFull);
+  sys_SetDisplayMode(newWidth, newHeight, gBPP, nowFull);
 end;
 
 procedure g_Game_AddPlayer(Team: Byte = TEAM_NONE);
@@ -4127,7 +4197,10 @@ begin
       gPlayer1.Respawn(False, True);
 
       if g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
+      begin
+        //g_Net_Slist_Update;
+        g_Net_Slist_Pulse();
+      end;
     end;
 
     Exit;
@@ -4159,7 +4232,10 @@ begin
       gPlayer2.Respawn(False, True);
 
       if g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
+      begin
+        //g_Net_Slist_Update;
+        g_Net_Slist_Pulse();
+      end;
     end;
 
     Exit;
@@ -4184,7 +4260,10 @@ begin
       g_Player_Remove(Pl.UID);
 
       if g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
+      begin
+        //g_Net_Slist_Update;
+        g_Net_Slist_Pulse();
+      end;
     end else
       gPlayer2 := nil;
     Exit;
@@ -4200,7 +4279,10 @@ begin
       g_Player_Remove(Pl.UID);
 
       if g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
+      begin
+        //g_Net_Slist_Update;
+        g_Net_Slist_Pulse();
+      end;
     end else
     begin
       gPlayer1 := nil;
@@ -4539,6 +4621,10 @@ begin
   NetState := NET_STATE_AUTH;
 
   g_Game_SetLoadingText(_lc[I_LOAD_CONNECT], 0, False);
+
+  // create (or update) map/resource databases
+  g_Res_CreateDatabases(true);
+
 // Ñòàðòóåì êëèåíò
   if not g_Net_Connect(Addr, Port) then
   begin
@@ -4554,14 +4640,24 @@ begin
   OuterLoop := True;
   while OuterLoop do
   begin
-    while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
+    while (enet_host_service(NetHost, @NetEvent, 50) > 0) do
     begin
       if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
       begin
+        if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then
+        begin
+          // ignore all download packets, they're processed by separate code
+          enet_packet_destroy(NetEvent.packet);
+          continue;
+        end;
         Ptr := NetEvent.packet^.data;
         if not InMsg.Init(Ptr, NetEvent.packet^.dataLength, True) then
+        begin
+          enet_packet_destroy(NetEvent.packet);
           continue;
+        end;
 
+        InMsg.ReadLongWord(); // skip size
         MID := InMsg.ReadByte();
 
         if (MID = NET_MSG_INFO) and (State = 0) then
@@ -4582,11 +4678,11 @@ begin
           gGameSettings.Options := InMsg.ReadLongWord();
           T := InMsg.ReadLongWord();
 
-          newResPath := g_Res_SearchSameWAD(MapsDir, WadName, gWADHash);
-          if newResPath = '' then
+          //newResPath := g_Res_SearchSameWAD(MapsDir, WadName, gWADHash);
+          //if newResPath = '' then
           begin
-            g_Game_SetLoadingText(_lc[I_LOAD_DL_RES], 0, False);
-            newResPath := g_Res_DownloadWAD(WadName);
+            //g_Game_SetLoadingText(_lc[I_LOAD_DL_RES], 0, False);
+            newResPath := g_Res_DownloadMapWAD(WadName, gWADHash);
             if newResPath = '' then
             begin
               g_FatalError(_lc[I_NET_ERR_HASH]);
@@ -4594,6 +4690,7 @@ begin
               NetState := NET_STATE_NONE;
               Exit;
             end;
+            e_LogWritefln('using downloaded map wad [%s] for [%s]`', [newResPath, WadName], TMsgType.Notify);
           end;
           newResPath := ExtractRelativePath(MapsDir, newResPath);
 
@@ -4649,8 +4746,7 @@ begin
 
     ProcessLoading(true);
 
-    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
+    if g_Net_UserRequestExit() then
     begin
       State := 0;
       break;
@@ -4713,6 +4809,7 @@ function g_Game_StartMap(Map: String; Force: Boolean = False; const oldMapPath:
 var
   NewWAD, ResName: String;
   I: Integer;
+  nws: AnsiString;
 begin
   g_Map_Free((Map <> gCurrentMapFileName) and (oldMapPath <> gCurrentMapFileName));
   g_Player_RemoveAllCorpses();
@@ -4736,15 +4833,25 @@ begin
     ResName := g_ExtractFileName(Map);
     if g_Game_IsServer then
     begin
-      gWADHash := MD5File(MapsDir + NewWAD);
-      g_Game_LoadWAD(NewWAD);
+      nws := findDiskWad(MapsDir+NewWAD);
+      if (length(nws) = 0) then
+      begin
+        ResName := '';
+      end
+      else
+      begin
+        if (g_Game_IsNet) then gWADHash := MD5File(nws);
+        //writeln('********: nws=', nws, ' : Map=', Map, ' : nw=', NewWAD, ' : resname=', ResName);
+        g_Game_LoadWAD(NewWAD);
+      end;
     end else
       // hash received in MC_RECV_GameEvent -> NET_EV_MAPSTART
       g_Game_ClientWAD(NewWAD, gWADHash);
   end else
     ResName := Map;
 
-  Result := g_Map_Load(MapsDir + gGameSettings.WAD + ':\' + ResName);
+  //writeln('********: gsw=', gGameSettings.WAD, '; rn=', ResName);
+  Result := (ResName <> '') and g_Map_Load(MapsDir + gGameSettings.WAD + ':\' + ResName);
   if Result then
     begin
       g_Player_ResetAll(Force or gLastMap, gGameSettings.GameType = GT_SINGLE);
@@ -4815,11 +4922,11 @@ begin
   // Ìàñòåðñåðâåð
     if NetUseMaster then
     begin
-      if (NetMHost = nil) or (NetMPeer = nil) then
-        if not g_Net_Slist_Connect then
-          g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR]);
-
+      {
+      if (not g_Net_Slist_IsConnectionActive) then g_Net_Slist_Connect(false);  // non-blocking connection to the master
       g_Net_Slist_Update;
+      }
+      g_Net_Slist_Pulse();
     end;
 
     if NetClients <> nil then
@@ -4916,19 +5023,33 @@ begin
   gNextMap := Map;
 end;
 
-procedure g_Game_ClientWAD(NewWAD: String; WHash: TMD5Digest);
+procedure g_Game_ClientWAD(NewWAD: String; const WHash: TMD5Digest);
 var
-  gWAD: String;
+  gWAD, xwad: String;
 begin
-  if LowerCase(NewWAD) = LowerCase(gGameSettings.WAD) then
-    Exit;
-  if not g_Game_IsClient then
+  if not g_Game_IsClient then Exit;
+  //e_LogWritefln('*** g_Game_ClientWAD: `%s`', [NewWAD]);
+
+  gWAD := g_Res_DownloadMapWAD(ExtractFileName(NewWAD), WHash);
+  if gWAD = '' then
+  begin
+    g_Game_Free();
+    g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [ExtractFileName(NewWAD)]));
     Exit;
+  end;
+
+  xwad := ExtractRelativePath(MapsDir, gWAD);
+  e_LogWritefln('using downloaded client map wad [%s] for [%s]`', [xwad, NewWAD], TMsgType.Notify);
+  NewWAD := xwad;
+  g_Game_LoadWAD(NewWAD);
+
+  {
+  if LowerCase(NewWAD) = LowerCase(gGameSettings.WAD) then Exit;
   gWAD := g_Res_SearchSameWAD(MapsDir, ExtractFileName(NewWAD), WHash);
   if gWAD = '' then
   begin
     g_Game_SetLoadingText(_lc[I_LOAD_DL_RES], 0, False);
-    gWAD := g_Res_DownloadWAD(ExtractFileName(NewWAD));
+    gWAD := g_Res_DownloadMapWAD(ExtractFileName(NewWAD), WHash);
     if gWAD = '' then
     begin
       g_Game_Free();
@@ -4938,6 +5059,7 @@ begin
   end;
   NewWAD := ExtractRelativePath(MapsDir, gWAD);
   g_Game_LoadWAD(NewWAD);
+  }
 end;
 
 procedure g_Game_RestartRound(NoMapRestart: Boolean = False);
@@ -5376,7 +5498,10 @@ begin
       if Length(NetServerName) > 64 then
         SetLength(NetServerName, 64);
       if g_Game_IsServer and g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
+      begin
+        //g_Net_Slist_Update;
+        g_Net_Slist_Pulse();
+      end;
     end;
 
     g_Console_Add(cmd + ' = "' + NetServerName + '"');
@@ -5389,7 +5514,10 @@ begin
       if Length(NetPassword) > 24 then
         SetLength(NetPassword, 24);
       if g_Game_IsServer and g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
+      begin
+        //g_Net_Slist_Update;
+        g_Net_Slist_Pulse();
+      end;
     end;
 
     g_Console_Add(cmd + ' = "' + AnsiLowerCase(NetPassword) + '"');
@@ -5415,7 +5543,10 @@ begin
             end;
           end;
         if NetUseMaster then
-          g_Net_Slist_Update;
+        begin
+          //g_Net_Slist_Update;
+          g_Net_Slist_Pulse();
+        end;
       end;
     end;
 
@@ -5427,16 +5558,21 @@ begin
     begin
       NetUseMaster := StrToIntDef(P[1], Byte(NetUseMaster)) > 0;
       if g_Game_IsServer and g_Game_IsNet then
+      begin
         if NetUseMaster then
         begin
-          if NetMPeer = nil then
-            if not g_Net_Slist_Connect() then
-              g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR]);
+          {
+          if (not g_Net_Slist_IsConnectionActive) then g_Net_Slist_Connect(false);  // non-blocking connection to the master
           g_Net_Slist_Update();
+          }
+          g_Net_Slist_Pulse();
         end
         else
-          if NetMPeer <> nil then
-            g_Net_Slist_Disconnect();
+        begin
+          //if (not g_Net_Slist_IsConnectionActive) then g_Net_Slist_Disconnect();
+          g_Net_Slist_Private();
+        end;
+      end;
     end;
 
     g_Console_Add(cmd + ' = ' + IntToStr(Byte(NetUseMaster)));
@@ -5534,18 +5670,7 @@ begin
   end
   else if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
   begin
-    if cmd = 'r_showtime' then
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
-        gShowTime := (P[1][1] = '1');
-
-      if gShowTime then
-        g_Console_Add(_lc[I_MSG_TIME_ON])
-      else
-        g_Console_Add(_lc[I_MSG_TIME_OFF]);
-    end
-    else if cmd = 'r_showscore' then
+    if cmd = 'r_showscore' then
     begin
       if (Length(P) > 1) and
          ((P[1] = '1') or (P[1] = '0')) then
@@ -6103,7 +6228,10 @@ begin
         g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
         MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
         if NetUseMaster then
-          g_Net_Slist_Update;
+        begin
+          //g_Net_Slist_Update;
+          g_Net_Slist_Pulse();
+        end;
       end else if gPlayers <> nil then
         for a := Low(gPlayers) to High(gPlayers) do
           if gPlayers[a] <> nil then
@@ -6117,7 +6245,10 @@ begin
               g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [gPlayers[a].Name]), True);
               g_Player_Remove(gPlayers[a].UID);
               if NetUseMaster then
-                g_Net_Slist_Update;
+              begin
+                //g_Net_Slist_Update;
+                g_Net_Slist_Pulse();
+              end;
               // Åñëè íå ïåðåìåøàòü, ïðè äîáàâëåíèè íîâûõ áîòîâ ïîÿâÿòñÿ ñòàðûå
               g_Bot_MixNames();
             end;
@@ -6149,7 +6280,10 @@ begin
           g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
           MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
           if NetUseMaster then
-            g_Net_Slist_Update;
+          begin
+            //g_Net_Slist_Update;
+            g_Net_Slist_Pulse();
+          end;
         end;
       end;
     end else
@@ -6179,7 +6313,10 @@ begin
         g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
         MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
         if NetUseMaster then
-          g_Net_Slist_Update;
+        begin
+          //g_Net_Slist_Update;
+          g_Net_Slist_Pulse();
+        end;
       end else
         g_Console_Add(Format(_lc[I_NET_ERR_NAME404], [P[1]]));
     end else
@@ -6210,7 +6347,10 @@ begin
           g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
           MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
           if NetUseMaster then
-            g_Net_Slist_Update;
+          begin
+            //g_Net_Slist_Update;
+            g_Net_Slist_Pulse();
+          end;
         end;
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
@@ -6240,7 +6380,10 @@ begin
         g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
         MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
         if NetUseMaster then
-          g_Net_Slist_Update;
+        begin
+          //g_Net_Slist_Update;
+          g_Net_Slist_Pulse();
+        end;
       end else
         g_Console_Add(Format(_lc[I_NET_ERR_NAME404], [P[1]]));
     end else
@@ -6272,7 +6415,10 @@ begin
           g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
           MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
           if NetUseMaster then
-            g_Net_Slist_Update;
+          begin
+            //g_Net_Slist_Update;
+            g_Net_Slist_Pulse();
+          end;
         end;
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
@@ -6372,7 +6518,9 @@ begin
   else if (cmd = 'addbot') or
      (cmd = 'bot_add') then
   begin
-    if Length(P) > 1 then
+    if Length(P) > 2 then
+      g_Bot_Add(TEAM_NONE, StrToIntDef(P[1], 2), StrToIntDef(P[2], 100))
+    else if Length(P) > 1 then
       g_Bot_Add(TEAM_NONE, StrToIntDef(P[1], 2))
     else
       g_Bot_Add(TEAM_NONE, 2);
@@ -6380,10 +6528,14 @@ begin
   else if cmd = 'bot_addlist' then
   begin
     if Length(P) > 1 then
+    begin
       if Length(P) = 2 then
         g_Bot_AddList(TEAM_NONE, P[1], StrToIntDef(P[1], -1))
+      else if Length(P) = 3 then
+        g_Bot_AddList(TEAM_NONE, P[1], StrToIntDef(P[1], -1), StrToIntDef(P[2], 100))
       else
         g_Bot_AddList(IfThen(P[2] = 'red', TEAM_RED, TEAM_BLUE), P[1], StrToIntDef(P[1], -1));
+    end;
   end
   else if cmd = 'bot_removeall' then
     g_Bot_RemoveAll()
@@ -7155,27 +7307,14 @@ begin
     e_StopChannels();
 end;
 
-procedure g_Game_UpdateTriggerSounds();
-var
-  i: Integer;
+procedure g_Game_UpdateTriggerSounds;
+  var i: Integer;
 begin
   if gTriggers <> nil then
     for i := 0 to High(gTriggers) do
       with gTriggers[i] do
-        if (TriggerType = TRIGGER_SOUND) and
-           (Sound <> nil) and
-           (tgcLocal) and
-           Sound.IsPlaying() then
-        begin
-          if ((gPlayer1 <> nil) and g_CollidePoint(gPlayer1.GameX, gPlayer1.GameY, X, Y, Width, Height)) or
-             ((gPlayer2 <> nil) and g_CollidePoint(gPlayer2.GameX, gPlayer2.GameY, X, Y, Width, Height)) then
-          begin
-            Sound.SetPan(0.5 - tgcPan/255.0);
-            Sound.SetVolume(tgcVolume/255.0);
-          end
-          else
-            Sound.SetCoords(X+(Width div 2), Y+(Height div 2), tgcVolume/255.0);
-        end;
+        if (TriggerType = TRIGGER_SOUND) and (Sound <> nil) and tgcLocal and Sound.IsPlaying() then
+          Sound.SetCoordsRect(X, Y, Width, Height, tgcVolume / 255.0)
 end;
 
 function g_Game_IsWatchedPlayer(UID: Word): Boolean;
@@ -7762,9 +7901,9 @@ begin
   // Number of players:
     s := Find_Param_Value(pars, '-pl');
     if (s = '') then
-      n := 1
+      n := DEFAULT_PLAYERS
     else
-      n := StrToIntDef(s, 1);
+      n := StrToIntDef(s, DEFAULT_PLAYERS);
 
   // Start:
     s := Find_Param_Value(pars, '-port');
@@ -7847,4 +7986,5 @@ begin
   conRegVar('r_smallmap_align_v', @r_smallmap_v, 'valign: 0: top; 1: center; 2: bottom', 'vertial aligning of small maps');
 
   conRegVar('r_showfps', @gShowFPS, 'draw fps counter', 'draw fps counter');
+  conRegVar('r_showtime', @gShowTime, 'show game time', 'show game time');
 end.