DEADSOFTWARE

render: separate game logic and drawing
authorDeaDDooMER <deaddoomer@deadsoftware.ru>
Mon, 7 Jun 2021 15:56:28 +0000 (18:56 +0300)
committerDeaDDooMER <deaddoomer@deadsoftware.ru>
Fri, 9 Jun 2023 07:40:41 +0000 (10:40 +0300)
src/game/Doom2DF.lpr
src/game/g_game.pas
src/game/g_main.pas
src/game/g_menu.pas
src/game/g_netmaster.pas
src/game/g_window.pas
src/game/opengl/r_console.pas
src/game/opengl/r_game.pas [new file with mode: 0644]

index 73a73a92291cd70c3ffc1eb44c146efd01cdfecc..0a18aa66da809235d0054167040c5faf6c6b460c 100644 (file)
@@ -154,6 +154,7 @@ uses
 {$ENDIF}
 
   r_console in 'opengl/r_console.pas',
+  r_game in 'opengl/r_game.pas',
 
 {$IFDEF USE_FMOD}
   fmod in '../lib/FMOD/fmod.pas',
index 9c3291c38485b12e84f298e858b8e8d26b887420..9226201986178582c9a617bc8e8889d065848d23 100644 (file)
@@ -90,7 +90,6 @@ procedure g_Game_LoadData();
 procedure g_Game_FreeData();
 procedure g_Game_Update();
 procedure g_Game_PreUpdate();
-procedure g_Game_Draw();
 procedure g_Game_Quit();
 procedure g_Game_SetupScreenSize();
 procedure g_Game_ChangeResolution(newWidth, newHeight: Word; nowFull, nowMax: Boolean);
@@ -149,8 +148,10 @@ procedure g_Game_SetLoadingText(Text: String; Max: Integer; reWrite: Boolean);
 procedure g_Game_StepLoading(Value: Integer = -1);
 procedure g_Game_ClearLoading();
 procedure g_Game_SetDebugMode();
-procedure DrawLoadingStat();
-procedure DrawMenuBackground(tex: AnsiString);
+
+function IsActivePlayer(p: TPlayer): Boolean;
+function GetActivePlayerID_Next(Skip: Integer = -1): Integer;
+procedure SortGameStat(var stat: TPlayerStatArray);
 
 { procedure SetWinPause(Enable: Boolean); }
 
@@ -388,6 +389,55 @@ procedure g_DynLightExplosion (x, y, radius: Integer; r, g, b: Single);
 function conIsCheatsEnabled (): Boolean; inline;
 function gPause (): Boolean; inline;
 
+  type (* private state *)
+    TEndCustomGameStat = record
+      PlayerStat: TPlayerStatArray;
+      TeamStat: TTeamStat;
+      GameTime: LongWord;
+      GameMode: Byte;
+      Map, MapName: String;
+    end;
+
+    TEndSingleGameStat = record
+      PlayerStat: Array [0..1] of record
+        Kills: Integer;
+        Secrets: Integer;
+      end;
+      GameTime: LongWord;
+      TwoPlayers: Boolean;
+      TotalSecrets: Integer;
+    end;
+
+    TLoadingStat = record
+      CurValue: Integer;
+      MaxValue: Integer;
+      ShowCount: Integer;
+      Msgs: Array of String;
+      NextMsg: Word;
+      PBarWasHere: Boolean; // did we draw a progress bar for this message?
+    end;
+
+    TDynLight = record
+      x, y, radius: Integer;
+      r, g, b, a: Single;
+      exploCount: Integer;
+      exploRadius: Integer;
+    end;
+
+  var (* private state *)
+    CustomStat: TEndCustomGameStat;
+    StatShotDone: Boolean;
+    StatFilename: string = ''; // used by stat screenshot to save with the same name as the csv
+    SingleStat: TEndSingleGameStat;
+    hasPBarGfx: Boolean;
+    LoadingStat: TLoadingStat;
+    MessageText: String;
+    IsDrawStat: Boolean;
+    EndingGameCounter: Byte;
+    UPS: Word;
+    g_playerLight: Boolean;
+    g_dynLights: array of TDynLight = nil;
+    g_dynLightCount: Integer = 0;
 
 implementation
 
@@ -405,48 +455,9 @@ uses
   sfs, wadreader, g_system;
 
 
-var
-  hasPBarGfx: Boolean = false;
-
-
 // ////////////////////////////////////////////////////////////////////////// //
 function gPause (): Boolean; inline; begin result := gPauseMain or gPauseHolmes; end;
 
-
-// ////////////////////////////////////////////////////////////////////////// //
-function conIsCheatsEnabled (): Boolean; inline;
-begin
-  result := false;
-  if g_Game_IsNet then exit;
-  if not gDebugMode then
-  begin
-    //if not gCheats then exit;
-    if not (gGameSettings.GameType in [GT_SINGLE, GT_CUSTOM]) then exit;
-    if not (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) then exit;
-  end;
-  result := true;
-end;
-
-
-// ////////////////////////////////////////////////////////////////////////// //
-var
-  profileFrameDraw: TProfiler = nil;
-
-
-// ////////////////////////////////////////////////////////////////////////// //
-type
-  TDynLight = record
-    x, y, radius: Integer;
-    r, g, b, a: Single;
-    exploCount: Integer;
-    exploRadius: Integer;
-  end;
-
-var
-  g_dynLights: array of TDynLight = nil;
-  g_dynLightCount: Integer = 0;
-  g_playerLight: Boolean = false;
-
 procedure g_ResetDynlights ();
 var
   lnum, idx: Integer;
@@ -507,75 +518,22 @@ begin
   Inc(g_dynLightCount);
 end;
 
-
 // ////////////////////////////////////////////////////////////////////////// //
-function calcProfilesHeight (prof: TProfiler): Integer;
-begin
-  result := 0;
-  if (prof = nil) then exit;
-  if (length(prof.bars) = 0) then exit;
-  result := length(prof.bars)*(16+2);
-end;
-
-// returns width
-function drawProfiles (x, y: Integer; prof: TProfiler): Integer;
-var
-  wdt, hgt: Integer;
-  yy: Integer;
-  ii: Integer;
+function conIsCheatsEnabled (): Boolean; inline;
 begin
-  result := 0;
-  if (prof = nil) then exit;
-  // gScreenWidth
-  if (length(prof.bars) = 0) then exit;
-  wdt := 192;
-  hgt := calcProfilesHeight(prof);
-  if (x < 0) then x := gScreenWidth-(wdt-1)+x;
-  if (y < 0) then y := gScreenHeight-(hgt-1)+y;
-  // background
-  //e_DrawFillQuad(x, y, x+wdt-1, y+hgt-1, 255, 255, 255, 200, B_BLEND);
-  //e_DrawFillQuad(x, y, x+wdt-1, y+hgt-1, 20, 20, 20, 0, B_NONE);
-  e_DarkenQuadWH(x, y, wdt, hgt, 150);
-  // title
-  yy := y+2;
-  for ii := 0 to High(prof.bars) do
-  begin
-    e_TextureFontPrintEx(x+2+4*prof.bars[ii].level, yy, Format('%s: %d', [prof.bars[ii].name, prof.bars[ii].value]), gStdFont, 255, 255, 0, 1, false);
-    Inc(yy, 16+2);
-  end;
-  result := wdt;
+  result := false;
+  if g_Game_IsNet then exit;
+  if not gDebugMode then
+  begin
+    //if not gCheats then exit;
+    if not (gGameSettings.GameType in [GT_SINGLE, GT_CUSTOM]) then exit;
+    if not (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) then exit;
+  end;
+  result := true;
 end;
 
-
 // ////////////////////////////////////////////////////////////////////////// //
 type
-  TEndCustomGameStat = record
-    PlayerStat: TPlayerStatArray;
-    TeamStat: TTeamStat;
-    GameTime: LongWord;
-    GameMode: Byte;
-    Map, MapName: String;
-  end;
-
-  TEndSingleGameStat = record
-    PlayerStat: Array [0..1] of record
-      Kills: Integer;
-      Secrets: Integer;
-    end;
-    GameTime: LongWord;
-    TwoPlayers: Boolean;
-    TotalSecrets: Integer;
-  end;
-
-  TLoadingStat = record
-    CurValue: Integer;
-    MaxValue: Integer;
-    ShowCount: Integer;
-    Msgs: Array of String;
-    NextMsg: Word;
-    PBarWasHere: Boolean; // did we draw a progress bar for this message?
-  end;
-
   TParamStrValue = record
     Name: String;
     Value: String;
@@ -589,23 +547,14 @@ const
   INTER_ACTION_MUSIC = 3;
 
 var
-  FPS, UPS: Word;
-  FPSCounter, UPSCounter: Word;
-  FPSTime, UPSTime: LongWord;
+  UPSCounter: Word;
+  UPSTime: LongWord;
   DataLoaded: Boolean = False;
-  IsDrawStat: Boolean = False;
-  CustomStat: TEndCustomGameStat;
-  SingleStat: TEndSingleGameStat;
-  LoadingStat: TLoadingStat;
-  EndingGameCounter: Byte = 0;
-  MessageText: String;
   MessageTime: Word;
   MessageLineLength: Integer = 80;
   MapList: SSArray = nil;
   MapIndex: Integer = -1;
   InterReadyTime: Integer = -1;
-  StatShotDone: Boolean = False;
-  StatFilename: string = ''; // used by stat screenshot to save with the same name as the csv
   StatDate: string = '';
   MegaWAD: record
     info: TMegaWADInfo;
@@ -912,7 +861,7 @@ begin
     g_Texture_CreateWADEx('TEXTURE_endpic', s);
     TEXTUREFILTER := GL_NEAREST;
   end;
-  MegaWAD.endmus := cfg.ReadStr('megawad', 'endmus', 'Standart.wad:D2DMUS\ÊÎÍÅÖ');
+  MegaWAD.endmus := cfg.ReadStr('megawad', 'endmus', 'Standart.wad:D2DMUS\КОНЕЦ');
   if MegaWAD.endmus <> '' then
   begin
     s := e_GetResourcePath(WadDirs, MegaWAD.endmus, WAD);
@@ -1003,7 +952,7 @@ begin
   if g_Game_IsNet and g_Game_IsServer then
     MH_SEND_GameEvent(NET_EV_MAPEND, Byte(gMissionFailed));
 
-// Ñòîï èãðà:
+// Стоп игра:
   gPauseMain := false;
   gPauseHolmes := false;
   gGameOn := false;
@@ -1020,16 +969,16 @@ begin
   gLMSRespawnTime := 0;
 
   case gExit of
-    EXIT_SIMPLE: // Âûõîä ÷åðåç ìåíþ èëè êîíåö òåñòà
+    EXIT_SIMPLE: // Выход через меню или конец теста
       begin
         g_Game_Free();
 
         if gMapOnce  then
-          begin // Ýòî áûë òåñò
+          begin // Это был тест
             g_Game_Quit();
           end
         else
-          begin // Âûõîä â ãëàâíîå ìåíþ
+          begin // Выход в главное меню
             gMusic.SetByName('MUSIC_MENU');
             gMusic.Play();
             if gState <> STATE_SLIST then
@@ -1038,7 +987,7 @@ begin
               gState := STATE_MENU;
             end else
             begin
-              // Îáíîâëÿåì ñïèñîê ñåðâåðîâ
+              // Обновляем список серверов
               slReturnPressed := True;
               if g_Net_Slist_Fetch(slCurrent) then
               begin
@@ -1054,14 +1003,14 @@ begin
           end;
       end;
 
-    EXIT_RESTART: // Íà÷àòü óðîâåíü ñíà÷àëà
+    EXIT_RESTART: // Начать уровень сначала
       begin
         if not g_Game_IsClient then g_Game_Restart();
       end;
 
-    EXIT_ENDLEVELCUSTOM: // Çàêîí÷èëñÿ óðîâåíü â Ñâîåé èãðå
+    EXIT_ENDLEVELCUSTOM: // Закончился уровень в Своей игре
       begin
-      // Ñòàòèñòèêà Ñâîåé èãðû:
+      // Статистика Своей игры:
         FileName := g_ExtractWadName(gMapInfo.Map);
 
         CustomStat.GameTime := gTime;
@@ -1073,7 +1022,7 @@ begin
 
         CustomStat.PlayerStat := nil;
 
-      // Ñòàòèñòèêà èãðîêîâ:
+      // Статистика игроков:
         if gPlayers <> nil then
         begin
           for a := 0 to High(gPlayers) do
@@ -1113,7 +1062,7 @@ begin
         if not g_Game_IsClient then g_Player_ResetReady;
         gInterReadyCount := 0;
 
-      // Çàòóõàþùèé ýêðàí:
+      // Затухающий экран:
         EndingGameCounter := 255;
         gState := STATE_FOLD;
         gInterTime := 0;
@@ -1123,16 +1072,16 @@ begin
           gInterEndTime := gDefInterTime * 1000;
       end;
 
-    EXIT_ENDLEVELSINGLE: // Çàêîí÷èëñÿ óðîâåíü â Îäèíî÷íîé èãðå
+    EXIT_ENDLEVELSINGLE: // Закончился уровень в Одиночной игре
       begin
-      // Ñòàòèñòèêà Îäèíî÷íîé èãðû:
+      // Статистика Одиночной игры:
         SingleStat.GameTime := gTime;
         SingleStat.TwoPlayers := gPlayer2 <> nil;
         SingleStat.TotalSecrets := gSecretsCount;
-      // Ñòàòèñòèêà ïåðâîãî èãðîêà:
+      // Статистика первого игрока:
         SingleStat.PlayerStat[0].Kills := gPlayer1.MonsterKills;
         SingleStat.PlayerStat[0].Secrets := gPlayer1.Secrets;
-      // Ñòàòèñòèêà âòîðîãî èãðîêà (åñëè åñòü):
+      // Статистика второго игрока (если есть):
         if SingleStat.TwoPlayers then
         begin
           SingleStat.PlayerStat[1].Kills := gPlayer2.MonsterKills;
@@ -1141,7 +1090,7 @@ begin
 
         g_Game_ExecuteEvent('onmapend');
 
-      // Åñòü åùå êàðòû:
+      // Есть еще карты:
         if gNextMap <> '' then
           begin
             gMusic.SetByName('MUSIC_INTERMUS');
@@ -1151,244 +1100,20 @@ begin
 
             g_Game_ExecuteEvent('oninter');
           end
-        else // Áîëüøå íåò êàðò
+        else // Больше нет карт
           begin
-          // Çàòóõàþùèé ýêðàí:
+          // Затухающий экран:
             EndingGameCounter := 255;
             gState := STATE_FOLD;
           end;
       end;
   end;
 
-// Îêîí÷àíèå îáðàáîòàíî:
+// Окончание обработано:
   if gExit <> EXIT_QUIT then
     gExit := 0;
 end;
 
-procedure drawTime(X, Y: Integer); inline;
-begin
-  e_TextureFontPrint(x, y,
-                     Format('%d:%.2d:%.2d', [
-                       gTime div 1000 div 3600,
-                       (gTime div 1000 div 60) mod 60,
-                       gTime div 1000 mod 60
-                     ]),
-                     gStdFont);
-end;
-
-procedure DrawStat();
-var
-  pc, x, y, w, h: Integer;
-  w1, w2, w3, w4: Integer;
-  a, aa: Integer;
-  cw, ch, r, g, b, rr, gg, bb: Byte;
-  s1, s2, s3: String;
-  _y: Integer;
-  stat: TPlayerStatArray;
-  wad, map: string;
-  mapstr: string;
-  namestr: string;
-begin
-  s1 := '';
-  s2 := '';
-  s3 := '';
-  pc := g_Player_GetCount;
-  e_TextureFontGetSize(gStdFont, cw, ch);
-
-  w := gScreenWidth-(gScreenWidth div 5);
-  if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
-    h := 32+ch*(11+pc)
-  else
-    h := 40+ch*5+(ch+8)*pc;
-  x := (gScreenWidth div 2)-(w div 2);
-  y := (gScreenHeight div 2)-(h div 2);
-
-  e_DrawFillQuad(x, y, x+w-1, y+h-1, 64, 64, 64, 32);
-  e_DrawQuad(x, y, x+w-1, y+h-1, 255, 127, 0);
-
-  drawTime(x+w-78, y+8);
-
-  wad := g_ExtractWadNameNoPath(gMapInfo.Map);
-  map := g_ExtractFileName(gMapInfo.Map);
-  mapstr := wad + ':\' + map + ' - ' + gMapInfo.Name;
-
-  case gGameSettings.GameMode of
-    GM_DM:
-    begin
-      if gGameSettings.MaxLives = 0 then
-        s1 := _lc[I_GAME_DM]
-      else
-        s1 := _lc[I_GAME_LMS];
-      s2 := Format(_lc[I_GAME_FRAG_LIMIT], [gGameSettings.ScoreLimit]);
-      s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
-    end;
-
-    GM_TDM:
-    begin
-      if gGameSettings.MaxLives = 0 then
-        s1 := _lc[I_GAME_TDM]
-      else
-        s1 := _lc[I_GAME_TLMS];
-      s2 := Format(_lc[I_GAME_FRAG_LIMIT], [gGameSettings.ScoreLimit]);
-      s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
-    end;
-
-    GM_CTF:
-    begin
-      s1 := _lc[I_GAME_CTF];
-      s2 := Format(_lc[I_GAME_SCORE_LIMIT], [gGameSettings.ScoreLimit]);
-      s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
-    end;
-
-    GM_COOP:
-    begin
-      if gGameSettings.MaxLives = 0 then
-        s1 := _lc[I_GAME_COOP]
-      else
-        s1 := _lc[I_GAME_SURV];
-      s2 := _lc[I_GAME_MONSTERS] + ' ' + IntToStr(gCoopMonstersKilled) + '/' + IntToStr(gTotalMonsters);
-      s3 := _lc[I_GAME_SECRETS] + ' ' + IntToStr(gCoopSecretsFound) + '/' + IntToStr(gSecretsCount);
-    end;
-
-    else
-    begin
-      s1 := '';
-      s2 := '';
-    end;
-  end;
-
-  _y := y+8;
-  e_TextureFontPrintEx(x+(w div 2)-(Length(s1)*cw div 2), _y, s1, gStdFont, 255, 255, 255, 1);
-  _y := _y+ch+8;
-  e_TextureFontPrintEx(x+(w div 2)-(Length(mapstr)*cw div 2), _y, mapstr, gStdFont, 200, 200, 200, 1);
-  _y := _y+ch+8;
-  e_TextureFontPrintEx(x+16, _y, s2, gStdFont, 200, 200, 200, 1);
-
-  e_TextureFontPrintEx(x+w-16-(Length(s3))*cw, _y, s3,
-                       gStdFont, 200, 200, 200, 1);
-
-  if NetMode = NET_SERVER then
-    e_TextureFontPrintEx(x+8, y + 8, _lc[I_NET_SERVER], gStdFont, 255, 255, 255, 1)
-  else
-    if NetMode = NET_CLIENT then
-      e_TextureFontPrintEx(x+8, y + 8,
-        NetClientIP + ':' + IntToStr(NetClientPort), gStdFont, 255, 255, 255, 1);
-
-  if pc = 0 then
-    Exit;
-  stat := g_Player_GetStats();
-  SortGameStat(stat);
-
-  w2 := (w-16) div 6 + 48; // øèðèíà 2 ñòîëáöà
-  w3 := (w-16) div 6; // øèðèíà 3 è 4 ñòîëáöîâ
-  w4 := w3;
-  w1 := w-16-w2-w3-w4; // îñòàâøååñÿ ïðîñòðàíñòâî - äëÿ öâåòà è èìåíè èãðîêà
-
-  if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
-  begin
-    _y := _y+ch+ch;
-
-    for a := TEAM_RED to TEAM_BLUE do
-    begin
-      if a = TEAM_RED then
-      begin
-        s1 := _lc[I_GAME_TEAM_RED];
-        r := 255;
-        g := 0;
-        b := 0;
-      end
-      else
-      begin
-        s1 := _lc[I_GAME_TEAM_BLUE];
-        r := 0;
-        g := 0;
-        b := 255;
-      end;
-
-      e_TextureFontPrintEx(x+16, _y, s1, gStdFont, r, g, b, 1);
-      e_TextureFontPrintEx(x+w1+16, _y, IntToStr(gTeamStat[a].Score),
-                           gStdFont, r, g, b, 1);
-
-      _y := _y+ch+(ch div 4);
-      e_DrawLine(1, x+16, _y, x+w-16, _y, r, g, b);
-      _y := _y+(ch div 4);
-
-      for aa := 0 to High(stat) do
-        if stat[aa].Team = a then
-          with stat[aa] do
-          begin
-            if Spectator then
-            begin
-              rr := r div 2;
-              gg := g div 2;
-              bb := b div 2;
-            end
-            else
-            begin
-              rr := r;
-              gg := g;
-              bb := b;
-            end;
-            if gShowPIDs then
-              namestr := Format('[%5d] %s', [UID, Name])
-            else
-              namestr := Name;
-            // Èìÿ
-            e_TextureFontPrintEx(x+16, _y, namestr, gStdFont, rr, gg, bb, 1);
-            // Ïèíã/ïîòåðè
-            e_TextureFontPrintEx(x+w1+16, _y, Format(_lc[I_GAME_PING_MS], [Ping, Loss]), gStdFont, rr, gg, bb, 1);
-            // Ôðàãè
-            e_TextureFontPrintEx(x+w1+w2+16, _y, IntToStr(Frags), gStdFont, rr, gg, bb, 1);
-            // Ñìåðòè
-            e_TextureFontPrintEx(x+w1+w2+w3+16, _y, IntToStr(Deaths), gStdFont, rr, gg, bb, 1);
-            _y := _y+ch;
-          end;
-
-          _y := _y+ch;
-    end;
-  end
-  else if gGameSettings.GameMode in [GM_DM, GM_COOP] then
-  begin
-    _y := _y+ch+ch;
-    e_TextureFontPrintEx(x+16, _y, _lc[I_GAME_PLAYER_NAME], gStdFont, 255, 127, 0, 1);
-    e_TextureFontPrintEx(x+16+w1, _y, _lc[I_GAME_PING], gStdFont, 255, 127, 0, 1);
-    e_TextureFontPrintEx(x+16+w1+w2, _y, _lc[I_GAME_FRAGS], gStdFont, 255, 127, 0, 1);
-    e_TextureFontPrintEx(x+16+w1+w2+w3, _y, _lc[I_GAME_DEATHS], gStdFont, 255, 127, 0, 1);
-
-    _y := _y+ch+8;
-    for aa := 0 to High(stat) do
-      with stat[aa] do
-      begin
-        if Spectator then
-        begin
-          r := 127;
-          g := 64;
-        end
-        else
-        begin
-          r := 255;
-          g := 127;
-        end;
-        if gShowPIDs then
-          namestr := Format('[%5d] %s', [UID, Name])
-        else
-          namestr := Name;
-        // Öâåò èãðîêà
-        e_DrawFillQuad(x+16, _y+4, x+32-1, _y+16+4-1, Color.R, Color.G, Color.B, 0);
-        e_DrawQuad(x+16, _y+4, x+32-1, _y+16+4-1, 192, 192, 192);
-        // Èìÿ
-        e_TextureFontPrintEx(x+16+16+8, _y+4, namestr, gStdFont, r, g, 0, 1);
-        // Ïèíã/ïîòåðè
-        e_TextureFontPrintEx(x+w1+16, _y+4, Format(_lc[I_GAME_PING_MS], [Ping, Loss]), gStdFont, r, g, 0, 1);
-        // Ôðàãè
-        e_TextureFontPrintEx(x+w1+w2+16, _y+4, IntToStr(Frags), gStdFont, r, g, 0, 1);
-        // Ñìåðòè
-        e_TextureFontPrintEx(x+w1+w2+w3+16, _y+4, IntToStr(Deaths), gStdFont, r, g, 0, 1);
-        _y := _y+ch+8;
-      end;
-  end
-end;
-
 procedure g_Game_Init();
 var
   SR: TSearchRec;
@@ -1530,25 +1255,6 @@ begin
   Result := (not p.FDummy) and (not p.FSpectator);
 end;
 
-function GetActivePlayer_ByID(ID: Integer): TPlayer;
-var
-  a: Integer;
-begin
-  Result := nil;
-  if ID < 0 then
-    Exit;
-  if gPlayers = nil then
-    Exit;
-  for a := Low(gPlayers) to High(gPlayers) do
-    if IsActivePlayer(gPlayers[a]) then
-    begin
-      if gPlayers[a].UID <> ID then
-        continue;
-      Result := gPlayers[a];
-      break;
-    end;
-end;
-
 function GetActivePlayerID_Next(Skip: Integer = -1): Integer;
 var
   a, idx: Integer;
@@ -1666,13 +1372,13 @@ begin
   MoveButton := MoveButton and $0F;
 
   if gPlayerAction[p, ACTION_MOVELEFT] and (not gPlayerAction[p, ACTION_MOVERIGHT]) then
-    MoveButton := 1 // Íàæàòà òîëüêî "Âëåâî"
+    MoveButton := 1 // Нажата только "Влево"
   else if (not gPlayerAction[p, ACTION_MOVELEFT]) and gPlayerAction[p, ACTION_MOVERIGHT] then
-    MoveButton := 2 // Íàæàòà òîëüêî "Âïðàâî"
+    MoveButton := 2 // Нажата только "Вправо"
   else if (not gPlayerAction[p, ACTION_MOVELEFT]) and (not gPlayerAction[p, ACTION_MOVERIGHT]) then
-    MoveButton := 0; // Íå íàæàòû íè "Âëåâî", íè "Âïðàâî"
+    MoveButton := 0; // Не нажаты ни "Влево", ни "Вправо"
 
-  // Ñåé÷àñ èëè ðàíüøå áûëè íàæàòû "Âëåâî"/"Âïðàâî" => ïåðåäàåì èãðîêó:
+  // Сейчас или раньше были нажаты "Влево"/"Вправо" => передаем игроку:
   if MoveButton = 1 then
     plr.PressKey(KEY_LEFT, time)
   else if MoveButton = 2 then
@@ -1693,13 +1399,13 @@ begin
   else
   begin
     strafeDir := 0; // not strafing anymore
-    // Ðàíüøå áûëà íàæàòà "Âïðàâî", à ñåé÷àñ "Âëåâî" => áåæèì âïðàâî, ñìîòðèì âëåâî:
+    // Ð Ð°Ð½Ñ\8cÑ\88е Ð±Ñ\8bла Ð½Ð°Ð¶Ð°Ñ\82а "Ð\92пÑ\80аво", Ð° Ñ\81ейÑ\87аÑ\81 "Ð\92лево" => Ð±ÐµÐ¶Ð¸Ð¼ Ð²Ð¿Ñ\80аво, Ñ\81моÑ\82Ñ\80им Ð²Ð»ÐµÐ²Ð¾:
     if (MoveButton = 2) and gPlayerAction[p, ACTION_MOVELEFT] then
       plr.SetDirection(TDirection.D_LEFT)
-    // Ðàíüøå áûëà íàæàòà "Âëåâî", à ñåé÷àñ "Âïðàâî" => áåæèì âëåâî, ñìîòðèì âïðàâî:
+    // Ð Ð°Ð½Ñ\8cÑ\88е Ð±Ñ\8bла Ð½Ð°Ð¶Ð°Ñ\82а "Ð\92лево", Ð° Ñ\81ейÑ\87аÑ\81 "Ð\92пÑ\80аво" => Ð±ÐµÐ¶Ð¸Ð¼ Ð²Ð»ÐµÐ²Ð¾, Ñ\81моÑ\82Ñ\80им Ð²Ð¿Ñ\80аво:
     else if (MoveButton = 1) and gPlayerAction[p, ACTION_MOVERIGHT] then
       plr.SetDirection(TDirection.D_RIGHT)
-    // ×òî-òî áûëî íàæàòî è íå èçìåíèëîñü => êóäà áåæèì, òóäà è ñìîòðèì:
+    // Что-то было нажато и не изменилось => куда бежим, туда и смотрим:
     else if MoveButton <> 0 then
       plr.SetDirection(TDirection(MoveButton-1))
   end;
@@ -1707,7 +1413,7 @@ begin
   // fix movebutton state
   MoveButton := MoveButton or (strafeDir shl 4);
 
-  // Îñòàëüíûå êëàâèøè:
+  // Остальные клавиши:
   if gPlayerAction[p, ACTION_JUMP] then plr.PressKey(KEY_JUMP, time);
   if gPlayerAction[p, ACTION_LOOKUP] then plr.PressKey(KEY_UP, time);
   if gPlayerAction[p, ACTION_LOOKDOWN] then plr.PressKey(KEY_DOWN, time);
@@ -1816,10 +1522,10 @@ begin
   g_ResetDynlights();
   framePool.reset();
 
-// Ïîðà âûêëþ÷àòü èãðó:
+// Пора выключать игру:
   if gExit = EXIT_QUIT then
     Exit;
-// Èãðà çàêîí÷èëàñü - îáðàáàòûâàåì:
+// Игра закончилась - обрабатываем:
   if gExit <> 0 then
   begin
     EndGame();
@@ -1827,10 +1533,10 @@ begin
       Exit;
   end;
 
-  // ×èòàåì êëàâèàòóðó è äæîéñòèê, åñëè îêíî àêòèâíî
+  // Читаем клавиатуру и джойстик, если окно активно
   // no need to, as we'll do it in event handler
 
-// Îáíîâëÿåì êîíñîëü (äâèæåíèå è ñîîáùåíèÿ):
+// Обновляем консоль (движение и сообщения):
   r_Console_Update;
   g_Console_Update();
 
@@ -1845,10 +1551,10 @@ begin
   g_Net_Slist_Pulse();
 
   case gState of
-    STATE_INTERSINGLE, // Ñòàòèñòêà ïîñëå ïðîõîæäåíèÿ óðîâíÿ â Îäèíî÷íîé èãðå
-    STATE_INTERCUSTOM, // Ñòàòèñòêà ïîñëå ïðîõîæäåíèÿ óðîâíÿ â Ñâîåé èãðå
-    STATE_INTERTEXT, // Òåêñò ìåæäó óðîâíÿìè
-    STATE_INTERPIC: // Êàðòèíêà ìåæäó óðîâíÿìè
+    STATE_INTERSINGLE, // Статистка после прохождения уровня в Одиночной игре
+    STATE_INTERCUSTOM, // Статистка после прохождения уровня в Своей игре
+    STATE_INTERTEXT, // Текст между уровнями
+    STATE_INTERPIC: // Картинка между уровнями
       begin
         if g_Game_IsNet and g_Game_IsServer then
         begin
@@ -1876,19 +1582,19 @@ begin
           or (g_Game_IsNet and ((gInterTime > gInterEndTime) or ((gInterReadyCount >= NetClientCount) and (NetClientCount > 0))))
         )
         then
-        begin // Íàæàëè <Enter>/<Ïðîáåë> èëè ïðîøëî äîñòàòî÷íî âðåìåíè:
+        begin // Нажали <Enter>/<Пробел> или прошло достаточно времени:
           g_Game_StopAllSounds(True);
 
-          if gMapOnce then // Ýòî áûë òåñò
+          if gMapOnce then // Это был тест
             gExit := EXIT_SIMPLE
           else
-            if gNextMap <> '' then // Ïåðåõîäèì íà ñëåäóþùóþ êàðòó
+            if gNextMap <> '' then // Переходим на следующую карту
               g_Game_ChangeMap(gNextMap)
-            else // Ñëåäóþùåé êàðòû íåò
+            else // Следующей карты нет
             begin
               if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER] then
               begin
-              // Âûõîä â ãëàâíîå ìåíþ:
+              // Выход в главное меню:
                 g_Game_Free;
                 g_GUI_ShowWindow('MainMenu');
                 gMusic.SetByName('MUSIC_MENU');
@@ -1896,7 +1602,7 @@ begin
                 gState := STATE_MENU;
               end else
               begin
-              // Ôèíàëüíàÿ êàðòèíêà:
+              // Финальная картинка:
                 g_Game_ExecuteEvent('onwadend');
                 g_Game_Free();
                 if not gMusic.SetByName('MUSIC_endmus') then
@@ -1931,11 +1637,11 @@ begin
             InterText.counter := InterText.counter - 1;
       end;
 
-    STATE_FOLD: // Çàòóõàíèå ýêðàíà
+    STATE_FOLD: // Затухание экрана
       begin
         if EndingGameCounter = 0 then
           begin
-          // Çàêîí÷èëñÿ óðîâåíü â Ñâîåé èãðå:
+          // Закончился уровень в Своей игре:
             if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
               begin
                 gState := STATE_INTERCUSTOM;
@@ -1951,7 +1657,7 @@ begin
                 gMusic.Play();
                 e_UnpressAllKeys();
               end
-            else // Çàêîí÷èëàñü ïîñëåäíÿÿ êàðòà â Îäèíî÷íîé èãðå
+            else // Закончилась последняя карта в Одиночной игре
               begin
                 gMusic.SetByName('MUSIC_INTERMUS');
                 gMusic.Play();
@@ -1964,9 +1670,9 @@ begin
           DecMin(EndingGameCounter, 6, 0);
       end;
 
-    STATE_ENDPIC: // Êàðòèíêà îêîí÷àíèÿ ìåãàÂàäà
+    STATE_ENDPIC: // Картинка окончания мегаВада
       begin
-        if gMapOnce then // Ýòî áûë òåñò
+        if gMapOnce then // Это был тест
         begin
           gExit := EXIT_SIMPLE;
           Exit;
@@ -1977,17 +1683,17 @@ begin
         g_Serverlist_Control(slCurrent, slTable);
   end;
 
-// Ñòàòèñòèêà ïî Tab:
+// Статистика по Tab:
   if gGameOn then
     IsDrawStat := (not gConsoleShow) and (not gChatShow) and (gGameSettings.GameType <> GT_SINGLE) and g_Console_Action(ACTION_SCORES);
 
-// Èãðà èäåò:
+// Игра идет:
   if gGameOn and not gPause and (gState <> STATE_FOLD) then
   begin
-  // Âðåìÿ += 28 ìèëëèñåêóíä:
+  // Время += 28 миллисекунд:
     gTime := gTime + GAME_TICK;
 
-  // Ñîîáùåíèå ïîñåðåäèíå ýêðàíà:
+  // Сообщение посередине экрана:
     if MessageTime = 0 then
       MessageText := '';
     if MessageTime > 0 then
@@ -1995,19 +1701,19 @@ begin
 
     if (g_Game_IsServer) then
     begin
-    // Áûë çàäàí ëèìèò âðåìåíè:
+    // Был задан лимит времени:
       if (gGameSettings.TimeLimit > 0) then
         if (gTime - gGameStartTime) div 1000 >= gGameSettings.TimeLimit then
-        begin // Îí ïðîøåë => êîíåö óðîâíÿ
+        begin // Он прошел => конец уровня
           g_Game_NextLevel();
           Exit;
         end;
 
-    // Íàäî ðåñïàâíèòü èãðîêîâ â LMS:
+    // Надо респавнить игроков в LMS:
       if (gLMSRespawn > LMS_RESPAWN_NONE) and (gLMSRespawnTime < gTime) then
         g_Game_RestartRound(gLMSSoftSpawn);
 
-    // Ïðîâåðèì ðåçóëüòàò ãîëîñîâàíèÿ, åñëè âðåìÿ ïðîøëî
+    // Проверим результат голосования, если время прошло
       if gVoteInProgress and (gVoteTimer < gTime) then
         g_Game_CheckVote
       else if gVotePassed and (gVoteCmdTimer < gTime) then
@@ -2017,19 +1723,19 @@ begin
         gVotePassed := False;
       end;
 
-    // Çàìåðÿåì âðåìÿ çàõâàòà ôëàãîâ
+    // Замеряем время захвата флагов
       if gFlags[FLAG_RED].State = FLAG_STATE_CAPTURED then
         gFlags[FLAG_RED].CaptureTime := gFlags[FLAG_RED].CaptureTime + GAME_TICK;
       if gFlags[FLAG_BLUE].State = FLAG_STATE_CAPTURED then
         gFlags[FLAG_BLUE].CaptureTime := gFlags[FLAG_BLUE].CaptureTime + GAME_TICK;
 
-    // Áûë çàäàí ëèìèò ïîáåä:
+    // Был задан лимит побед:
       if (gGameSettings.ScoreLimit > 0) then
       begin
         b := 0;
 
         if gGameSettings.GameMode = GM_DM then
-          begin // Â DM èùåì èãðîêà ñ max ôðàãàìè
+          begin // В DM ищем игрока с max фрагами
             for i := 0 to High(gPlayers) do
               if gPlayers[i] <> nil then
                 if gPlayers[i].Frags > b then
@@ -2037,11 +1743,11 @@ begin
           end
         else
           if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
-          begin // Â CTF/TDM âûáèðàåì êîìàíäó ñ íàèáîëüøèì ñ÷åòîì
+          begin // В CTF/TDM выбираем команду с наибольшим счетом
             b := Max(gTeamStat[TEAM_RED].Score, gTeamStat[TEAM_BLUE].Score);
           end;
 
-      // Ëèìèò ïîáåä íàáðàí => êîíåö óðîâíÿ:
+      // Лимит побед набран => конец уровня:
         if b >= gGameSettings.ScoreLimit then
         begin
           g_Game_NextLevel();
@@ -2049,7 +1755,7 @@ begin
         end;
       end;
 
-    // Îáðàáàòûâàåì êëàâèøè èãðîêîâ:
+    // Обрабатываем клавиши игроков:
       if gPlayer1 <> nil then gPlayer1.ReleaseKeys();
       if gPlayer2 <> nil then gPlayer2.ReleaseKeys();
       if (not gConsoleShow) and (not gChatShow) and (g_ActiveWindow = nil) then
@@ -2064,7 +1770,7 @@ begin
       // process weapon switch queue
     end; // if server
 
-  // Íàáëþäàòåëü
+  // Наблюдатель
     if (gPlayer1 = nil) and (gPlayer2 = nil) and
        (not gConsoleShow) and (not gChatShow) and (g_ActiveWindow = nil) then
     begin
@@ -2221,7 +1927,7 @@ begin
       end;
     end;
 
-  // Îáíîâëÿåì âñå îñòàëüíîå:
+  // Обновляем все остальное:
     g_Map_Update();
     g_Items_Update();
     g_Triggers_Update();
@@ -2319,7 +2025,7 @@ begin
     end;
   end; // if gameOn ...
 
-// Àêòèâíî îêíî èíòåðôåéñà - ïåðåäàåì êëàâèøè åìó:
+// Активно окно интерфейса - передаем клавиши ему:
   if g_ActiveWindow <> nil then
   begin
     w := e_GetFirstKeyPressed();
@@ -2331,11 +2037,11 @@ begin
         g_ActiveWindow.OnMessage(Msg);
       end;
 
-  // Åñëè îíî îò ýòîãî íå çàêðûëîñü, òî îáíîâëÿåì:
+  // Если оно от этого не закрылось, то обновляем:
     if g_ActiveWindow <> nil then
       g_ActiveWindow.Update();
 
-  // Íóæíî ñìåíèòü ðàçðåøåíèå:
+  // Нужно сменить разрешение:
     if gResolutionChange then
     begin
       e_WriteLog('Changing resolution', TMsgType.Notify);
@@ -2344,7 +2050,7 @@ begin
       g_ActiveWindow := nil;
     end;
 
-  // Íóæíî ñìåíèòü ÿçûê:
+  // Нужно сменить язык:
     if gLanguageChange then
     begin
       //e_WriteLog('Read language file', MSG_NOTIFY);
@@ -2357,7 +2063,7 @@ begin
     end;
   end;
 
-// Ãîðÿ÷àÿ êëàâèøà äëÿ âûçîâà ìåíþ âûõîäà èç èãðû (F10):
+// Горячая клавиша для вызова меню выхода из игры (F10):
   if e_KeyPressed(IK_F10) and
      gGameOn and
      (not gConsoleShow) and
@@ -2368,7 +2074,7 @@ begin
 
   Time := sys_GetTicks() {div 1000};
 
-// Îáðàáîòêà îòëîæåííûõ ñîáûòèé:
+// Обработка отложенных событий:
   if gDelayedEvents <> nil then
     for a := 0 to High(gDelayedEvents) do
       if gDelayedEvents[a].Pending and
@@ -2397,7 +2103,7 @@ begin
         gDelayedEvents[a].Pending := False;
       end;
 
-// Êàæäóþ ñåêóíäó îáíîâëÿåì ñ÷åò÷èê îáíîâëåíèé:
+// Каждую секунду обновляем счетчик обновлений:
   UPSCounter := UPSCounter + 1;
   if Time - UPSTime >= 1000 then
   begin
@@ -2618,6 +2324,28 @@ begin
   DataLoaded := True;
 end;
 
+procedure g_Game_Quit();
+begin
+  g_Game_StopAllSounds(True);
+  gMusic.Free();
+  g_Game_FreeData();
+  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;
+
+// remove map after test
+  if gMapToDelete <> '' then
+    g_Game_DeleteTestMap();
+
+  gExit := EXIT_QUIT;
+  sys_RequestQuit;
+end;
+
 procedure g_Game_FreeData();
 begin
   if not DataLoaded then Exit;
@@ -2711,1572 +2439,6 @@ begin
   DataLoaded := False;
 end;
 
-procedure DrawCustomStat();
-var
-  pc, x, y, w, _y,
-  w1, w2, w3,
-  t, p, m: Integer;
-  ww1, hh1: Word;
-  ww2, hh2, r, g, b, rr, gg, bb: Byte;
-  s1, s2, topstr: String;
-begin
-  e_TextureFontGetSize(gStdFont, ww2, hh2);
-
-  sys_HandleInput;
-
-  if g_Console_Action(ACTION_SCORES) then
-  begin
-    if not gStatsPressed then
-    begin
-      gStatsOff := not gStatsOff;
-      gStatsPressed := True;
-    end;
-  end
-  else
-    gStatsPressed := False;
-
-  if gStatsOff then
-  begin
-    s1 := _lc[I_MENU_INTER_NOTICE_TAB];
-    w := (Length(s1) * ww2) div 2;
-    x := gScreenWidth div 2 - w;
-    y := 8;
-    e_TextureFontPrint(x, y, s1, gStdFont);
-    Exit;
-  end;
-
-  if (gGameSettings.GameMode = GM_COOP) then
-  begin
-    if gMissionFailed then
-      topstr := _lc[I_MENU_INTER_MISSION_FAIL]
-    else
-      topstr := _lc[I_MENU_INTER_LEVEL_COMPLETE];
-  end
-  else
-    topstr := _lc[I_MENU_INTER_ROUND_OVER];
-
-  e_CharFont_GetSize(gMenuFont, topstr, ww1, hh1);
-  e_CharFont_Print(gMenuFont, (gScreenWidth div 2)-(ww1 div 2), 16, topstr);
-
-  if g_Game_IsNet then
-  begin
-    topstr := Format(_lc[I_MENU_INTER_NOTICE_TIME], [gServInterTime]);
-    if not gChatShow then
-      e_TextureFontPrintEx((gScreenWidth div 2)-(Length(topstr)*ww2 div 2),
-                           gScreenHeight-(hh2+4)*2, topstr, gStdFont, 255, 255, 255, 1);
-  end;
-
-  if g_Game_IsClient then
-    topstr := _lc[I_MENU_INTER_NOTICE_MAP]
-  else
-    topstr := _lc[I_MENU_INTER_NOTICE_SPACE];
-  if not gChatShow then
-    e_TextureFontPrintEx((gScreenWidth div 2)-(Length(topstr)*ww2 div 2),
-                         gScreenHeight-(hh2+4), topstr, gStdFont, 255, 255, 255, 1);
-
-  x := 32;
-  y := 16+hh1+16;
-
-  w := gScreenWidth-x*2;
-
-  w2 := (w-16) div 6;
-  w3 := w2;
-  w1 := w-16-w2-w3;
-
-  e_DrawFillQuad(x, y, gScreenWidth-x-1, gScreenHeight-y-1, 64, 64, 64, 32);
-  e_DrawQuad(x, y, gScreenWidth-x-1, gScreenHeight-y-1, 255, 127, 0);
-
-  m := Max(Length(_lc[I_MENU_MAP])+1, Length(_lc[I_GAME_GAME_TIME])+1)*ww2;
-
-  case CustomStat.GameMode of
-    GM_DM:
-    begin
-      if gGameSettings.MaxLives = 0 then
-        s1 := _lc[I_GAME_DM]
-      else
-        s1 := _lc[I_GAME_LMS];
-    end;
-    GM_TDM:
-    begin
-      if gGameSettings.MaxLives = 0 then
-        s1 := _lc[I_GAME_TDM]
-      else
-        s1 := _lc[I_GAME_TLMS];
-    end;
-    GM_CTF: s1 := _lc[I_GAME_CTF];
-    GM_COOP:
-    begin
-      if gGameSettings.MaxLives = 0 then
-        s1 := _lc[I_GAME_COOP]
-      else
-        s1 := _lc[I_GAME_SURV];
-    end;
-    else s1 := '';
-  end;
-
-  _y := y+16;
-  e_TextureFontPrintEx(x+(w div 2)-(Length(s1)*ww2 div 2), _y, s1, gStdFont, 255, 255, 255, 1);
-  _y := _y+8;
-
-  _y := _y+16;
-  e_TextureFontPrintEx(x+8, _y, _lc[I_MENU_MAP], gStdFont, 255, 127, 0, 1);
-  e_TextureFontPrint(x+8+m, _y, Format('%s - %s', [CustomStat.Map, CustomStat.MapName]), gStdFont);
-
-  _y := _y+16;
-  e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_GAME_TIME], gStdFont, 255, 127, 0, 1);
-  e_TextureFontPrint(x+8+m, _y, Format('%d:%.2d:%.2d', [CustomStat.GameTime div 1000 div 3600,
-                                                       (CustomStat.GameTime div 1000 div 60) mod 60,
-                                                        CustomStat.GameTime div 1000 mod 60]), gStdFont);
-
-  pc := Length(CustomStat.PlayerStat);
-  if pc = 0 then Exit;
-
-  if CustomStat.GameMode = GM_COOP then
-  begin
-    m := Max(Length(_lc[I_GAME_MONSTERS])+1, Length(_lc[I_GAME_SECRETS])+1)*ww2;
-    _y := _y+32;
-    s2 := _lc[I_GAME_MONSTERS];
-    e_TextureFontPrintEx(x+8, _y, s2, gStdFont, 255, 127, 0, 1);
-    e_TextureFontPrintEx(x+8+m, _y, IntToStr(gCoopMonstersKilled) + '/' + IntToStr(gTotalMonsters), gStdFont, 255, 255, 255, 1);
-    _y := _y+16;
-    s2 := _lc[I_GAME_SECRETS];
-    e_TextureFontPrintEx(x+8, _y, s2, gStdFont, 255, 127, 0, 1);
-    e_TextureFontPrintEx(x+8+m, _y, IntToStr(gCoopSecretsFound) + '/' + IntToStr(gSecretsCount), gStdFont, 255, 255, 255, 1);
-    if gLastMap then
-    begin
-      m := Max(Length(_lc[I_GAME_MONSTERS_TOTAL])+1, Length(_lc[I_GAME_SECRETS_TOTAL])+1)*ww2;
-      _y := _y-16;
-      s2 := _lc[I_GAME_MONSTERS_TOTAL];
-      e_TextureFontPrintEx(x+250, _y, s2, gStdFont, 255, 127, 0, 1);
-      e_TextureFontPrintEx(x+250+m, _y, IntToStr(gCoopTotalMonstersKilled) + '/' + IntToStr(gCoopTotalMonsters), gStdFont, 255, 255, 255, 1);
-      _y := _y+16;
-      s2 := _lc[I_GAME_SECRETS_TOTAL];
-      e_TextureFontPrintEx(x+250, _y, s2, gStdFont, 255, 127, 0, 1);
-      e_TextureFontPrintEx(x+250+m, _y, IntToStr(gCoopTotalSecretsFound) + '/' + IntToStr(gCoopTotalSecrets), gStdFont, 255, 255,  255, 1);
-    end;
-  end;
-
-  if CustomStat.GameMode in [GM_TDM, GM_CTF] then
-  begin
-    _y := _y+16+16;
-
-    with CustomStat do
-      if TeamStat[TEAM_RED].Score > TeamStat[TEAM_BLUE].Score then s1 := _lc[I_GAME_WIN_RED]
-        else if TeamStat[TEAM_BLUE].Score > TeamStat[TEAM_RED].Score then s1 := _lc[I_GAME_WIN_BLUE]
-          else s1 := _lc[I_GAME_WIN_DRAW];
-
-    e_TextureFontPrintEx(x+8+(w div 2)-(Length(s1)*ww2 div 2), _y, s1, gStdFont, 255, 255, 255, 1);
-    _y := _y+40;
-
-    for t := TEAM_RED to TEAM_BLUE do
-    begin
-      if t = TEAM_RED then
-      begin
-        e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_TEAM_RED],
-                             gStdFont, 255, 0, 0, 1);
-        e_TextureFontPrintEx(x+w1+8, _y, IntToStr(CustomStat.TeamStat[TEAM_RED].Score),
-                             gStdFont, 255, 0, 0, 1);
-        r := 255;
-        g := 0;
-        b := 0;
-      end
-      else
-      begin
-        e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_TEAM_BLUE],
-                             gStdFont, 0, 0, 255, 1);
-        e_TextureFontPrintEx(x+w1+8, _y, IntToStr(CustomStat.TeamStat[TEAM_BLUE].Score),
-                             gStdFont, 0, 0, 255, 1);
-        r := 0;
-        g := 0;
-        b := 255;
-      end;
-
-      e_DrawLine(1, x+8, _y+20, x-8+w, _y+20, r, g, b);
-      _y := _y+24;
-
-      for p := 0 to High(CustomStat.PlayerStat) do
-        if CustomStat.PlayerStat[p].Team = t then
-          with CustomStat.PlayerStat[p] do
-          begin
-            if Spectator then
-            begin
-              rr := r div 2;
-              gg := g div 2;
-              bb := b div 2;
-            end
-            else
-            begin
-              rr := r;
-              gg := g;
-              bb := b;
-            end;
-            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;
-
-      _y := _y+16+16;
-    end;
-  end
-  else if CustomStat.GameMode in [GM_DM, GM_COOP] then
-  begin
-    _y := _y+40;
-    e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_PLAYER_NAME], gStdFont, 255, 127, 0, 1);
-    e_TextureFontPrintEx(x+8+w1, _y, _lc[I_GAME_FRAGS], gStdFont, 255, 127, 0, 1);
-    e_TextureFontPrintEx(x+8+w1+w2, _y, _lc[I_GAME_DEATHS], gStdFont, 255, 127, 0, 1);
-
-    _y := _y+24;
-    for p := 0 to High(CustomStat.PlayerStat) do
-      with CustomStat.PlayerStat[p] do
-      begin
-        e_DrawFillQuad(x+8, _y+4, x+24-1, _y+16+4-1, Color.R, Color.G, Color.B, 0);
-
-        if Spectator then
-          r := 127
-        else
-          r := 255;
-
-        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;
-      end;
-  end;
-
-  // HACK: take stats screenshot immediately after the first frame of the stats showing
-  if gScreenshotStats and (not StatShotDone) and (Length(CustomStat.PlayerStat) > 1) then
-  begin
-    g_TakeScreenShot('stats/' + StatFilename);
-    StatShotDone := True;
-  end;
-end;
-
-procedure DrawSingleStat();
-var
-  tm, key_x, val_x, y: Integer;
-  w1, w2, h: Word;
-  s1, s2: String;
-
-  procedure player_stat(n: Integer);
-  var
-    kpm: Real;
-
-  begin
-  // "Kills: # / #":
-    s1 := Format(' %d ', [SingleStat.PlayerStat[n].Kills]);
-    s2 := Format(' %d', [gTotalMonsters]);
-
-    e_CharFont_Print(gMenuFont, key_x, y, _lc[I_MENU_INTER_KILLS]);
-    e_CharFont_PrintEx(gMenuFont, val_x, y, s1, _RGB(255, 0, 0));
-    e_CharFont_GetSize(gMenuFont, s1, w1, h);
-    e_CharFont_Print(gMenuFont, val_x+w1, y, '/');
-    s1 := s1 + '/';
-    e_CharFont_GetSize(gMenuFont, s1, w1, h);
-    e_CharFont_PrintEx(gMenuFont, val_x+w1, y, s2, _RGB(255, 0, 0));
-
-  // "Kills-per-minute: ##.#":
-    s1 := _lc[I_MENU_INTER_KPM];
-    if tm > 0 then
-      kpm := (SingleStat.PlayerStat[n].Kills / tm) * 60
-    else
-      kpm := SingleStat.PlayerStat[n].Kills;
-    s2 := Format(' %.1f', [kpm]);
-
-    e_CharFont_Print(gMenuFont, key_x, y+32, s1);
-    e_CharFont_PrintEx(gMenuFont, val_x, y+32, s2, _RGB(255, 0, 0));
-
-  // "Secrets found: # / #":
-    s1 := Format(' %d ', [SingleStat.PlayerStat[n].Secrets]);
-    s2 := Format(' %d', [SingleStat.TotalSecrets]);
-
-    e_CharFont_Print(gMenuFont, key_x, y+64, _lc[I_MENU_INTER_SECRETS]);
-    e_CharFont_PrintEx(gMenuFont, val_x, y+64, s1, _RGB(255, 0, 0));
-    e_CharFont_GetSize(gMenuFont, s1, w1, h);
-    e_CharFont_Print(gMenuFont, val_x+w1, y+64, '/');
-    s1 := s1 + '/';
-    e_CharFont_GetSize(gMenuFont, s1, w1, h);
-    e_CharFont_PrintEx(gMenuFont, val_x+w1, y+64, s2, _RGB(255, 0, 0));
-  end;
-
-begin
-// "Level Complete":
-  e_CharFont_GetSize(gMenuFont, _lc[I_MENU_INTER_LEVEL_COMPLETE], w1, h);
-  e_CharFont_Print(gMenuFont, (gScreenWidth-w1) div 2, 32, _lc[I_MENU_INTER_LEVEL_COMPLETE]);
-
-// Îïðåäåëÿåì êîîðäèíàòû âûðàâíèâàíèÿ ïî ñàìîé äëèííîé ñòðîêå:
-  s1 := _lc[I_MENU_INTER_KPM];
-  e_CharFont_GetSize(gMenuFont, s1, w1, h);
-  Inc(w1, 16);
-  s1 := ' 9999.9';
-  e_CharFont_GetSize(gMenuFont, s1, w2, h);
-
-  key_x := (gScreenWidth-w1-w2) div 2;
-  val_x := key_x + w1;
-
-// "Time: #:##:##":
-  tm := SingleStat.GameTime div 1000;
-  s1 := _lc[I_MENU_INTER_TIME];
-  s2 := Format(' %d:%.2d:%.2d', [tm div (60*60), (tm mod (60*60)) div 60, tm mod 60]);
-
-  e_CharFont_Print(gMenuFont, key_x, 80, s1);
-  e_CharFont_PrintEx(gMenuFont, val_x, 80, s2, _RGB(255, 0, 0));
-
-  if SingleStat.TwoPlayers then
-    begin
-    // "Player 1":
-      s1 := _lc[I_MENU_PLAYER_1];
-      e_CharFont_GetSize(gMenuFont, s1, w1, h);
-      e_CharFont_Print(gMenuFont, (gScreenWidth-w1) div 2, 128, s1);
-
-    // Ñòàòèñòèêà ïåðâîãî èãðîêà:
-      y := 176;
-      player_stat(0);
-
-    // "Player 2":
-      s1 := _lc[I_MENU_PLAYER_2];
-      e_CharFont_GetSize(gMenuFont, s1, w1, h);
-      e_CharFont_Print(gMenuFont, (gScreenWidth-w1) div 2, 288, s1);
-
-    // Ñòàòèñòèêà âòîðîãî èãðîêà:
-      y := 336;
-      player_stat(1);
-    end
-  else
-    begin
-    // Ñòàòèñòèêà ïåðâîãî èãðîêà:
-      y := 128;
-      player_stat(0);
-    end;
-end;
-
-procedure DrawLoadingStat();
-  procedure drawRect (x, y, w, h: Integer);
-  begin
-    if (w < 1) or (h < 1) then exit;
-    glBegin(GL_QUADS);
-      glVertex2f(x+0.375, y+0.375);
-      glVertex2f(x+w+0.375, y+0.375);
-      glVertex2f(x+w+0.375, y+h+0.375);
-      glVertex2f(x+0.375, y+h+0.375);
-    glEnd();
-  end;
-
-  function drawPBar (cur, total: Integer; washere: Boolean): Boolean;
-  var
-    rectW, rectH: Integer;
-    x0, y0: Integer;
-    wdt: Integer;
-    wl, hl: Integer;
-    wr, hr: Integer;
-    wb, hb: Integer;
-    wm, hm: Integer;
-    idl, idr, idb, idm: LongWord;
-    f, my: Integer;
-  begin
-    result := false;
-    if (total < 1) then exit;
-    if (cur < 1) then exit; // don't blink
-    if (not washere) and (cur >= total) then exit; // don't blink
-    //if (cur < 0) then cur := 0;
-    //if (cur > total) then cur := total;
-    result := true;
-
-    if (hasPBarGfx) then
-    begin
-      g_Texture_Get('UI_GFX_PBAR_LEFT', idl);
-      g_Texture_GetSize('UI_GFX_PBAR_LEFT', wl, hl);
-      g_Texture_Get('UI_GFX_PBAR_RIGHT', idr);
-      g_Texture_GetSize('UI_GFX_PBAR_RIGHT', wr, hr);
-      g_Texture_Get('UI_GFX_PBAR_MIDDLE', idb);
-      g_Texture_GetSize('UI_GFX_PBAR_MIDDLE', wb, hb);
-      g_Texture_Get('UI_GFX_PBAR_MARKER', idm);
-      g_Texture_GetSize('UI_GFX_PBAR_MARKER', wm, hm);
-
-      //rectW := gScreenWidth-360;
-      rectW := trunc(624.0*gScreenWidth/1024.0);
-      rectH := hl;
-
-      x0 := (gScreenWidth-rectW) div 2;
-      y0 := gScreenHeight-rectH-64;
-      if (y0 < 2) then y0 := 2;
-
-      glEnable(GL_SCISSOR_TEST);
-
-      // left and right
-      glScissor(x0, gScreenHeight-y0-rectH, rectW, rectH);
-      e_DrawSize(idl, x0, y0, 0, true, false, wl, hl);
-      e_DrawSize(idr, x0+rectW-wr, y0, 0, true, false, wr, hr);
-
-      // body
-      glScissor(x0+wl, gScreenHeight-y0-rectH, rectW-wl-wr, rectH);
-      f := x0+wl;
-      while (f < x0+rectW) do
-      begin
-        e_DrawSize(idb, f, y0, 0, true, false, wb, hb);
-        f += wb;
-      end;
-
-      // filled part
-      wdt := (rectW-wl-wr)*cur div total;
-      if (wdt > rectW-wl-wr) then wdt := rectW-wr-wr;
-      if (wdt > 0) then
-      begin
-        my := y0; // don't be so smart, ketmar: +(rectH-wm) div 2;
-        glScissor(x0+wl, gScreenHeight-my-rectH, wdt, hm);
-        f := x0+wl;
-        while (wdt > 0) do
-        begin
-          e_DrawSize(idm, f, y0, 0, true, false, wm, hm);
-          f += wm;
-          wdt -= wm;
-        end;
-      end;
-
-      glScissor(0, 0, gScreenWidth, gScreenHeight);
-    end
-    else
-    begin
-      rectW := gScreenWidth-64;
-      rectH := 16;
-
-      x0 := (gScreenWidth-rectW) div 2;
-      y0 := gScreenHeight-rectH-64;
-      if (y0 < 2) then y0 := 2;
-
-      glDisable(GL_BLEND);
-      glDisable(GL_TEXTURE_2D);
-
-      //glClearColor(0, 0, 0, 0);
-      //glClear(GL_COLOR_BUFFER_BIT);
-
-      glColor4ub(127, 127, 127, 255);
-      drawRect(x0-2, y0-2, rectW+4, rectH+4);
-
-      glColor4ub(0, 0, 0, 255);
-      drawRect(x0-1, y0-1, rectW+2, rectH+2);
-
-      glColor4ub(127, 127, 127, 255);
-      wdt := rectW*cur div total;
-      if (wdt > rectW) then wdt := rectW;
-      drawRect(x0, y0, wdt, rectH);
-    end;
-  end;
-
-var
-  ww, hh: Word;
-  xx, yy, i: Integer;
-  s: String;
-begin
-  if (Length(LoadingStat.Msgs) = 0) then exit;
-
-  e_CharFont_GetSize(gMenuFont, _lc[I_MENU_LOADING], ww, hh);
-  yy := (gScreenHeight div 3);
-  e_CharFont_Print(gMenuFont, (gScreenWidth div 2)-(ww div 2), yy-2*hh, _lc[I_MENU_LOADING]);
-  xx := (gScreenWidth div 3);
-
-  with LoadingStat do
-  begin
-    for i := 0 to NextMsg-1 do
-    begin
-      if (i = (NextMsg-1)) and (MaxValue > 0) then
-        s := Format('%s:  %d/%d', [Msgs[i], CurValue, MaxValue])
-      else
-        s := Msgs[i];
-
-      e_CharFont_PrintEx(gMenuSmallFont, xx, yy, s, _RGB(255, 0, 0));
-      yy := yy + LOADING_INTERLINE;
-      PBarWasHere := drawPBar(CurValue, MaxValue, PBarWasHere);
-    end;
-  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;
-
-  function monDraw (mon: TMonster): Boolean;
-  begin
-    result := false; // don't stop
-    with mon do
-    begin
-      if alive then
-      begin
-        // Ëåâûé âåðõíèé óãîë
-        aX := Obj.X div ScaleSz + 1;
-        aY := Obj.Y div ScaleSz + 1;
-        // Ðàçìåðû
-        aX2 := max(Obj.Rect.Width div ScaleSz, 1);
-        aY2 := max(Obj.Rect.Height div ScaleSz, 1);
-        // Ïðàâûé íèæíèé óãîë
-        aX2 := aX + aX2 - 1;
-        aY2 := aY + aY2 - 1;
-        e_DrawFillQuad(aX, aY, aX2, aY2, 255, 255, 0, 0);
-      end;
-    end;
-  end;
-
-begin
-  if (gMapInfo.Width > RenderRect.Right - RenderRect.Left) or
-     (gMapInfo.Height > RenderRect.Bottom - RenderRect.Top) then
-  begin
-    Scale := 1;
-  // Ñêîëüêî ïèêñåëîâ êàðòû â 1 ïèêñåëå ìèíè-êàðòû:
-    ScaleSz := 16 div Scale;
-  // Ðàçìåðû ìèíè-êàðòû:
-    aX := max(gMapInfo.Width div ScaleSz, 1);
-    aY := max(gMapInfo.Height div ScaleSz, 1);
-  // Ðàìêà êàðòû:
-    e_DrawFillQuad(0, 0, aX-1, aY-1, 0, 0, 0, 0);
-
-    if gWalls <> nil then
-    begin
-    // Ðèñóåì ñòåíû:
-      for a := 0 to High(gWalls) do
-        with gWalls[a] do
-          if PanelType <> 0 then
-          begin
-          // Ëåâûé âåðõíèé óãîë:
-            aX := X div ScaleSz;
-            aY := Y div ScaleSz;
-          // Ðàçìåðû:
-            aX2 := max(Width div ScaleSz, 1);
-            aY2 := max(Height div ScaleSz, 1);
-          // Ïðàâûé íèæíèé óãîë:
-            aX2 := aX + aX2 - 1;
-            aY2 := aY + aY2 - 1;
-
-            case PanelType of
-              PANEL_WALL:      e_DrawFillQuad(aX, aY, aX2, aY2, 208, 208, 208, 0);
-              PANEL_OPENDOOR, PANEL_CLOSEDOOR:
-                if Enabled then e_DrawFillQuad(aX, aY, aX2, aY2, 160, 160, 160, 0);
-            end;
-          end;
-    end;
-    if gSteps <> nil then
-    begin
-    // Ðèñóåì ñòóïåíè:
-      for a := 0 to High(gSteps) do
-        with gSteps[a] do
-          if PanelType <> 0 then
-          begin
-          // Ëåâûé âåðõíèé óãîë:
-            aX := X div ScaleSz;
-            aY := Y div ScaleSz;
-          // Ðàçìåðû:
-            aX2 := max(Width div ScaleSz, 1);
-            aY2 := max(Height div ScaleSz, 1);
-          // Ïðàâûé íèæíèé óãîë:
-            aX2 := aX + aX2 - 1;
-            aY2 := aY + aY2 - 1;
-
-            e_DrawFillQuad(aX, aY, aX2, aY2, 128, 128, 128, 0);
-          end;
-    end;
-    if gLifts <> nil then
-    begin
-    // Ðèñóåì ëèôòû:
-      for a := 0 to High(gLifts) do
-        with gLifts[a] do
-          if PanelType <> 0 then
-          begin
-          // Ëåâûé âåðõíèé óãîë:
-            aX := X div ScaleSz;
-            aY := Y div ScaleSz;
-          // Ðàçìåðû:
-            aX2 := max(Width div ScaleSz, 1);
-            aY2 := max(Height div ScaleSz, 1);
-          // Ïðàâûé íèæíèé óãîë:
-            aX2 := aX + aX2 - 1;
-            aY2 := aY + aY2 - 1;
-
-            case LiftType of
-              LIFTTYPE_UP:    e_DrawFillQuad(aX, aY, aX2, aY2, 116,  72,  36, 0);
-              LIFTTYPE_DOWN:  e_DrawFillQuad(aX, aY, aX2, aY2, 116, 124,  96, 0);
-              LIFTTYPE_LEFT:  e_DrawFillQuad(aX, aY, aX2, aY2, 200,  80,   4, 0);
-              LIFTTYPE_RIGHT: e_DrawFillQuad(aX, aY, aX2, aY2, 252, 140,  56, 0);
-            end;
-          end;
-    end;
-    if gWater <> nil then
-    begin
-    // Ðèñóåì âîäó:
-      for a := 0 to High(gWater) do
-        with gWater[a] do
-          if PanelType <> 0 then
-          begin
-          // Ëåâûé âåðõíèé óãîë:
-            aX := X div ScaleSz;
-            aY := Y div ScaleSz;
-          // Ðàçìåðû:
-            aX2 := max(Width div ScaleSz, 1);
-            aY2 := max(Height div ScaleSz, 1);
-          // Ïðàâûé íèæíèé óãîë:
-            aX2 := aX + aX2 - 1;
-            aY2 := aY + aY2 - 1;
-
-            e_DrawFillQuad(aX, aY, aX2, aY2, 0, 0, 192, 0);
-          end;
-    end;
-    if gAcid1 <> nil then
-    begin
-    // Ðèñóåì êèñëîòó 1:
-      for a := 0 to High(gAcid1) do
-        with gAcid1[a] do
-          if PanelType <> 0 then
-          begin
-          // Ëåâûé âåðõíèé óãîë:
-            aX := X div ScaleSz;
-            aY := Y div ScaleSz;
-          // Ðàçìåðû:
-            aX2 := max(Width div ScaleSz, 1);
-            aY2 := max(Height div ScaleSz, 1);
-          // Ïðàâûé íèæíèé óãîë:
-            aX2 := aX + aX2 - 1;
-            aY2 := aY + aY2 - 1;
-
-            e_DrawFillQuad(aX, aY, aX2, aY2, 0, 176, 0, 0);
-          end;
-    end;
-    if gAcid2 <> nil then
-    begin
-    // Ðèñóåì êèñëîòó 2:
-      for a := 0 to High(gAcid2) do
-        with gAcid2[a] do
-          if PanelType <> 0 then
-          begin
-          // Ëåâûé âåðõíèé óãîë:
-            aX := X div ScaleSz;
-            aY := Y div ScaleSz;
-          // Ðàçìåðû:
-            aX2 := max(Width div ScaleSz, 1);
-            aY2 := max(Height div ScaleSz, 1);
-          // Ïðàâûé íèæíèé óãîë:
-            aX2 := aX + aX2 - 1;
-            aY2 := aY + aY2 - 1;
-
-            e_DrawFillQuad(aX, aY, aX2, aY2, 176, 0, 0, 0);
-          end;
-    end;
-    if gPlayers <> nil then
-    begin
-    // Ðèñóåì èãðîêîâ:
-      for a := 0 to High(gPlayers) do
-        if gPlayers[a] <> nil then with gPlayers[a] do
-          if alive then begin
-          // Ëåâûé âåðõíèé óãîë:
-            aX := Obj.X div ScaleSz + 1;
-            aY := Obj.Y div ScaleSz + 1;
-          // Ðàçìåðû:
-            aX2 := max(Obj.Rect.Width div ScaleSz, 1);
-            aY2 := max(Obj.Rect.Height div ScaleSz, 1);
-          // Ïðàâûé íèæíèé óãîë:
-            aX2 := aX + aX2 - 1;
-            aY2 := aY + aY2 - 1;
-
-            if gPlayers[a] = p then
-              e_DrawFillQuad(aX, aY, aX2, aY2, 0, 255, 0, 0)
-            else
-              case Team of
-                TEAM_RED:  e_DrawFillQuad(aX, aY, aX2, aY2, 255,   0,   0, 0);
-                TEAM_BLUE: e_DrawFillQuad(aX, aY, aX2, aY2, 0,     0, 255, 0);
-                else       e_DrawFillQuad(aX, aY, aX2, aY2, 255, 128,   0, 0);
-              end;
-          end;
-    end;
-    // Ðèñóåì ìîíñòðîâ
-    g_Mons_ForEach(monDraw);
-  end;
-end;
-
-
-procedure renderAmbientQuad (hasAmbient: Boolean; constref ambColor: TDFColor);
-begin
-  if not hasAmbient then exit;
-  e_AmbientQuad(sX, sY, sWidth, sHeight, ambColor.r, ambColor.g, ambColor.b, ambColor.a);
-end;
-
-
-// setup sX, sY, sWidth, sHeight, and transformation matrix before calling this!
-//FIXME: broken for splitscreen mode
-procedure renderDynLightsInternal ();
-var
-  //hasAmbient: Boolean;
-  //ambColor: TDFColor;
-  lln: Integer;
-  lx, ly, lrad: Integer;
-  scxywh: array[0..3] of GLint;
-  wassc: Boolean;
-begin
-  if e_NoGraphics then exit;
-
-  //TODO: lights should be in separate grid, i think
-  //      but on the other side: grid may be slower for dynlights, as their lifetime is short
-  if (not gwin_k8_enable_light_experiments) or (not gwin_has_stencil) or (g_dynLightCount < 1) then exit;
-
-  // rendering mode
-  //ambColor := gCurrentMap['light_ambient'].rgba;
-  //hasAmbient := (not ambColor.isOpaque) or (not ambColor.isBlack);
-
-  { // this will multiply incoming color to alpha from framebuffer
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_DST_ALPHA, GL_ONE);
-  }
-
-  (*
-   * light rendering: (INVALID!)
-   *   glStencilFunc(GL_EQUAL, 0, $ff);
-   *   for each light:
-   *     glClear(GL_STENCIL_BUFFER_BIT);
-   *     glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
-   *     draw shadow volume into stencil buffer
-   *     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // modify color buffer
-   *     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // don't modify stencil buffer
-   *     turn off blending
-   *     draw color-less quad with light alpha (WARNING! don't touch color!)
-   *     glEnable(GL_BLEND);
-   *     glBlendFunc(GL_DST_ALPHA, GL_ONE);
-   *     draw all geometry up to and including walls (with alpha-testing, probably) -- this does lighting
-   *)
-  wassc := (glIsEnabled(GL_SCISSOR_TEST) <> 0);
-  if wassc then glGetIntegerv(GL_SCISSOR_BOX, @scxywh[0]) else glGetIntegerv(GL_VIEWPORT, @scxywh[0]);
-
-  // setup OpenGL parameters
-  glStencilMask($FFFFFFFF);
-  glStencilFunc(GL_ALWAYS, 0, $FFFFFFFF);
-  glEnable(GL_STENCIL_TEST);
-  glEnable(GL_SCISSOR_TEST);
-  glClear(GL_STENCIL_BUFFER_BIT);
-  glStencilFunc(GL_EQUAL, 0, $ff);
-
-  for lln := 0 to g_dynLightCount-1 do
-  begin
-    lx := g_dynLights[lln].x;
-    ly := g_dynLights[lln].y;
-    lrad := g_dynLights[lln].radius;
-    if (lrad < 3) then continue;
-
-    if (lx-sX+lrad < 0) then continue;
-    if (ly-sY+lrad < 0) then continue;
-    if (lx-sX-lrad >= gPlayerScreenSize.X) then continue;
-    if (ly-sY-lrad >= gPlayerScreenSize.Y) then continue;
-
-    // set scissor to optimize drawing
-    if (g_dbg_scale = 1.0) then
-    begin
-      glScissor((lx-sX)-lrad+2, gPlayerScreenSize.Y-(ly-sY)-lrad-1+2, lrad*2-4, lrad*2-4);
-    end
-    else
-    begin
-      glScissor(0, 0, gScreenWidth, gScreenHeight);
-    end;
-    // no need to clear stencil buffer, light blitting will do it for us... but only for normal scale
-    if (g_dbg_scale <> 1.0) then glClear(GL_STENCIL_BUFFER_BIT);
-    glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
-    // draw extruded panels
-    glDisable(GL_TEXTURE_2D);
-    glDisable(GL_BLEND);
-    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // no need to modify color buffer
-    if (lrad > 4) then g_Map_DrawPanelShadowVolumes(lx, ly, lrad);
-    // render light texture
-    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // modify color buffer
-    glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); // draw light, and clear stencil buffer
-    // blend it
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    glEnable(GL_TEXTURE_2D);
-    // color and opacity
-    glColor4f(g_dynLights[lln].r, g_dynLights[lln].g, g_dynLights[lln].b, g_dynLights[lln].a);
-    glBindTexture(GL_TEXTURE_2D, g_Texture_Light());
-    glBegin(GL_QUADS);
-      glTexCoord2f(0.0, 0.0); glVertex2i(lx-lrad, ly-lrad); // top-left
-      glTexCoord2f(1.0, 0.0); glVertex2i(lx+lrad, ly-lrad); // top-right
-      glTexCoord2f(1.0, 1.0); glVertex2i(lx+lrad, ly+lrad); // bottom-right
-      glTexCoord2f(0.0, 1.0); glVertex2i(lx-lrad, ly+lrad); // bottom-left
-    glEnd();
-  end;
-
-  // done
-  glDisable(GL_STENCIL_TEST);
-  glDisable(GL_BLEND);
-  glDisable(GL_SCISSOR_TEST);
-  //glScissor(0, 0, sWidth, sHeight);
-
-  glScissor(scxywh[0], scxywh[1], scxywh[2], scxywh[3]);
-  if wassc then glEnable(GL_SCISSOR_TEST) else glDisable(GL_SCISSOR_TEST);
-end;
-
-
-function fixViewportForScale (): Boolean;
-var
-  nx0, ny0, nw, nh: Integer;
-begin
-  result := false;
-  if (g_dbg_scale <> 1.0) then
-  begin
-    result := true;
-    nx0 := round(sX-(gPlayerScreenSize.X-(sWidth*g_dbg_scale))/2/g_dbg_scale);
-    ny0 := round(sY-(gPlayerScreenSize.Y-(sHeight*g_dbg_scale))/2/g_dbg_scale);
-    nw := round(sWidth/g_dbg_scale);
-    nh := round(sHeight/g_dbg_scale);
-    sX := nx0;
-    sY := ny0;
-    sWidth := nw;
-    sHeight := nh;
-  end;
-end;
-
-
-// setup sX, sY, sWidth, sHeight, and transformation matrix before calling this!
-// WARNING! this WILL CALL `glTranslatef()`, but won't restore matrices!
-procedure renderMapInternal (backXOfs, backYOfs: Integer; setTransMatrix: Boolean);
-type
-  TDrawCB = procedure ();
-
-var
-  hasAmbient: Boolean;
-  ambColor: TDFColor;
-  doAmbient: Boolean = false;
-
-  procedure drawPanelType (profname: AnsiString; panType: DWord; doDraw: Boolean);
-  var
-    tagmask: Integer;
-    pan: TPanel;
-  begin
-    if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin(profname);
-    if gdbg_map_use_accel_render then
-    begin
-      tagmask := panelTypeToTag(panType);
-      while (gDrawPanelList.count > 0) do
-      begin
-        pan := TPanel(gDrawPanelList.front());
-        if ((pan.tag and tagmask) = 0) then break;
-        if doDraw then pan.Draw(doAmbient, ambColor);
-        gDrawPanelList.popFront();
-      end;
-    end
-    else
-    begin
-      if doDraw then g_Map_DrawPanels(panType, hasAmbient, ambColor);
-    end;
-    if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
-  end;
-
-  procedure drawOther (profname: AnsiString; cb: TDrawCB);
-  begin
-    if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin(profname);
-    if assigned(cb) then cb();
-    if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
-  end;
-
-begin
-  if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin('total');
-
-  // our accelerated renderer will collect all panels to gDrawPanelList
-  // we can use panel tag to render level parts (see GridTagXXX in g_map.pas)
-  if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin('collect');
-  if gdbg_map_use_accel_render then
-  begin
-    g_Map_CollectDrawPanels(sX, sY, sWidth, sHeight);
-  end;
-  if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
-
-  if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin('skyback');
-  g_Map_DrawBack(backXOfs, backYOfs);
-  if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
-
-  if setTransMatrix then
-  begin
-    //if (g_dbg_scale <> 1.0) then glTranslatef(0.0, -0.375/2, 0);
-    glScalef(g_dbg_scale, g_dbg_scale, 1.0);
-    glTranslatef(-sX, -sY, 0);
-  end;
-
-  // rendering mode
-  ambColor := gCurrentMap['light_ambient'].rgba;
-  hasAmbient := (not ambColor.isOpaque) or (not ambColor.isBlack);
-
-  {
-  if hasAmbient then
-  begin
-    //writeln('color: (', ambColor.r, ',', ambColor.g, ',', ambColor.b, ',', ambColor.a, ')');
-    glColor4ub(ambColor.r, ambColor.g, ambColor.b, ambColor.a);
-    glClear(GL_COLOR_BUFFER_BIT);
-  end;
-  }
-  //writeln('color: (', ambColor.r, ',', ambColor.g, ',', ambColor.b, ',', ambColor.a, ')');
-
-
-  drawPanelType('*back', PANEL_BACK, g_rlayer_back);
-  drawPanelType('*step', PANEL_STEP, g_rlayer_step);
-  drawOther('items', @g_Items_Draw);
-  drawOther('weapons', @g_Weapon_Draw);
-  drawOther('shells', @g_Player_DrawShells);
-  drawOther('drawall', @g_Player_DrawAll);
-  drawOther('corpses', @g_Player_DrawCorpses);
-  drawPanelType('*wall', PANEL_WALL, g_rlayer_wall);
-  drawOther('monsters', @g_Monsters_Draw);
-  drawOther('itemdrop', @g_Items_DrawDrop);
-  drawPanelType('*door', PANEL_CLOSEDOOR, g_rlayer_door);
-  drawOther('gfx', @g_GFX_Draw);
-  drawOther('flags', @g_Map_DrawFlags);
-  drawPanelType('*acid1', PANEL_ACID1, g_rlayer_acid1);
-  drawPanelType('*acid2', PANEL_ACID2, g_rlayer_acid2);
-  drawPanelType('*water', PANEL_WATER, g_rlayer_water);
-  drawOther('dynlights', @renderDynLightsInternal);
-
-  if hasAmbient {and ((not g_playerLight) or (not gwin_has_stencil) or (g_dynLightCount < 1))} then
-  begin
-    renderAmbientQuad(hasAmbient, ambColor);
-  end;
-
-  doAmbient := true;
-  drawPanelType('*fore', PANEL_FORE, g_rlayer_fore);
-
-
-  if g_debug_HealthBar then
-  begin
-    g_Monsters_DrawHealth();
-    g_Player_DrawHealth();
-  end;
-
-  if (profileFrameDraw <> nil) then profileFrameDraw.mainEnd(); // map rendering
-end;
-
-
-procedure DrawMapView(x, y, w, h: Integer);
-
-var
-  bx, by: Integer;
-begin
-  glPushMatrix();
-
-  bx := Round(x/(gMapInfo.Width - w)*(gBackSize.X - w));
-  by := Round(y/(gMapInfo.Height - h)*(gBackSize.Y - h));
-
-  sX := x;
-  sY := y;
-  sWidth := w;
-  sHeight := h;
-
-  fixViewportForScale();
-  renderMapInternal(-bx, -by, true);
-
-  glPopMatrix();
-end;
-
-
-procedure DrawPlayer(p: TPlayer);
-var
-  px, py, a, b, c, d, i, fX, fY: Integer;
-  camObj: TObj;
-  //R: TRect;
-begin
-  if (p = nil) or (p.FDummy) then
-  begin
-    glPushMatrix();
-    g_Map_DrawBack(0, 0);
-    glPopMatrix();
-    Exit;
-  end;
-
-  if (profileFrameDraw = nil) then profileFrameDraw := TProfiler.Create('RENDER', g_profile_history_size);
-  if (profileFrameDraw <> nil) then profileFrameDraw.mainBegin(g_profile_frame_draw);
-
-  gPlayerDrawn := p;
-
-  glPushMatrix();
-
-  camObj := p.getCameraObj();
-  camObj.lerp(gLerpFactor, fX, fY);
-  px := fX + PLAYER_RECT_CX;
-  py := fY + PLAYER_RECT_CY+nlerp(p.SlopeOld, camObj.slopeUpLeft, gLerpFactor);
-
-  if (g_dbg_scale = 1.0) and (not g_dbg_ignore_bounds) then
-  begin
-    if (px > (gPlayerScreenSize.X div 2)) then a := -px+(gPlayerScreenSize.X div 2) else a := 0;
-    if (py > (gPlayerScreenSize.Y div 2)) then b := -py+(gPlayerScreenSize.Y div 2) else b := 0;
-
-    if (px > gMapInfo.Width-(gPlayerScreenSize.X div 2)) then a := -gMapInfo.Width+gPlayerScreenSize.X;
-    if (py > gMapInfo.Height-(gPlayerScreenSize.Y div 2)) then b := -gMapInfo.Height+gPlayerScreenSize.Y;
-
-         if (gMapInfo.Width = gPlayerScreenSize.X) then a := 0
-    else if (gMapInfo.Width < gPlayerScreenSize.X) then
-    begin
-      // hcenter
-      a := (gPlayerScreenSize.X-gMapInfo.Width) div 2;
-    end;
-
-         if (gMapInfo.Height = gPlayerScreenSize.Y) then b := 0
-    else if (gMapInfo.Height < gPlayerScreenSize.Y) then
-    begin
-      // vcenter
-      b := (gPlayerScreenSize.Y-gMapInfo.Height) div 2;
-    end;
-  end
-  else
-  begin
-    // scaled, ignore level bounds
-    a := -px+(gPlayerScreenSize.X div 2);
-    b := -py+(gPlayerScreenSize.Y div 2);
-  end;
-
-  sX := -a;
-  sY := -b;
-  sWidth := gPlayerScreenSize.X;
-  sHeight := gPlayerScreenSize.Y;
-  fixViewportForScale();
-
-  i := py - (sY + sHeight div 2);
-  if (p.IncCam > 0) then
-  begin
-    // clamp to level bounds
-    if (sY - p.IncCam < 0) then
-      p.IncCam := nclamp(sY, 0, 120);
-    // clamp around player position
-    if (i > 0) then
-      p.IncCam := nclamp(p.IncCam, 0, max(0, 120 - i));
-  end
-  else if (p.IncCam < 0) then
-  begin
-    // clamp to level bounds
-    if (sY + sHeight - p.IncCam > gMapInfo.Height) then
-      p.IncCam := nclamp(sY + sHeight - gMapInfo.Height, -120, 0);
-    // clamp around player position
-    if (i < 0) then
-      p.IncCam := nclamp(p.IncCam, min(0, -120 - i), 0);
-  end;
-
-  sY := sY - nlerp(p.IncCamOld, p.IncCam, gLerpFactor);
-
-  if (not g_dbg_ignore_bounds) then
-  begin
-    if (sX+sWidth > gMapInfo.Width) then sX := gMapInfo.Width-sWidth;
-    if (sY+sHeight > gMapInfo.Height) then sY := gMapInfo.Height-sHeight;
-    if (sX < 0) then sX := 0;
-    if (sY < 0) then sY := 0;
-  end;
-
-  if (gBackSize.X <= gPlayerScreenSize.X) or (gMapInfo.Width <= sWidth) then c := 0 else c := trunc((gBackSize.X-gPlayerScreenSize.X)*sX/(gMapInfo.Width-sWidth));
-  if (gBackSize.Y <= gPlayerScreenSize.Y) or (gMapInfo.Height <= sHeight) then d := 0 else d := trunc((gBackSize.Y-gPlayerScreenSize.Y)*sY/(gMapInfo.Height-sHeight));
-
-  //r_smallmap_h: 0: left; 1: center; 2: right
-  //r_smallmap_v: 0: top; 1: center; 2: bottom
-  // horiz small map?
-  if (gMapInfo.Width = sWidth) then
-  begin
-    sX := 0;
-  end
-  else if (gMapInfo.Width < sWidth) then
-  begin
-    case r_smallmap_h of
-      1: sX := -((sWidth-gMapInfo.Width) div 2); // center
-      2: sX := -(sWidth-gMapInfo.Width); // right
-      else sX := 0; // left
-    end;
-  end;
-  // vert small map?
-  if (gMapInfo.Height = sHeight) then
-  begin
-    sY := 0;
-  end
-  else if (gMapInfo.Height < sHeight) then
-  begin
-    case r_smallmap_v of
-      1: sY := -((sHeight-gMapInfo.Height) div 2); // center
-      2: sY := -(sHeight-gMapInfo.Height); // bottom
-      else sY := 0; // top
-    end;
-  end;
-
-  p.viewPortX := sX;
-  p.viewPortY := sY;
-  p.viewPortW := sWidth;
-  p.viewPortH := sHeight;
-
-{$IFDEF ENABLE_HOLMES}
-  if (p = gPlayer1) then
-  begin
-    g_Holmes_plrViewPos(sX, sY);
-    g_Holmes_plrViewSize(sWidth, sHeight);
-  end;
-{$ENDIF}
-
-  renderMapInternal(-c, -d, true);
-
-  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;
-
-  {
-  for a := 0 to High(gCollideMap) do
-    for b := 0 to High(gCollideMap[a]) do
-    begin
-      d := 0;
-      if ByteBool(gCollideMap[a, b] and MARK_WALL) then
-        d := d + 1;
-      if ByteBool(gCollideMap[a, b] and MARK_DOOR) then
-        d := d + 2;
-
-      case d of
-        1: e_DrawPoint(1, b, a, 200, 200, 200);
-        2: e_DrawPoint(1, b, a, 64, 64, 255);
-        3: e_DrawPoint(1, b, a, 255, 0, 255);
-      end;
-    end;
-  }
-
-  glPopMatrix();
-
-  p.DrawPain();
-  p.DrawPickup();
-  p.DrawRulez();
-  if gShowMap then DrawMinimap(p, _TRect(0, 0, 128, 128));
-  if g_Debug_Player then
-    g_Player_DrawDebug(p);
-  p.DrawGUI();
-end;
-
-procedure drawProfilers ();
-var
-  px: Integer = -1;
-  py: Integer = -1;
-begin
-  if g_profile_frame_draw and (profileFrameDraw <> nil) then px := px-drawProfiles(px, py, profileFrameDraw);
-  if g_profile_collision and (profMapCollision <> nil) then begin px := px-drawProfiles(px, py, profMapCollision); py -= calcProfilesHeight(profMonsLOS); end;
-  if g_profile_los and (profMonsLOS <> nil) then begin px := px-drawProfiles(px, py, profMonsLOS); py -= calcProfilesHeight(profMonsLOS); end;
-end;
-
-procedure g_Game_Draw();
-var
-  ID: DWORD;
-  w, h: Word;
-  ww, hh: Byte;
-  Time: Int64;
-  back: string;
-  plView1, plView2: TPlayer;
-  Split: Boolean;
-begin
-  if gExit = EXIT_QUIT then Exit;
-
-  Time := sys_GetTicks() {div 1000};
-  FPSCounter := FPSCounter+1;
-  if Time - FPSTime >= 1000 then
-  begin
-    FPS := FPSCounter;
-    FPSCounter := 0;
-    FPSTime := Time;
-  end;
-
-  e_SetRendertarget(True);
-  e_SetViewPort(0, 0, gScreenWidth, gScreenHeight);
-
-  if gGameOn or (gState = STATE_FOLD) then
-  begin
-    if (gPlayer1 <> nil) and (gPlayer2 <> nil) then
-    begin
-      gSpectMode := SPECT_NONE;
-      if not gRevertPlayers then
-      begin
-        plView1 := gPlayer1;
-        plView2 := gPlayer2;
-      end
-      else
-      begin
-        plView1 := gPlayer2;
-        plView2 := gPlayer1;
-      end;
-    end
-    else
-      if (gPlayer1 <> nil) or (gPlayer2 <> nil) then
-      begin
-        gSpectMode := SPECT_NONE;
-        if gPlayer2 = nil then
-          plView1 := gPlayer1
-        else
-          plView1 := gPlayer2;
-        plView2 := nil;
-      end
-      else
-      begin
-        plView1 := nil;
-        plView2 := nil;
-      end;
-
-    if (plView1 = nil) and (plView2 = nil) and (gSpectMode = SPECT_NONE) then
-      gSpectMode := SPECT_STATS;
-
-    if gSpectMode = SPECT_PLAYERS then
-      if gPlayers <> nil then
-      begin
-        plView1 := GetActivePlayer_ByID(gSpectPID1);
-        if plView1 = nil then
-        begin
-          gSpectPID1 := GetActivePlayerID_Next();
-          plView1 := GetActivePlayer_ByID(gSpectPID1);
-        end;
-        if gSpectViewTwo then
-        begin
-          plView2 := GetActivePlayer_ByID(gSpectPID2);
-          if plView2 = nil then
-          begin
-            gSpectPID2 := GetActivePlayerID_Next();
-            plView2 := GetActivePlayer_ByID(gSpectPID2);
-          end;
-        end;
-      end;
-
-    if gSpectMode = SPECT_MAPVIEW then
-    begin
-    // Ðåæèì ïðîñìîòðà êàðòû
-      Split := False;
-      e_SetViewPort(0, 0, gScreenWidth, gScreenHeight);
-      DrawMapView(gSpectX, gSpectY, gScreenWidth, gScreenHeight);
-      gHearPoint1.Active := True;
-      gHearPoint1.Coords.X := gScreenWidth div 2 + gSpectX;
-      gHearPoint1.Coords.Y := gScreenHeight div 2 + gSpectY;
-      gHearPoint2.Active := False;
-    end
-    else
-    begin
-      Split := (plView1 <> nil) and (plView2 <> nil);
-
-    // Òî÷êè ñëóõà èãðîêîâ
-      if plView1 <> nil then
-      begin
-        gHearPoint1.Active := True;
-        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 + PLAYER_RECT.Width;
-        gHearPoint2.Coords.Y := plView2.GameY + PLAYER_RECT.Height DIV 2;
-      end else
-        gHearPoint2.Active := False;
-
-    // Ðàçìåð ýêðàíîâ èãðîêîâ:
-      gPlayerScreenSize.X := gScreenWidth-196;
-      if Split then
-      begin
-        gPlayerScreenSize.Y := gScreenHeight div 2;
-        if gScreenHeight mod 2 = 0 then
-          Dec(gPlayerScreenSize.Y);
-      end
-      else
-        gPlayerScreenSize.Y := gScreenHeight;
-
-      if Split then
-        if gScreenHeight mod 2 = 0 then
-          e_SetViewPort(0, gPlayerScreenSize.Y+2, gPlayerScreenSize.X+196, gPlayerScreenSize.Y)
-        else
-          e_SetViewPort(0, gPlayerScreenSize.Y+1, gPlayerScreenSize.X+196, gPlayerScreenSize.Y);
-
-      DrawPlayer(plView1);
-      gPlayer1ScreenCoord.X := sX;
-      gPlayer1ScreenCoord.Y := sY;
-
-      if Split then
-      begin
-        e_SetViewPort(0, 0, gPlayerScreenSize.X+196, gPlayerScreenSize.Y);
-
-        DrawPlayer(plView2);
-        gPlayer2ScreenCoord.X := sX;
-        gPlayer2ScreenCoord.Y := sY;
-      end;
-
-      e_SetViewPort(0, 0, gScreenWidth, gScreenHeight);
-
-      if Split then
-        e_DrawLine(2, 0, gScreenHeight div 2, gScreenWidth, gScreenHeight div 2, 0, 0, 0);
-    end;
-
-{$IFDEF ENABLE_HOLMES}
-    // draw inspector
-    if (g_holmes_enabled) then g_Holmes_Draw();
-{$ENDIF}
-
-    if MessageText <> '' then
-    begin
-      w := 0;
-      h := 0;
-      e_CharFont_GetSizeFmt(gMenuFont, MessageText, w, h);
-      if Split then
-        e_CharFont_PrintFmt(gMenuFont, (gScreenWidth div 2)-(w div 2),
-                        (gScreenHeight div 2)-(h div 2), MessageText)
-      else
-        e_CharFont_PrintFmt(gMenuFont, (gScreenWidth div 2)-(w div 2),
-                  Round(gScreenHeight / 2.75)-(h div 2), MessageText);
-    end;
-
-    if IsDrawStat or (gSpectMode = SPECT_STATS) then
-      DrawStat();
-
-    if gSpectHUD and (not gChatShow) and (gSpectMode <> SPECT_NONE) and (not gSpectAuto) then
-    begin
-    // Draw spectator GUI
-      ww := 0;
-      hh := 0;
-      e_TextureFontGetSize(gStdFont, ww, hh);
-      case gSpectMode of
-        SPECT_STATS:
-          e_TextureFontPrintEx(0, gScreenHeight - (hh+2)*2, 'MODE: Stats', gStdFont, 255, 255, 255, 1);
-        SPECT_MAPVIEW:
-          e_TextureFontPrintEx(0, gScreenHeight - (hh+2)*2, 'MODE: Observe Map', gStdFont, 255, 255, 255, 1);
-        SPECT_PLAYERS:
-          e_TextureFontPrintEx(0, gScreenHeight - (hh+2)*2, 'MODE: Watch Players', gStdFont, 255, 255, 255, 1);
-      end;
-      e_TextureFontPrintEx(2*ww, gScreenHeight - (hh+2), '< jump >', gStdFont, 255, 255, 255, 1);
-      if gSpectMode = SPECT_STATS then
-      begin
-        e_TextureFontPrintEx(16*ww, gScreenHeight - (hh+2)*2, 'Autoview', gStdFont, 255, 255, 255, 1);
-        e_TextureFontPrintEx(16*ww, gScreenHeight - (hh+2), '< fire >', gStdFont, 255, 255, 255, 1);
-      end;
-      if gSpectMode = SPECT_MAPVIEW then
-      begin
-        e_TextureFontPrintEx(22*ww, gScreenHeight - (hh+2)*2, '[-]', gStdFont, 255, 255, 255, 1);
-        e_TextureFontPrintEx(26*ww, gScreenHeight - (hh+2)*2, 'Step ' + IntToStr(gSpectStep), gStdFont, 255, 255, 255, 1);
-        e_TextureFontPrintEx(34*ww, gScreenHeight - (hh+2)*2, '[+]', gStdFont, 255, 255, 255, 1);
-        e_TextureFontPrintEx(18*ww, gScreenHeight - (hh+2), '<prev weap>', gStdFont, 255, 255, 255, 1);
-        e_TextureFontPrintEx(30*ww, gScreenHeight - (hh+2), '<next weap>', gStdFont, 255, 255, 255, 1);
-      end;
-      if gSpectMode = SPECT_PLAYERS then
-      begin
-        e_TextureFontPrintEx(22*ww, gScreenHeight - (hh+2)*2, 'Player 1', gStdFont, 255, 255, 255, 1);
-        e_TextureFontPrintEx(20*ww, gScreenHeight - (hh+2), '<left/right>', gStdFont, 255, 255, 255, 1);
-        if gSpectViewTwo then
-        begin
-          e_TextureFontPrintEx(37*ww, gScreenHeight - (hh+2)*2, 'Player 2', gStdFont, 255, 255, 255, 1);
-          e_TextureFontPrintEx(34*ww, gScreenHeight - (hh+2), '<prev w/next w>', gStdFont, 255, 255, 255, 1);
-          e_TextureFontPrintEx(52*ww, gScreenHeight - (hh+2)*2, '2x View', gStdFont, 255, 255, 255, 1);
-          e_TextureFontPrintEx(51*ww, gScreenHeight - (hh+2), '<up/down>', gStdFont, 255, 255, 255, 1);
-        end
-        else
-        begin
-          e_TextureFontPrintEx(35*ww, gScreenHeight - (hh+2)*2, '2x View', gStdFont, 255, 255, 255, 1);
-          e_TextureFontPrintEx(34*ww, gScreenHeight - (hh+2), '<up/down>', gStdFont, 255, 255, 255, 1);
-        end;
-      end;
-    end;
-  end;
-
-  if gPauseMain and gGameOn and (g_ActiveWindow = nil) then
-  begin
-    //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
-    e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
-
-    e_CharFont_GetSize(gMenuFont, _lc[I_MENU_PAUSE], w, h);
-    e_CharFont_Print(gMenuFont, (gScreenWidth div 2)-(w div 2),
-                    (gScreenHeight div 2)-(h div 2), _lc[I_MENU_PAUSE]);
-  end;
-
-  if not gGameOn then
-  begin
-    if (gState = STATE_MENU) then
-    begin
-      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
-      begin
-        //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
-        e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
-      end
-      else
-      begin
-        // F3 at titlepic will show game loading dialog
-        if e_KeyPressed(IK_F3) then
-        begin
-          g_Menu_Show_LoadMenu(true);
-          if (g_ActiveWindow <> nil) then e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
-        end;
-      end;
-    end;
-
-    if gState = STATE_FOLD then
-    begin
-      e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 0, 0, 0, EndingGameCounter);
-    end;
-
-    if gState = STATE_INTERCUSTOM then
-    begin
-      if gLastMap and (gGameSettings.GameMode = GM_COOP) then
-      begin
-        back := 'TEXTURE_endpic';
-        if not g_Texture_Get(back, ID) then
-          back := _lc[I_TEXTURE_ENDPIC];
-      end
-      else
-        back := 'INTER';
-
-      DrawMenuBackground(back);
-
-      DrawCustomStat();
-
-      if g_ActiveWindow <> nil then
-      begin
-        //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
-        e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
-      end;
-    end;
-
-    if gState = STATE_INTERSINGLE then
-    begin
-      if EndingGameCounter > 0 then
-      begin
-        e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 0, 0, 0, EndingGameCounter);
-      end
-      else
-      begin
-        back := 'INTER';
-
-        DrawMenuBackground(back);
-
-        DrawSingleStat();
-
-        if g_ActiveWindow <> nil then
-        begin
-          //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
-          e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
-        end;
-      end;
-    end;
-
-    if gState = STATE_ENDPIC then
-    begin
-      ID := DWORD(-1);
-      if g_Texture_Get('TEXTURE_endpic', ID) then DrawMenuBackground('TEXTURE_endpic')
-      else DrawMenuBackground(_lc[I_TEXTURE_ENDPIC]);
-
-      if g_ActiveWindow <> nil then
-      begin
-        //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
-        e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
-      end;
-    end;
-
-    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);
-//      end;
-      DrawMenuBackground('MENU_BACKGROUND');
-      e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
-      g_Serverlist_Draw(slCurrent, slTable);
-    end;
-  end;
-
-  if g_ActiveWindow <> nil then
-  begin
-    if gGameOn then
-    begin
-      //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
-      e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
-    end;
-    g_ActiveWindow.Draw();
-  end;
-
-{$IFNDEF HEADLESS}
-  r_Console_Draw();
-{$ENDIF}
-
-  if g_debug_Sounds and gGameOn then
-  begin
-    for w := 0 to High(e_SoundsArray) do
-      for h := 0 to e_SoundsArray[w].nRefs do
-        e_DrawPoint(1, w+100, h+100, 255, 0, 0);
-  end;
-
-  if gShowFPS then
-  begin
-    e_TextureFontPrint(0, 0, Format('FPS: %d', [FPS]), gStdFont);
-    e_TextureFontPrint(0, 16, Format('UPS: %d', [UPS]), gStdFont);
-  end;
-
-  if gGameOn and gShowTime then
-    drawTime(gScreenWidth-72, gScreenHeight-16);
-
-  if gGameOn then drawProfilers();
-
-  // TODO: draw this after the FBO and remap mouse click coordinates
-
-{$IFDEF ENABLE_HOLMES}
-  g_Holmes_DrawUI();
-{$ENDIF}
-
-  // blit framebuffer to screen
-
-  e_SetRendertarget(False);
-  e_SetViewPort(0, 0, gWinSizeX, gWinSizeY);
-  e_BlitFramebuffer(gWinSizeX, gWinSizeY);
-
-  // draw the overlay stuff on top of it
-
-  g_Touch_Draw;
-end;
-
-procedure g_Game_Quit();
-begin
-  g_Game_StopAllSounds(True);
-  gMusic.Free();
-  g_Game_FreeData();
-  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;
-
-// Íàäî óäàëèòü êàðòó ïîñëå òåñòà:
-  if gMapToDelete <> '' then
-    g_Game_DeleteTestMap();
-
-  gExit := EXIT_QUIT;
-  sys_RequestQuit;
-end;
-
 procedure g_FatalError(Text: String);
 begin
   g_Console_Add(Format(_lc[I_FATAL_ERROR], [Text]), True);
@@ -4300,14 +2462,14 @@ var
   rf: Single;
   bw, bh: Word;
 begin
-// Ðàçìåð ýêðàíîâ èãðîêîâ:
+// Ð Ð°Ð·Ð¼ÐµÑ\80 Ñ\8dкÑ\80анов Ð¸Ð³Ñ\80оков:
   gPlayerScreenSize.X := gScreenWidth-196;
   if (gPlayer1 <> nil) and (gPlayer2 <> nil) then
     gPlayerScreenSize.Y := gScreenHeight div 2
   else
     gPlayerScreenSize.Y := gScreenHeight;
 
-// Ðàçìåð çàäíåãî ïëàíà:
+// Ð Ð°Ð·Ð¼ÐµÑ\80 Ð·Ð°Ð´Ð½ÐµÐ³Ð¾ Ð¿Ð»Ð°Ð½Ð°:
   if BackID <> DWORD(-1) then
   begin
     s := SKY_STRETCH;
@@ -4357,7 +2519,7 @@ begin
     if not (Team in [TEAM_RED, TEAM_BLUE]) then
       Team := gPlayer1Settings.Team;
 
-    // Ñîçäàíèå ïåðâîãî èãðîêà:
+    // Создание первого игрока:
     gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                              gPlayer1Settings.Color,
                                              Team, False));
@@ -4391,7 +2553,7 @@ begin
     if not (Team in [TEAM_RED, TEAM_BLUE]) then
       Team := gPlayer2Settings.Team;
 
-    // Ñîçäàíèå âòîðîãî èãðîêà:
+    // Создание второго игрока:
     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
                                              gPlayer2Settings.Color,
                                              Team, False));
@@ -4485,7 +2647,7 @@ begin
 
   g_Game_ClearLoading();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   FillByte(gGameSettings, SizeOf(TGameSettings), 0);
   gAimLine := False;
   gShowMap := False;
@@ -4505,10 +2667,10 @@ begin
 
   g_Game_ExecuteEvent('ongamestart');
 
-// Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
+// Установка размеров окон игроков:
   g_Game_SetupScreenSize();
 
-// Ñîçäàíèå ïåðâîãî èãðîêà:
+// Создание первого игрока:
   gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                            gPlayer1Settings.Color,
                                            gPlayer1Settings.Team, False));
@@ -4525,7 +2687,7 @@ begin
   gPlayer1.SkipFist := gPlayer1Settings.SkipFist;
   nPl := 1;
 
-// Ñîçäàíèå âòîðîãî èãðîêà, åñëè åñòü:
+// Создание второго игрока, если есть:
   if TwoPlayers then
   begin
     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
@@ -4545,7 +2707,7 @@ begin
     Inc(nPl);
   end;
 
-// Çàãðóçêà è çàïóñê êàðòû:
+// Загрузка и запуск карты:
   if not g_Game_StartMap(false{asMegawad}, MAP, True) then
   begin
     if (Pos(':\', Map) > 0) or (Pos(':/', Map) > 0) then tmps := Map else tmps := gGameSettings.WAD + ':\' + MAP;
@@ -4553,10 +2715,10 @@ begin
     Exit;
   end;
 
-// Íàñòðîéêè èãðîêîâ è áîòîâ:
+// Настройки игроков и ботов:
   g_Player_Init();
 
-// Ñîçäàåì áîòîâ:
+// Создаем ботов:
   for i := nPl+1 to nPlayers do
     g_Player_Create(STD_PLAYER_MODEL, _RGB(0, 0, 0), 0, True);
 end;
@@ -4574,7 +2736,7 @@ begin
 
   g_Game_ClearLoading();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   gGameSettings.GameType := GT_CUSTOM;
   gGameSettings.GameMode := GameMode;
   gSwitchGameMode := GameMode;
@@ -4597,10 +2759,10 @@ begin
 
   g_Game_ExecuteEvent('ongamestart');
 
-// Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
+// Установка размеров окон игроков:
   g_Game_SetupScreenSize();
 
-// Ðåæèì íàáëþäàòåëÿ:
+// Ð ÐµÐ¶Ð¸Ð¼ Ð½Ð°Ð±Ð»Ñ\8eдаÑ\82елÑ\8f:
   if nPlayers = 0 then
   begin
     gPlayer1 := nil;
@@ -4610,7 +2772,7 @@ begin
   nPl := 0;
   if nPlayers >= 1 then
   begin
-  // Ñîçäàíèå ïåðâîãî èãðîêà:
+  // Создание первого игрока:
     gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                              gPlayer1Settings.Color,
                                              gPlayer1Settings.Team, False));
@@ -4630,7 +2792,7 @@ begin
 
   if nPlayers >= 2 then
   begin
-  // Ñîçäàíèå âòîðîãî èãðîêà:
+  // Создание второго игрока:
     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
                                              gPlayer2Settings.Color,
                                              gPlayer2Settings.Team, False));
@@ -4648,14 +2810,14 @@ begin
     Inc(nPl);
   end;
 
-// Çàãðóçêà è çàïóñê êàðòû:
+// Загрузка и запуск карты:
   if not g_Game_StartMap(true{asMegawad}, Map, True) then
   begin
     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [Map]));
     Exit;
   end;
 
-// Íåò òî÷åê ïîÿâëåíèÿ:
+// Нет точек появления:
   if (g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) +
       g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) +
       g_Map_GetPointCount(RESPAWNPOINT_DM) +
@@ -4666,10 +2828,10 @@ begin
     Exit;
   end;
 
-// Íàñòðîéêè èãðîêîâ è áîòîâ:
+// Настройки игроков и ботов:
   g_Player_Init();
 
-// Ñîçäàåì áîòîâ:
+// Создаем ботов:
   for i := nPl+1 to nPlayers do
     g_Player_Create(STD_PLAYER_MODEL, _RGB(0, 0, 0), 0, True);
 end;
@@ -4688,7 +2850,7 @@ begin
 
   ClearDebugCvars();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   gGameSettings.GameType := GT_SERVER;
   gGameSettings.GameMode := GameMode;
   gSwitchGameMode := GameMode;
@@ -4711,10 +2873,10 @@ begin
 
   g_Game_ExecuteEvent('ongamestart');
 
-// Óñòàíîâêà ðàçìåðîâ îêíà èãðîêà
+// Установка размеров окна игрока
   g_Game_SetupScreenSize();
 
-// Ðåæèì íàáëþäàòåëÿ:
+// Ð ÐµÐ¶Ð¸Ð¼ Ð½Ð°Ð±Ð»Ñ\8eдаÑ\82елÑ\8f:
   if nPlayers = 0 then
   begin
     gPlayer1 := nil;
@@ -4723,7 +2885,7 @@ begin
 
   if nPlayers >= 1 then
   begin
-  // Ñîçäàíèå ïåðâîãî èãðîêà:
+  // Создание первого игрока:
     gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                              gPlayer1Settings.Color,
                                              gPlayer1Settings.Team, False));
@@ -4742,7 +2904,7 @@ begin
 
   if nPlayers >= 2 then
   begin
-  // Ñîçäàíèå âòîðîãî èãðîêà:
+  // Создание второго игрока:
     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
                                              gPlayer2Settings.Color,
                                              gPlayer2Settings.Team, False));
@@ -4763,7 +2925,7 @@ begin
   if NetForwardPorts then
     g_Game_SetLoadingText(_lc[I_LOAD_PORTS], 0, False);
 
-// Ñòàðòóåì ñåðâåð
+// Стартуем сервер
   if not g_Net_Host(IPAddr, Port, NetMaxClients) then
   begin
     g_FatalError(_lc[I_NET_MSG] + Format(_lc[I_NET_ERR_HOST], [Port]));
@@ -4774,7 +2936,7 @@ begin
 
   g_Net_Slist_ServerStarted();
 
-// Çàãðóçêà è çàïóñê êàðòû:
+// Загрузка и запуск карты:
   if not g_Game_StartMap(false{asMegawad}, Map, True) then
   begin
     g_Net_Slist_ServerClosed();
@@ -4782,7 +2944,7 @@ begin
     Exit;
   end;
 
-// Íåò òî÷åê ïîÿâëåíèÿ:
+// Нет точек появления:
   if (g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) +
       g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) +
       g_Map_GetPointCount(RESPAWNPOINT_DM) +
@@ -4794,7 +2956,7 @@ begin
     Exit;
   end;
 
-// Íàñòðîéêè èãðîêîâ è áîòîâ:
+// Настройки игроков и ботов:
   g_Player_Init();
 
   g_Net_Slist_ServerMapStarted();
@@ -4823,7 +2985,7 @@ begin
 
   ClearDebugCvars();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   gGameSettings.GameType := GT_CLIENT;
 
   gCoopTotalMonstersKilled := 0;
@@ -4835,7 +2997,7 @@ begin
 
   g_Game_ExecuteEvent('ongamestart');
 
-// Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
+// Установка размеров окон игроков:
   g_Game_SetupScreenSize();
 
   NetState := NET_STATE_AUTH;
@@ -4850,7 +3012,7 @@ begin
   gSpectLatchPID1 := 0;
   gSpectLatchPID2 := 0;
 
-// Ñòàðòóåì êëèåíò
+// Стартуем клиент
   if not g_Net_Connect(Addr, Port) then
   begin
     g_FatalError(_lc[I_NET_MSG] + _lc[I_NET_ERR_CONN]);
@@ -5002,7 +3164,7 @@ begin
   g_Game_ClearLoading();
 
   Force := gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF];
-  // Åñëè óðîâåíü çàâåðøèëñÿ ïî òðèããåðó Âûõîä, íå î÷èùàòü èíâåíòàðü
+  // Если уровень завершился по триггеру Выход, не очищать инвентарь
   if gExitByTrigger then
   begin
     Force := False;
@@ -5119,7 +3281,7 @@ begin
       begin
         g_Map_ResetFlag(FLAG_RED);
         g_Map_ResetFlag(FLAG_BLUE);
-        // CTF, à ôëàãîâ íåò:
+        // CTF, а флагов нет:
         if not g_Map_HaveFlagPoints() then
           g_SimpleError(_lc[I_GAME_ERROR_CTF]);
       end;
@@ -5181,7 +3343,7 @@ begin
 
     MH_SEND_GameEvent(NET_EV_MAPSTART, gGameSettings.GameMode, Map);
 
-  // Ìàñòåðñåðâåð
+  // Мастерсервер
     g_Net_Slist_ServerMapStarted();
 
     if NetClients <> nil then
@@ -5234,10 +3396,10 @@ begin
   gCoopTotalMonsters := gCoopTotalMonsters + gTotalMonsters;
   gCoopTotalSecrets := gCoopTotalSecrets + gSecretsCount;
 
-// Âûøëè â âûõîä â Îäèíî÷íîé èãðå:
+// Вышли в выход в Одиночной игре:
   if gGameSettings.GameType = GT_SINGLE then
     gExit := EXIT_ENDLEVELSINGLE
-  else // Âûøëè â âûõîä â Ñâîåé èãðå
+  else // Вышли в выход в Своей игре
   begin
     gExit := EXIT_ENDLEVELCUSTOM;
     if gGameSettings.GameMode = GM_COOP then
@@ -5482,7 +3644,7 @@ begin
   if (a = 0) then a := Pos('.wad:/', toLowerCase1251(gMapToDelete));
   if (a = 0) then exit;
 
-  // Âûäåëÿåì èìÿ wad-ôàéëà è èìÿ êàðòû
+  // Выделяем имя wad-файла и имя карты
   WadName := Copy(gMapToDelete, 1, a+3);
   Delete(gMapToDelete, 1, a+5);
   gMapToDelete := UpperCase(gMapToDelete);
@@ -5490,7 +3652,7 @@ begin
   //CopyMemory(@MapName[0], @gMapToDelete[1], Min(16, Length(gMapToDelete)));
 
 {
-// Èìÿ êàðòû íå ñòàíäàðòíîå òåñòîâîå:
+// Имя карты не стандартное тестовое:
   if MapName <> TEST_MAP_NAME then
     Exit;
 
@@ -5499,14 +3661,14 @@ begin
     time := g_GetFileTime(WadName);
     WAD := TWADFile.Create();
 
-  // ×èòàåì Wad-ôàéë:
+  // Читаем Wad-файл:
     if not WAD.ReadFile(WadName) then
-    begin // Íåò òàêîãî WAD-ôàéëà
+    begin // Нет такого WAD-файла
       WAD.Free();
       Exit;
     end;
 
-  // Ñîñòàâëÿåì ñïèñîê êàðò è èùåì íóæíóþ:
+  // Составляем список карт и ищем нужную:
     WAD.CreateImage();
     MapList := WAD.GetResourcesList('');
 
@@ -5514,7 +3676,7 @@ begin
       for a := 0 to High(MapList) do
         if MapList[a] = MapName then
         begin
-        // Óäàëÿåì è ñîõðàíÿåì:
+        // Удаляем и сохраняем:
           WAD.RemoveResource('', MapName);
           WAD.SaveTo(WadName);
           Break;
@@ -6213,7 +4375,7 @@ var
   //pt: TDFPoint;
   mon: TMonster;
 begin
-// Êîìàíäû îòëàäî÷íîãî ðåæèìà:
+// Команды отладочного режима:
   if {gDebugMode}conIsCheatsEnabled then
   begin
     cmd := LowerCase(P[0]);
@@ -6562,7 +4724,7 @@ var
   listen: LongWord;
   found: Boolean;
 begin
-// Îáùèå êîìàíäû:
+// Общие команды:
   cmd := LowerCase(P[0]);
   chstr := '';
   if cmd = 'pause' then
@@ -6614,7 +4776,7 @@ begin
           if gPlayers[a] <> nil then
             if Copy(LowerCase(gPlayers[a].Name), 1, Length(P[1])) = LowerCase(P[1]) then
             begin
-              // Íå îòêëþ÷àòü îñíîâíûõ èãðîêîâ â ñèíãëå
+              // Не отключать основных игроков в сингле
               if not(gPlayers[a] is TBot) and (gGameSettings.GameType = GT_SINGLE) then
                 continue;
               gPlayers[a].Lives := 0;
@@ -6622,7 +4784,7 @@ begin
               g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [gPlayers[a].Name]), True);
               g_Player_Remove(gPlayers[a].UID);
               g_Net_Slist_ServerPlayerLeaves();
-              // Åñëè íå ïåðåìåøàòü, ïðè äîáàâëåíèè íîâûõ áîòîâ ïîÿâÿòñÿ ñòàðûå
+              // Если не перемешать, при добавлении новых ботов появятся старые
               g_Bot_MixNames();
             end;
     end else
@@ -7595,7 +5757,7 @@ begin
     else
       MC_SEND_CheatRequest(NET_CHEAT_DROPFLAG);
   end
-// Êîìàíäû Ñâîåé èãðû:
+// Команды Своей игры:
   else if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
   begin
     if cmd = 'bot_addred' then
@@ -7699,7 +5861,7 @@ begin
     begin
       if (Length(P) = 1) or (StrToIntDef(P[1], -1) <= 0) then
         Exit;
-      // Äîïîëíèòåëüíîå âðåìÿ:
+      // Дополнительное время:
       gGameSettings.TimeLimit := (gTime - gGameStartTime) div 1000 + Word(StrToIntDef(P[1], 0));
 
       g_Console_Add(Format(_lc[I_MSG_TIME_LIMIT],
@@ -7915,14 +6077,14 @@ begin
       end;
       g_Sound_PlayEx('MENU_OPEN');
 
-    // Ïàóçà ïðè ìåíþ òîëüêî â îäèíî÷íîé èãðå:
+    // Пауза при меню только в одиночной игре:
       if (not g_Game_IsNet) then
         g_Game_Pause(True);
     end
   else
     if (g_ActiveWindow <> nil) and (not Show) then
     begin
-    // Ïàóçà ïðè ìåíþ òîëüêî â îäèíî÷íîé èãðå:
+    // Пауза при меню только в одиночной игре:
       if (not g_Game_IsNet) then
         g_Game_Pause(False);
     end;
@@ -7959,7 +6121,7 @@ procedure g_Game_PauseAllSounds(Enable: Boolean);
 var
   i: Integer;
 begin
-// Òðèããåðû:
+// Триггеры:
   if gTriggers <> nil then
     for i := 0 to High(gTriggers) do
       with gTriggers[i] do
@@ -7970,13 +6132,13 @@ begin
           Sound.Pause(Enable);
         end;
 
-// Çâóêè èãðîêîâ:
+// Звуки игроков:
   if gPlayers <> nil then
     for i := 0 to High(gPlayers) do
       if gPlayers[i] <> nil then
         gPlayers[i].PauseSounds(Enable);
 
-// Ìóçûêà:
+// Музыка:
   if gMusic <> nil then
     gMusic.Pause(Enable);
 end;
@@ -8343,7 +6505,7 @@ end;
 procedure g_Game_SetDebugMode();
 begin
   gDebugMode := True;
-// ×èòû (äàæå â ñâîåé èãðå):
+// Читы (даже в своей игре):
   gCheats := True;
 end;
 
@@ -8357,7 +6519,7 @@ begin
   with LoadingStat do
   begin
     if not reWrite then
-    begin // Ïåðåõîäèì íà ñëåäóþùóþ ñòðîêó èëè ñêðîëëèðóåì:
+    begin // Переходим на следующую строку или скроллируем:
       if NextMsg = Length(Msgs) then
         begin // scroll
           for i := 0 to High(Msgs)-1 do
@@ -8432,7 +6594,7 @@ begin
     if (Length(s) > 1) and (s[1] = '-') then
     begin
       if (Length(s) > 2) and (s[2] = '-') then
-        begin // Îäèíî÷íûé ïàðàìåòð
+        begin // Одиночный параметр
           SetLength(pars, Length(pars) + 1);
           with pars[High(pars)] do
           begin
@@ -8442,7 +6604,7 @@ begin
         end
       else
         if (i < ParamCount) then
-        begin // Ïàðàìåòð ñî çíà÷åíèåì
+        begin // Параметр со значением
           Inc(i);
           SetLength(pars, Length(pars) + 1);
           with pars[High(pars)] do
index ad97a79bb5c9182ebe9132632f113f606acb0cd1..4b227e0f020962f3385f21e2a281d57cd04dc534 100644 (file)
@@ -71,7 +71,7 @@ uses
   e_sound, g_options, g_sound, g_player, g_basic,
   g_weapons, SysUtils, g_triggers, MAPDEF, g_map, e_res,
   g_menu, g_language, g_net, g_touch, g_system, g_res_downloader,
-  conbuf, envvars,
+  conbuf, envvars, r_game,
   xparser;
 
 
@@ -689,7 +689,7 @@ end;
 
 procedure Draw ();
 begin
-  g_Game_Draw();
+  r_Game_Draw();
 end;
 
 
index f021de31011bce9e4b0ad6e4a79123d234e89a4b..3bc2d4c0965c589852206bfac6bfa1a802df3a2e 100644 (file)
@@ -50,7 +50,7 @@ uses
   MAPDEF, Math, g_saveload,
   e_texture, g_language, e_res,
   g_net, g_netmsg, g_netmaster, g_items, e_input, g_touch,
-  utils, wadreader, g_system;
+  utils, wadreader, g_system, r_game;
 
 
 type TYNCallback = procedure (yes:Boolean);
@@ -861,7 +861,7 @@ begin
 
   slWaitStr := _lc[I_NET_SLIST_WAIT];
 
-  g_Game_Draw;
+  r_Game_Draw;
   sys_Repaint;
 
   slReturnPressed := True;
index ef68c00dcd59d745fcafb772c7f9240d4405bfeb..e6e7a6c5eed31414263719c61717a372ccf56d2f 100644 (file)
@@ -173,7 +173,7 @@ implementation
 
 uses
   e_input, e_graphics, e_log, g_window, g_net, g_console,
-  g_map, g_game, g_sound, g_gui, g_menu, g_options, g_language, g_basic,
+  g_map, g_game, g_sound, g_gui, g_menu, g_options, g_language, g_basic, r_game,
   wadreader, g_system, utils, hashtable;
 
 
@@ -1925,7 +1925,7 @@ begin
     begin
       slWaitStr := _lc[I_NET_SLIST_WAIT];
 
-      g_Game_Draw;
+      r_Game_Draw;
       sys_Repaint;
 
       if g_Net_Slist_Fetch(SL) then
index 34bd7dc8142affcb5f010881f9ece8f6b6a0dddd..720be1b85ce934a2ec0e834939402d0ca2f4c743 100644 (file)
@@ -40,7 +40,7 @@ uses
 {$INCLUDE ../nogl/noGLuses.inc}
   SysUtils, Classes, MAPDEF, Math,
   e_graphics, e_log, e_texture, g_main,
-  g_console, r_console, e_input, g_options, g_game,
+  g_console, r_console, e_input, g_options, g_game, r_game,
   g_basic, g_textures, e_sound, g_sound, g_menu, ENet, g_net,
   g_map, g_gfx, g_monsters, xprofiler,
   g_touch, g_gui, g_system, g_netmaster;
index 194efe18427f70edc2679efdb2f4a85f44f7ece3..78eccc9c6eb6c55dc75d69719e33df4d5b3ca345 100644 (file)
@@ -12,7 +12,7 @@
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *)
-{$INCLUDE ../shared/a_modes.inc}
+{$INCLUDE ../../shared/a_modes.inc}
 unit r_console;
 
 interface
diff --git a/src/game/opengl/r_game.pas b/src/game/opengl/r_game.pas
new file mode 100644 (file)
index 0000000..5635ee4
--- /dev/null
@@ -0,0 +1,1862 @@
+(* Copyright (C)  Doom 2D: Forever Developers
+ *
+ * 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, 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *)
+{$INCLUDE ../../shared/a_modes.inc}
+unit r_game;
+
+interface
+
+  procedure r_Game_Draw;
+  procedure DrawLoadingStat;
+  procedure DrawMenuBackground (tex: AnsiString);
+
+implementation
+
+  uses
+    {$INCLUDE ../nogl/noGLuses.inc}
+    SysUtils, Classes, Math,
+    e_graphics,
+    g_system, g_touch,
+    MAPDEF, xprofiler, utils, wadreader,
+    g_textures, e_input, e_sound,
+    g_language, g_console, g_menu, g_triggers, g_player, g_options, g_monsters, g_map, g_panel, g_window,
+    g_items, g_weapons, g_gfx, g_phys, g_net, g_gui, g_netmaster,
+    g_game, r_console
+  ;
+
+  var
+    profileFrameDraw: TProfiler = nil;
+
+    FPS: Word;
+    FPSCounter: Word;
+    FPSTime: LongWord;
+
+function GetActivePlayer_ByID(ID: Integer): TPlayer;
+var
+  a: Integer;
+begin
+  Result := nil;
+  if ID < 0 then
+    Exit;
+  if gPlayers = nil then
+    Exit;
+  for a := Low(gPlayers) to High(gPlayers) do
+    if IsActivePlayer(gPlayers[a]) then
+    begin
+      if gPlayers[a].UID <> ID then
+        continue;
+      Result := gPlayers[a];
+      break;
+    end;
+end;
+
+function calcProfilesHeight (prof: TProfiler): Integer;
+begin
+  result := 0;
+  if (prof = nil) then exit;
+  if (length(prof.bars) = 0) then exit;
+  result := length(prof.bars)*(16+2);
+end;
+
+// returns width
+function drawProfiles (x, y: Integer; prof: TProfiler): Integer;
+var
+  wdt, hgt: Integer;
+  yy: Integer;
+  ii: Integer;
+begin
+  result := 0;
+  if (prof = nil) then exit;
+  // gScreenWidth
+  if (length(prof.bars) = 0) then exit;
+  wdt := 192;
+  hgt := calcProfilesHeight(prof);
+  if (x < 0) then x := gScreenWidth-(wdt-1)+x;
+  if (y < 0) then y := gScreenHeight-(hgt-1)+y;
+  // background
+  //e_DrawFillQuad(x, y, x+wdt-1, y+hgt-1, 255, 255, 255, 200, B_BLEND);
+  //e_DrawFillQuad(x, y, x+wdt-1, y+hgt-1, 20, 20, 20, 0, B_NONE);
+  e_DarkenQuadWH(x, y, wdt, hgt, 150);
+  // title
+  yy := y+2;
+  for ii := 0 to High(prof.bars) do
+  begin
+    e_TextureFontPrintEx(x+2+4*prof.bars[ii].level, yy, Format('%s: %d', [prof.bars[ii].name, prof.bars[ii].value]), gStdFont, 255, 255, 0, 1, false);
+    Inc(yy, 16+2);
+  end;
+  result := wdt;
+end;
+
+procedure drawTime(X, Y: Integer); inline;
+begin
+  e_TextureFontPrint(x, y, Format('%d:%.2d:%.2d', [gTime div 1000 div 3600, (gTime div 1000 div 60) mod 60, gTime div 1000 mod 60]), gStdFont);
+end;
+
+procedure DrawStat();
+var
+  pc, x, y, w, h: Integer;
+  w1, w2, w3, w4: Integer;
+  a, aa: Integer;
+  cw, ch, r, g, b, rr, gg, bb: Byte;
+  s1, s2, s3: String;
+  _y: Integer;
+  stat: TPlayerStatArray;
+  wad, map: string;
+  mapstr: string;
+  namestr: string;
+begin
+  s1 := '';
+  s2 := '';
+  s3 := '';
+  pc := g_Player_GetCount;
+  e_TextureFontGetSize(gStdFont, cw, ch);
+
+  w := gScreenWidth-(gScreenWidth div 5);
+  if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
+    h := 32+ch*(11+pc)
+  else
+    h := 40+ch*5+(ch+8)*pc;
+  x := (gScreenWidth div 2)-(w div 2);
+  y := (gScreenHeight div 2)-(h div 2);
+
+  e_DrawFillQuad(x, y, x+w-1, y+h-1, 64, 64, 64, 32);
+  e_DrawQuad(x, y, x+w-1, y+h-1, 255, 127, 0);
+
+  drawTime(x+w-78, y+8);
+
+  wad := g_ExtractWadNameNoPath(gMapInfo.Map);
+  map := g_ExtractFileName(gMapInfo.Map);
+  mapstr := wad + ':\' + map + ' - ' + gMapInfo.Name;
+
+  case gGameSettings.GameMode of
+    GM_DM:
+    begin
+      if gGameSettings.MaxLives = 0 then
+        s1 := _lc[I_GAME_DM]
+      else
+        s1 := _lc[I_GAME_LMS];
+      s2 := Format(_lc[I_GAME_FRAG_LIMIT], [gGameSettings.ScoreLimit]);
+      s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
+    end;
+
+    GM_TDM:
+    begin
+      if gGameSettings.MaxLives = 0 then
+        s1 := _lc[I_GAME_TDM]
+      else
+        s1 := _lc[I_GAME_TLMS];
+      s2 := Format(_lc[I_GAME_FRAG_LIMIT], [gGameSettings.ScoreLimit]);
+      s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
+    end;
+
+    GM_CTF:
+    begin
+      s1 := _lc[I_GAME_CTF];
+      s2 := Format(_lc[I_GAME_SCORE_LIMIT], [gGameSettings.ScoreLimit]);
+      s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
+    end;
+
+    GM_COOP:
+    begin
+      if gGameSettings.MaxLives = 0 then
+        s1 := _lc[I_GAME_COOP]
+      else
+        s1 := _lc[I_GAME_SURV];
+      s2 := _lc[I_GAME_MONSTERS] + ' ' + IntToStr(gCoopMonstersKilled) + '/' + IntToStr(gTotalMonsters);
+      s3 := _lc[I_GAME_SECRETS] + ' ' + IntToStr(gCoopSecretsFound) + '/' + IntToStr(gSecretsCount);
+    end;
+
+    else
+    begin
+      s1 := '';
+      s2 := '';
+    end;
+  end;
+  _y := y+8;
+  e_TextureFontPrintEx(x+(w div 2)-(Length(s1)*cw div 2), _y, s1, gStdFont, 255, 255, 255, 1);
+  _y := _y+ch+8;
+  e_TextureFontPrintEx(x+(w div 2)-(Length(mapstr)*cw div 2), _y, mapstr, gStdFont, 200, 200, 200, 1);
+  _y := _y+ch+8;
+  e_TextureFontPrintEx(x+16, _y, s2, gStdFont, 200, 200, 200, 1);
+
+  e_TextureFontPrintEx(x+w-16-(Length(s3))*cw, _y, s3,
+                       gStdFont, 200, 200, 200, 1);
+
+  if NetMode = NET_SERVER then
+    e_TextureFontPrintEx(x+8, y + 8, _lc[I_NET_SERVER], gStdFont, 255, 255, 255, 1)
+  else
+    if NetMode = NET_CLIENT then
+      e_TextureFontPrintEx(x+8, y + 8,
+        NetClientIP + ':' + IntToStr(NetClientPort), gStdFont, 255, 255, 255, 1);
+
+  if pc = 0 then
+    Exit;
+  stat := g_Player_GetStats();
+  SortGameStat(stat);
+
+  w2 := (w-16) div 6 + 48; // ширина 2 столбца
+  w3 := (w-16) div 6; // ширина 3 и 4 столбцов
+  w4 := w3;
+  w1 := w-16-w2-w3-w4; // оставшееся пространство - для цвета и имени игрока
+
+  if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
+  begin
+    _y := _y+ch+ch;
+
+    for a := TEAM_RED to TEAM_BLUE do
+    begin
+      if a = TEAM_RED then
+      begin
+        s1 := _lc[I_GAME_TEAM_RED];
+        r := 255;
+        g := 0;
+        b := 0;
+      end
+      else
+      begin
+        s1 := _lc[I_GAME_TEAM_BLUE];
+        r := 0;
+        g := 0;
+        b := 255;
+      end;
+
+      e_TextureFontPrintEx(x+16, _y, s1, gStdFont, r, g, b, 1);
+      e_TextureFontPrintEx(x+w1+16, _y, IntToStr(gTeamStat[a].Score),
+                           gStdFont, r, g, b, 1);
+
+      _y := _y+ch+(ch div 4);
+      e_DrawLine(1, x+16, _y, x+w-16, _y, r, g, b);
+      _y := _y+(ch div 4);
+
+      for aa := 0 to High(stat) do
+        if stat[aa].Team = a then
+          with stat[aa] do
+          begin
+            if Spectator then
+            begin
+              rr := r div 2;
+              gg := g div 2;
+              bb := b div 2;
+            end
+            else
+            begin
+              rr := r;
+              gg := g;
+              bb := b;
+            end;
+            if gShowPIDs then
+              namestr := Format('[%5d] %s', [UID, Name])
+            else
+              namestr := Name;
+            // Имя
+            e_TextureFontPrintEx(x+16, _y, namestr, gStdFont, rr, gg, bb, 1);
+            // Пинг/потери
+            e_TextureFontPrintEx(x+w1+16, _y, Format(_lc[I_GAME_PING_MS], [Ping, Loss]), gStdFont, rr, gg, bb, 1);
+            // Фраги
+            e_TextureFontPrintEx(x+w1+w2+16, _y, IntToStr(Frags), gStdFont, rr, gg, bb, 1);
+            // Смерти
+            e_TextureFontPrintEx(x+w1+w2+w3+16, _y, IntToStr(Deaths), gStdFont, rr, gg, bb, 1);
+            _y := _y+ch;
+          end;
+
+          _y := _y+ch;
+    end;
+  end
+  else if gGameSettings.GameMode in [GM_DM, GM_COOP] then
+  begin
+    _y := _y+ch+ch;
+    e_TextureFontPrintEx(x+16, _y, _lc[I_GAME_PLAYER_NAME], gStdFont, 255, 127, 0, 1);
+    e_TextureFontPrintEx(x+16+w1, _y, _lc[I_GAME_PING], gStdFont, 255, 127, 0, 1);
+    e_TextureFontPrintEx(x+16+w1+w2, _y, _lc[I_GAME_FRAGS], gStdFont, 255, 127, 0, 1);
+    e_TextureFontPrintEx(x+16+w1+w2+w3, _y, _lc[I_GAME_DEATHS], gStdFont, 255, 127, 0, 1);
+
+    _y := _y+ch+8;
+    for aa := 0 to High(stat) do
+      with stat[aa] do
+      begin
+        if Spectator then
+        begin
+          r := 127;
+          g := 64;
+        end
+        else
+        begin
+          r := 255;
+          g := 127;
+        end;
+        if gShowPIDs then
+          namestr := Format('[%5d] %s', [UID, Name])
+        else
+          namestr := Name;
+        // Цвет игрока
+        e_DrawFillQuad(x+16, _y+4, x+32-1, _y+16+4-1, Color.R, Color.G, Color.B, 0);
+        e_DrawQuad(x+16, _y+4, x+32-1, _y+16+4-1, 192, 192, 192);
+        // Имя
+        e_TextureFontPrintEx(x+16+16+8, _y+4, namestr, gStdFont, r, g, 0, 1);
+        // Пинг/потери
+        e_TextureFontPrintEx(x+w1+16, _y+4, Format(_lc[I_GAME_PING_MS], [Ping, Loss]), gStdFont, r, g, 0, 1);
+        // Фраги
+        e_TextureFontPrintEx(x+w1+w2+16, _y+4, IntToStr(Frags), gStdFont, r, g, 0, 1);
+        // Смерти
+        e_TextureFontPrintEx(x+w1+w2+w3+16, _y+4, IntToStr(Deaths), gStdFont, r, g, 0, 1);
+        _y := _y+ch+8;
+      end;
+  end
+end;
+
+procedure DrawCustomStat();
+var
+  pc, x, y, w, _y,
+  w1, w2, w3,
+  t, p, m: Integer;
+  ww1, hh1: Word;
+  ww2, hh2, r, g, b, rr, gg, bb: Byte;
+  s1, s2, topstr: String;
+begin
+  e_TextureFontGetSize(gStdFont, ww2, hh2);
+
+  sys_HandleInput;
+
+  if g_Console_Action(ACTION_SCORES) then
+  begin
+    if not gStatsPressed then
+    begin
+      gStatsOff := not gStatsOff;
+      gStatsPressed := True;
+    end;
+  end
+  else
+    gStatsPressed := False;
+
+  if gStatsOff then
+  begin
+    s1 := _lc[I_MENU_INTER_NOTICE_TAB];
+    w := (Length(s1) * ww2) div 2;
+    x := gScreenWidth div 2 - w;
+    y := 8;
+    e_TextureFontPrint(x, y, s1, gStdFont);
+    Exit;
+  end;
+
+  if (gGameSettings.GameMode = GM_COOP) then
+  begin
+    if gMissionFailed then
+      topstr := _lc[I_MENU_INTER_MISSION_FAIL]
+    else
+      topstr := _lc[I_MENU_INTER_LEVEL_COMPLETE];
+  end
+  else
+    topstr := _lc[I_MENU_INTER_ROUND_OVER];
+
+  e_CharFont_GetSize(gMenuFont, topstr, ww1, hh1);
+  e_CharFont_Print(gMenuFont, (gScreenWidth div 2)-(ww1 div 2), 16, topstr);
+
+  if g_Game_IsNet then
+  begin
+    topstr := Format(_lc[I_MENU_INTER_NOTICE_TIME], [gServInterTime]);
+    if not gChatShow then
+      e_TextureFontPrintEx((gScreenWidth div 2)-(Length(topstr)*ww2 div 2),
+                           gScreenHeight-(hh2+4)*2, topstr, gStdFont, 255, 255, 255, 1);
+  end;
+
+  if g_Game_IsClient then
+    topstr := _lc[I_MENU_INTER_NOTICE_MAP]
+  else
+    topstr := _lc[I_MENU_INTER_NOTICE_SPACE];
+  if not gChatShow then
+    e_TextureFontPrintEx((gScreenWidth div 2)-(Length(topstr)*ww2 div 2),
+                         gScreenHeight-(hh2+4), topstr, gStdFont, 255, 255, 255, 1);
+
+  x := 32;
+  y := 16+hh1+16;
+
+  w := gScreenWidth-x*2;
+
+  w2 := (w-16) div 6;
+  w3 := w2;
+  w1 := w-16-w2-w3;
+
+  e_DrawFillQuad(x, y, gScreenWidth-x-1, gScreenHeight-y-1, 64, 64, 64, 32);
+  e_DrawQuad(x, y, gScreenWidth-x-1, gScreenHeight-y-1, 255, 127, 0);
+
+  m := Max(Length(_lc[I_MENU_MAP])+1, Length(_lc[I_GAME_GAME_TIME])+1)*ww2;
+
+  case CustomStat.GameMode of
+    GM_DM:
+    begin
+      if gGameSettings.MaxLives = 0 then
+        s1 := _lc[I_GAME_DM]
+      else
+        s1 := _lc[I_GAME_LMS];
+    end;
+    GM_TDM:
+    begin
+      if gGameSettings.MaxLives = 0 then
+        s1 := _lc[I_GAME_TDM]
+      else
+        s1 := _lc[I_GAME_TLMS];
+    end;
+    GM_CTF: s1 := _lc[I_GAME_CTF];
+    GM_COOP:
+    begin
+      if gGameSettings.MaxLives = 0 then
+        s1 := _lc[I_GAME_COOP]
+      else
+        s1 := _lc[I_GAME_SURV];
+    end;
+    else s1 := '';
+  end;
+
+  _y := y+16;
+  e_TextureFontPrintEx(x+(w div 2)-(Length(s1)*ww2 div 2), _y, s1, gStdFont, 255, 255, 255, 1);
+  _y := _y+8;
+
+  _y := _y+16;
+  e_TextureFontPrintEx(x+8, _y, _lc[I_MENU_MAP], gStdFont, 255, 127, 0, 1);
+  e_TextureFontPrint(x+8+m, _y, Format('%s - %s', [CustomStat.Map, CustomStat.MapName]), gStdFont);
+
+  _y := _y+16;
+  e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_GAME_TIME], gStdFont, 255, 127, 0, 1);
+  e_TextureFontPrint(x+8+m, _y, Format('%d:%.2d:%.2d', [CustomStat.GameTime div 1000 div 3600,
+                                                       (CustomStat.GameTime div 1000 div 60) mod 60,
+                                                        CustomStat.GameTime div 1000 mod 60]), gStdFont);
+
+  pc := Length(CustomStat.PlayerStat);
+  if pc = 0 then Exit;
+
+  if CustomStat.GameMode = GM_COOP then
+  begin
+    m := Max(Length(_lc[I_GAME_MONSTERS])+1, Length(_lc[I_GAME_SECRETS])+1)*ww2;
+    _y := _y+32;
+    s2 := _lc[I_GAME_MONSTERS];
+    e_TextureFontPrintEx(x+8, _y, s2, gStdFont, 255, 127, 0, 1);
+    e_TextureFontPrintEx(x+8+m, _y, IntToStr(gCoopMonstersKilled) + '/' + IntToStr(gTotalMonsters), gStdFont, 255, 255, 255, 1);
+    _y := _y+16;
+    s2 := _lc[I_GAME_SECRETS];
+    e_TextureFontPrintEx(x+8, _y, s2, gStdFont, 255, 127, 0, 1);
+    e_TextureFontPrintEx(x+8+m, _y, IntToStr(gCoopSecretsFound) + '/' + IntToStr(gSecretsCount), gStdFont, 255, 255, 255, 1);
+    if gLastMap then
+    begin
+      m := Max(Length(_lc[I_GAME_MONSTERS_TOTAL])+1, Length(_lc[I_GAME_SECRETS_TOTAL])+1)*ww2;
+      _y := _y-16;
+      s2 := _lc[I_GAME_MONSTERS_TOTAL];
+      e_TextureFontPrintEx(x+250, _y, s2, gStdFont, 255, 127, 0, 1);
+      e_TextureFontPrintEx(x+250+m, _y, IntToStr(gCoopTotalMonstersKilled) + '/' + IntToStr(gCoopTotalMonsters), gStdFont, 255, 255, 255, 1);
+      _y := _y+16;
+      s2 := _lc[I_GAME_SECRETS_TOTAL];
+      e_TextureFontPrintEx(x+250, _y, s2, gStdFont, 255, 127, 0, 1);
+      e_TextureFontPrintEx(x+250+m, _y, IntToStr(gCoopTotalSecretsFound) + '/' + IntToStr(gCoopTotalSecrets), gStdFont, 255, 255,  255, 1);
+    end;
+  end;
+
+  if CustomStat.GameMode in [GM_TDM, GM_CTF] then
+  begin
+    _y := _y+16+16;
+
+    with CustomStat do
+      if TeamStat[TEAM_RED].Score > TeamStat[TEAM_BLUE].Score then s1 := _lc[I_GAME_WIN_RED]
+        else if TeamStat[TEAM_BLUE].Score > TeamStat[TEAM_RED].Score then s1 := _lc[I_GAME_WIN_BLUE]
+          else s1 := _lc[I_GAME_WIN_DRAW];
+
+    e_TextureFontPrintEx(x+8+(w div 2)-(Length(s1)*ww2 div 2), _y, s1, gStdFont, 255, 255, 255, 1);
+    _y := _y+40;
+
+    for t := TEAM_RED to TEAM_BLUE do
+    begin
+      if t = TEAM_RED then
+      begin
+        e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_TEAM_RED],
+                             gStdFont, 255, 0, 0, 1);
+        e_TextureFontPrintEx(x+w1+8, _y, IntToStr(CustomStat.TeamStat[TEAM_RED].Score),
+                             gStdFont, 255, 0, 0, 1);
+        r := 255;
+        g := 0;
+        b := 0;
+      end
+      else
+      begin
+        e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_TEAM_BLUE],
+                             gStdFont, 0, 0, 255, 1);
+        e_TextureFontPrintEx(x+w1+8, _y, IntToStr(CustomStat.TeamStat[TEAM_BLUE].Score),
+                             gStdFont, 0, 0, 255, 1);
+        r := 0;
+        g := 0;
+        b := 255;
+      end;
+
+      e_DrawLine(1, x+8, _y+20, x-8+w, _y+20, r, g, b);
+      _y := _y+24;
+
+      for p := 0 to High(CustomStat.PlayerStat) do
+        if CustomStat.PlayerStat[p].Team = t then
+          with CustomStat.PlayerStat[p] do
+          begin
+            if Spectator then
+            begin
+              rr := r div 2;
+              gg := g div 2;
+              bb := b div 2;
+            end
+            else
+            begin
+              rr := r;
+              gg := g;
+              bb := b;
+            end;
+            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;
+
+      _y := _y+16+16;
+    end;
+  end
+  else if CustomStat.GameMode in [GM_DM, GM_COOP] then
+  begin
+    _y := _y+40;
+    e_TextureFontPrintEx(x+8, _y, _lc[I_GAME_PLAYER_NAME], gStdFont, 255, 127, 0, 1);
+    e_TextureFontPrintEx(x+8+w1, _y, _lc[I_GAME_FRAGS], gStdFont, 255, 127, 0, 1);
+    e_TextureFontPrintEx(x+8+w1+w2, _y, _lc[I_GAME_DEATHS], gStdFont, 255, 127, 0, 1);
+
+    _y := _y+24;
+    for p := 0 to High(CustomStat.PlayerStat) do
+      with CustomStat.PlayerStat[p] do
+      begin
+        e_DrawFillQuad(x+8, _y+4, x+24-1, _y+16+4-1, Color.R, Color.G, Color.B, 0);
+
+        if Spectator then
+          r := 127
+        else
+          r := 255;
+
+        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;
+      end;
+  end;
+
+  // HACK: take stats screenshot immediately after the first frame of the stats showing
+  if gScreenshotStats and (not StatShotDone) and (Length(CustomStat.PlayerStat) > 1) then
+  begin
+    g_TakeScreenShot('stats/' + StatFilename);
+    StatShotDone := True;
+  end;
+end;
+
+procedure DrawSingleStat();
+var
+  tm, key_x, val_x, y: Integer;
+  w1, w2, h: Word;
+  s1, s2: String;
+
+  procedure player_stat(n: Integer);
+  var
+    kpm: Real;
+
+  begin
+  // "Kills: # / #":
+    s1 := Format(' %d ', [SingleStat.PlayerStat[n].Kills]);
+    s2 := Format(' %d', [gTotalMonsters]);
+
+    e_CharFont_Print(gMenuFont, key_x, y, _lc[I_MENU_INTER_KILLS]);
+    e_CharFont_PrintEx(gMenuFont, val_x, y, s1, _RGB(255, 0, 0));
+    e_CharFont_GetSize(gMenuFont, s1, w1, h);
+    e_CharFont_Print(gMenuFont, val_x+w1, y, '/');
+    s1 := s1 + '/';
+    e_CharFont_GetSize(gMenuFont, s1, w1, h);
+    e_CharFont_PrintEx(gMenuFont, val_x+w1, y, s2, _RGB(255, 0, 0));
+
+  // "Kills-per-minute: ##.#":
+    s1 := _lc[I_MENU_INTER_KPM];
+    if tm > 0 then
+      kpm := (SingleStat.PlayerStat[n].Kills / tm) * 60
+    else
+      kpm := SingleStat.PlayerStat[n].Kills;
+    s2 := Format(' %.1f', [kpm]);
+
+    e_CharFont_Print(gMenuFont, key_x, y+32, s1);
+    e_CharFont_PrintEx(gMenuFont, val_x, y+32, s2, _RGB(255, 0, 0));
+
+  // "Secrets found: # / #":
+    s1 := Format(' %d ', [SingleStat.PlayerStat[n].Secrets]);
+    s2 := Format(' %d', [SingleStat.TotalSecrets]);
+
+    e_CharFont_Print(gMenuFont, key_x, y+64, _lc[I_MENU_INTER_SECRETS]);
+    e_CharFont_PrintEx(gMenuFont, val_x, y+64, s1, _RGB(255, 0, 0));
+    e_CharFont_GetSize(gMenuFont, s1, w1, h);
+    e_CharFont_Print(gMenuFont, val_x+w1, y+64, '/');
+    s1 := s1 + '/';
+    e_CharFont_GetSize(gMenuFont, s1, w1, h);
+    e_CharFont_PrintEx(gMenuFont, val_x+w1, y+64, s2, _RGB(255, 0, 0));
+  end;
+
+begin
+// "Level Complete":
+  e_CharFont_GetSize(gMenuFont, _lc[I_MENU_INTER_LEVEL_COMPLETE], w1, h);
+  e_CharFont_Print(gMenuFont, (gScreenWidth-w1) div 2, 32, _lc[I_MENU_INTER_LEVEL_COMPLETE]);
+
+// Определяем координаты выравнивания по самой длинной строке:
+  s1 := _lc[I_MENU_INTER_KPM];
+  e_CharFont_GetSize(gMenuFont, s1, w1, h);
+  Inc(w1, 16);
+  s1 := ' 9999.9';
+  e_CharFont_GetSize(gMenuFont, s1, w2, h);
+
+  key_x := (gScreenWidth-w1-w2) div 2;
+  val_x := key_x + w1;
+
+// "Time: #:##:##":
+  tm := SingleStat.GameTime div 1000;
+  s1 := _lc[I_MENU_INTER_TIME];
+  s2 := Format(' %d:%.2d:%.2d', [tm div (60*60), (tm mod (60*60)) div 60, tm mod 60]);
+
+  e_CharFont_Print(gMenuFont, key_x, 80, s1);
+  e_CharFont_PrintEx(gMenuFont, val_x, 80, s2, _RGB(255, 0, 0));
+
+  if SingleStat.TwoPlayers then
+    begin
+    // "Player 1":
+      s1 := _lc[I_MENU_PLAYER_1];
+      e_CharFont_GetSize(gMenuFont, s1, w1, h);
+      e_CharFont_Print(gMenuFont, (gScreenWidth-w1) div 2, 128, s1);
+
+    // Статистика первого игрока:
+      y := 176;
+      player_stat(0);
+
+    // "Player 2":
+      s1 := _lc[I_MENU_PLAYER_2];
+      e_CharFont_GetSize(gMenuFont, s1, w1, h);
+      e_CharFont_Print(gMenuFont, (gScreenWidth-w1) div 2, 288, s1);
+
+    // Статистика второго игрока:
+      y := 336;
+      player_stat(1);
+    end
+  else
+    begin
+    // Статистика первого игрока:
+      y := 128;
+      player_stat(0);
+    end;
+end;
+
+procedure DrawLoadingStat();
+  procedure drawRect (x, y, w, h: Integer);
+  begin
+    if (w < 1) or (h < 1) then exit;
+    glBegin(GL_QUADS);
+      glVertex2f(x+0.375, y+0.375);
+      glVertex2f(x+w+0.375, y+0.375);
+      glVertex2f(x+w+0.375, y+h+0.375);
+      glVertex2f(x+0.375, y+h+0.375);
+    glEnd();
+  end;
+
+  function drawPBar (cur, total: Integer; washere: Boolean): Boolean;
+  var
+    rectW, rectH: Integer;
+    x0, y0: Integer;
+    wdt: Integer;
+    wl, hl: Integer;
+    wr, hr: Integer;
+    wb, hb: Integer;
+    wm, hm: Integer;
+    idl, idr, idb, idm: LongWord;
+    f, my: Integer;
+  begin
+    result := false;
+    if (total < 1) then exit;
+    if (cur < 1) then exit; // don't blink
+    if (not washere) and (cur >= total) then exit; // don't blink
+    //if (cur < 0) then cur := 0;
+    //if (cur > total) then cur := total;
+    result := true;
+
+    if (hasPBarGfx) then
+    begin
+      g_Texture_Get('UI_GFX_PBAR_LEFT', idl);
+      g_Texture_GetSize('UI_GFX_PBAR_LEFT', wl, hl);
+      g_Texture_Get('UI_GFX_PBAR_RIGHT', idr);
+      g_Texture_GetSize('UI_GFX_PBAR_RIGHT', wr, hr);
+      g_Texture_Get('UI_GFX_PBAR_MIDDLE', idb);
+      g_Texture_GetSize('UI_GFX_PBAR_MIDDLE', wb, hb);
+      g_Texture_Get('UI_GFX_PBAR_MARKER', idm);
+      g_Texture_GetSize('UI_GFX_PBAR_MARKER', wm, hm);
+
+      //rectW := gScreenWidth-360;
+      rectW := trunc(624.0*gScreenWidth/1024.0);
+      rectH := hl;
+
+      x0 := (gScreenWidth-rectW) div 2;
+      y0 := gScreenHeight-rectH-64;
+      if (y0 < 2) then y0 := 2;
+
+      glEnable(GL_SCISSOR_TEST);
+
+      // left and right
+      glScissor(x0, gScreenHeight-y0-rectH, rectW, rectH);
+      e_DrawSize(idl, x0, y0, 0, true, false, wl, hl);
+      e_DrawSize(idr, x0+rectW-wr, y0, 0, true, false, wr, hr);
+
+      // body
+      glScissor(x0+wl, gScreenHeight-y0-rectH, rectW-wl-wr, rectH);
+      f := x0+wl;
+      while (f < x0+rectW) do
+      begin
+        e_DrawSize(idb, f, y0, 0, true, false, wb, hb);
+        f += wb;
+      end;
+
+      // filled part
+      wdt := (rectW-wl-wr)*cur div total;
+      if (wdt > rectW-wl-wr) then wdt := rectW-wr-wr;
+      if (wdt > 0) then
+      begin
+        my := y0; // don't be so smart, ketmar: +(rectH-wm) div 2;
+        glScissor(x0+wl, gScreenHeight-my-rectH, wdt, hm);
+        f := x0+wl;
+        while (wdt > 0) do
+        begin
+          e_DrawSize(idm, f, y0, 0, true, false, wm, hm);
+          f += wm;
+          wdt -= wm;
+        end;
+      end;
+
+      glScissor(0, 0, gScreenWidth, gScreenHeight);
+    end
+    else
+    begin
+      rectW := gScreenWidth-64;
+      rectH := 16;
+
+      x0 := (gScreenWidth-rectW) div 2;
+      y0 := gScreenHeight-rectH-64;
+      if (y0 < 2) then y0 := 2;
+
+      glDisable(GL_BLEND);
+      glDisable(GL_TEXTURE_2D);
+
+      //glClearColor(0, 0, 0, 0);
+      //glClear(GL_COLOR_BUFFER_BIT);
+
+      glColor4ub(127, 127, 127, 255);
+      drawRect(x0-2, y0-2, rectW+4, rectH+4);
+
+      glColor4ub(0, 0, 0, 255);
+      drawRect(x0-1, y0-1, rectW+2, rectH+2);
+
+      glColor4ub(127, 127, 127, 255);
+      wdt := rectW*cur div total;
+      if (wdt > rectW) then wdt := rectW;
+      drawRect(x0, y0, wdt, rectH);
+    end;
+  end;
+
+var
+  ww, hh: Word;
+  xx, yy, i: Integer;
+  s: String;
+begin
+  if (Length(LoadingStat.Msgs) = 0) then exit;
+
+  e_CharFont_GetSize(gMenuFont, _lc[I_MENU_LOADING], ww, hh);
+  yy := (gScreenHeight div 3);
+  e_CharFont_Print(gMenuFont, (gScreenWidth div 2)-(ww div 2), yy-2*hh, _lc[I_MENU_LOADING]);
+  xx := (gScreenWidth div 3);
+
+  with LoadingStat do
+  begin
+    for i := 0 to NextMsg-1 do
+    begin
+      if (i = (NextMsg-1)) and (MaxValue > 0) then
+        s := Format('%s:  %d/%d', [Msgs[i], CurValue, MaxValue])
+      else
+        s := Msgs[i];
+
+      e_CharFont_PrintEx(gMenuSmallFont, xx, yy, s, _RGB(255, 0, 0));
+      yy := yy + LOADING_INTERLINE;
+      PBarWasHere := drawPBar(CurValue, MaxValue, PBarWasHere);
+    end;
+  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;
+
+  function monDraw (mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    with mon do
+    begin
+      if alive then
+      begin
+        // Левый верхний угол
+        aX := Obj.X div ScaleSz + 1;
+        aY := Obj.Y div ScaleSz + 1;
+        // Размеры
+        aX2 := max(Obj.Rect.Width div ScaleSz, 1);
+        aY2 := max(Obj.Rect.Height div ScaleSz, 1);
+        // Правый нижний угол
+        aX2 := aX + aX2 - 1;
+        aY2 := aY + aY2 - 1;
+        e_DrawFillQuad(aX, aY, aX2, aY2, 255, 255, 0, 0);
+      end;
+    end;
+  end;
+
+begin
+  if (gMapInfo.Width > RenderRect.Right - RenderRect.Left) or
+     (gMapInfo.Height > RenderRect.Bottom - RenderRect.Top) then
+  begin
+    Scale := 1;
+  // Сколько пикселов карты в 1 пикселе мини-карты:
+    ScaleSz := 16 div Scale;
+  // Размеры мини-карты:
+    aX := max(gMapInfo.Width div ScaleSz, 1);
+    aY := max(gMapInfo.Height div ScaleSz, 1);
+  // Рамка карты:
+    e_DrawFillQuad(0, 0, aX-1, aY-1, 0, 0, 0, 0);
+
+    if gWalls <> nil then
+    begin
+    // Рисуем стены:
+      for a := 0 to High(gWalls) do
+        with gWalls[a] do
+          if PanelType <> 0 then
+          begin
+          // Левый верхний угол:
+            aX := X div ScaleSz;
+            aY := Y div ScaleSz;
+          // Размеры:
+            aX2 := max(Width div ScaleSz, 1);
+            aY2 := max(Height div ScaleSz, 1);
+          // Правый нижний угол:
+            aX2 := aX + aX2 - 1;
+            aY2 := aY + aY2 - 1;
+
+            case PanelType of
+              PANEL_WALL:      e_DrawFillQuad(aX, aY, aX2, aY2, 208, 208, 208, 0);
+              PANEL_OPENDOOR, PANEL_CLOSEDOOR:
+                if Enabled then e_DrawFillQuad(aX, aY, aX2, aY2, 160, 160, 160, 0);
+            end;
+          end;
+    end;
+    if gSteps <> nil then
+    begin
+    // Рисуем ступени:
+      for a := 0 to High(gSteps) do
+        with gSteps[a] do
+          if PanelType <> 0 then
+          begin
+          // Левый верхний угол:
+            aX := X div ScaleSz;
+            aY := Y div ScaleSz;
+          // Размеры:
+            aX2 := max(Width div ScaleSz, 1);
+            aY2 := max(Height div ScaleSz, 1);
+          // Правый нижний угол:
+            aX2 := aX + aX2 - 1;
+            aY2 := aY + aY2 - 1;
+
+            e_DrawFillQuad(aX, aY, aX2, aY2, 128, 128, 128, 0);
+          end;
+    end;
+    if gLifts <> nil then
+    begin
+    // Рисуем лифты:
+      for a := 0 to High(gLifts) do
+        with gLifts[a] do
+          if PanelType <> 0 then
+          begin
+          // Левый верхний угол:
+            aX := X div ScaleSz;
+            aY := Y div ScaleSz;
+          // Размеры:
+            aX2 := max(Width div ScaleSz, 1);
+            aY2 := max(Height div ScaleSz, 1);
+          // Правый нижний угол:
+            aX2 := aX + aX2 - 1;
+            aY2 := aY + aY2 - 1;
+
+            case LiftType of
+              LIFTTYPE_UP:    e_DrawFillQuad(aX, aY, aX2, aY2, 116,  72,  36, 0);
+              LIFTTYPE_DOWN:  e_DrawFillQuad(aX, aY, aX2, aY2, 116, 124,  96, 0);
+              LIFTTYPE_LEFT:  e_DrawFillQuad(aX, aY, aX2, aY2, 200,  80,   4, 0);
+              LIFTTYPE_RIGHT: e_DrawFillQuad(aX, aY, aX2, aY2, 252, 140,  56, 0);
+            end;
+          end;
+    end;
+    if gWater <> nil then
+    begin
+    // Рисуем воду:
+      for a := 0 to High(gWater) do
+        with gWater[a] do
+          if PanelType <> 0 then
+          begin
+          // Левый верхний угол:
+            aX := X div ScaleSz;
+            aY := Y div ScaleSz;
+          // Размеры:
+            aX2 := max(Width div ScaleSz, 1);
+            aY2 := max(Height div ScaleSz, 1);
+          // Правый нижний угол:
+            aX2 := aX + aX2 - 1;
+            aY2 := aY + aY2 - 1;
+
+            e_DrawFillQuad(aX, aY, aX2, aY2, 0, 0, 192, 0);
+          end;
+    end;
+    if gAcid1 <> nil then
+    begin
+    // Рисуем кислоту 1:
+      for a := 0 to High(gAcid1) do
+        with gAcid1[a] do
+          if PanelType <> 0 then
+          begin
+          // Левый верхний угол:
+            aX := X div ScaleSz;
+            aY := Y div ScaleSz;
+          // Размеры:
+            aX2 := max(Width div ScaleSz, 1);
+            aY2 := max(Height div ScaleSz, 1);
+          // Правый нижний угол:
+            aX2 := aX + aX2 - 1;
+            aY2 := aY + aY2 - 1;
+
+            e_DrawFillQuad(aX, aY, aX2, aY2, 0, 176, 0, 0);
+          end;
+    end;
+    if gAcid2 <> nil then
+    begin
+    // Рисуем кислоту 2:
+      for a := 0 to High(gAcid2) do
+        with gAcid2[a] do
+          if PanelType <> 0 then
+          begin
+          // Левый верхний угол:
+            aX := X div ScaleSz;
+            aY := Y div ScaleSz;
+          // Размеры:
+            aX2 := max(Width div ScaleSz, 1);
+            aY2 := max(Height div ScaleSz, 1);
+          // Правый нижний угол:
+            aX2 := aX + aX2 - 1;
+            aY2 := aY + aY2 - 1;
+
+            e_DrawFillQuad(aX, aY, aX2, aY2, 176, 0, 0, 0);
+          end;
+    end;
+    if gPlayers <> nil then
+    begin
+    // Рисуем игроков:
+      for a := 0 to High(gPlayers) do
+        if gPlayers[a] <> nil then with gPlayers[a] do
+          if alive then begin
+          // Левый верхний угол:
+            aX := Obj.X div ScaleSz + 1;
+            aY := Obj.Y div ScaleSz + 1;
+          // Размеры:
+            aX2 := max(Obj.Rect.Width div ScaleSz, 1);
+            aY2 := max(Obj.Rect.Height div ScaleSz, 1);
+          // Правый нижний угол:
+            aX2 := aX + aX2 - 1;
+            aY2 := aY + aY2 - 1;
+
+            if gPlayers[a] = p then
+              e_DrawFillQuad(aX, aY, aX2, aY2, 0, 255, 0, 0)
+            else
+              case Team of
+                TEAM_RED:  e_DrawFillQuad(aX, aY, aX2, aY2, 255,   0,   0, 0);
+                TEAM_BLUE: e_DrawFillQuad(aX, aY, aX2, aY2, 0,     0, 255, 0);
+                else       e_DrawFillQuad(aX, aY, aX2, aY2, 255, 128,   0, 0);
+              end;
+          end;
+    end;
+    // Рисуем монстров
+    g_Mons_ForEach(monDraw);
+  end;
+end;
+
+
+procedure renderAmbientQuad (hasAmbient: Boolean; constref ambColor: TDFColor);
+begin
+  if not hasAmbient then exit;
+  e_AmbientQuad(sX, sY, sWidth, sHeight, ambColor.r, ambColor.g, ambColor.b, ambColor.a);
+end;
+
+
+// setup sX, sY, sWidth, sHeight, and transformation matrix before calling this!
+//FIXME: broken for splitscreen mode
+procedure renderDynLightsInternal ();
+var
+  //hasAmbient: Boolean;
+  //ambColor: TDFColor;
+  lln: Integer;
+  lx, ly, lrad: Integer;
+  scxywh: array[0..3] of GLint;
+  wassc: Boolean;
+begin
+  if e_NoGraphics then exit;
+
+  //TODO: lights should be in separate grid, i think
+  //      but on the other side: grid may be slower for dynlights, as their lifetime is short
+  if (not gwin_k8_enable_light_experiments) or (not gwin_has_stencil) or (g_dynLightCount < 1) then exit;
+
+  // rendering mode
+  //ambColor := gCurrentMap['light_ambient'].rgba;
+  //hasAmbient := (not ambColor.isOpaque) or (not ambColor.isBlack);
+
+  { // this will multiply incoming color to alpha from framebuffer
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_DST_ALPHA, GL_ONE);
+  }
+
+  (*
+   * light rendering: (INVALID!)
+   *   glStencilFunc(GL_EQUAL, 0, $ff);
+   *   for each light:
+   *     glClear(GL_STENCIL_BUFFER_BIT);
+   *     glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+   *     draw shadow volume into stencil buffer
+   *     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // modify color buffer
+   *     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // don't modify stencil buffer
+   *     turn off blending
+   *     draw color-less quad with light alpha (WARNING! don't touch color!)
+   *     glEnable(GL_BLEND);
+   *     glBlendFunc(GL_DST_ALPHA, GL_ONE);
+   *     draw all geometry up to and including walls (with alpha-testing, probably) -- this does lighting
+   *)
+  wassc := (glIsEnabled(GL_SCISSOR_TEST) <> 0);
+  if wassc then glGetIntegerv(GL_SCISSOR_BOX, @scxywh[0]) else glGetIntegerv(GL_VIEWPORT, @scxywh[0]);
+
+  // setup OpenGL parameters
+  glStencilMask($FFFFFFFF);
+  glStencilFunc(GL_ALWAYS, 0, $FFFFFFFF);
+  glEnable(GL_STENCIL_TEST);
+  glEnable(GL_SCISSOR_TEST);
+  glClear(GL_STENCIL_BUFFER_BIT);
+  glStencilFunc(GL_EQUAL, 0, $ff);
+
+  for lln := 0 to g_dynLightCount-1 do
+  begin
+    lx := g_dynLights[lln].x;
+    ly := g_dynLights[lln].y;
+    lrad := g_dynLights[lln].radius;
+    if (lrad < 3) then continue;
+
+    if (lx-sX+lrad < 0) then continue;
+    if (ly-sY+lrad < 0) then continue;
+    if (lx-sX-lrad >= gPlayerScreenSize.X) then continue;
+    if (ly-sY-lrad >= gPlayerScreenSize.Y) then continue;
+
+    // set scissor to optimize drawing
+    if (g_dbg_scale = 1.0) then
+    begin
+      glScissor((lx-sX)-lrad+2, gPlayerScreenSize.Y-(ly-sY)-lrad-1+2, lrad*2-4, lrad*2-4);
+    end
+    else
+    begin
+      glScissor(0, 0, gScreenWidth, gScreenHeight);
+    end;
+    // no need to clear stencil buffer, light blitting will do it for us... but only for normal scale
+    if (g_dbg_scale <> 1.0) then glClear(GL_STENCIL_BUFFER_BIT);
+    glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+    // draw extruded panels
+    glDisable(GL_TEXTURE_2D);
+    glDisable(GL_BLEND);
+    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // no need to modify color buffer
+    if (lrad > 4) then g_Map_DrawPanelShadowVolumes(lx, ly, lrad);
+    // render light texture
+    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // modify color buffer
+    glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); // draw light, and clear stencil buffer
+    // blend it
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    glEnable(GL_TEXTURE_2D);
+    // color and opacity
+    glColor4f(g_dynLights[lln].r, g_dynLights[lln].g, g_dynLights[lln].b, g_dynLights[lln].a);
+    glBindTexture(GL_TEXTURE_2D, g_Texture_Light());
+    glBegin(GL_QUADS);
+      glTexCoord2f(0.0, 0.0); glVertex2i(lx-lrad, ly-lrad); // top-left
+      glTexCoord2f(1.0, 0.0); glVertex2i(lx+lrad, ly-lrad); // top-right
+      glTexCoord2f(1.0, 1.0); glVertex2i(lx+lrad, ly+lrad); // bottom-right
+      glTexCoord2f(0.0, 1.0); glVertex2i(lx-lrad, ly+lrad); // bottom-left
+    glEnd();
+  end;
+
+  // done
+  glDisable(GL_STENCIL_TEST);
+  glDisable(GL_BLEND);
+  glDisable(GL_SCISSOR_TEST);
+  //glScissor(0, 0, sWidth, sHeight);
+
+  glScissor(scxywh[0], scxywh[1], scxywh[2], scxywh[3]);
+  if wassc then glEnable(GL_SCISSOR_TEST) else glDisable(GL_SCISSOR_TEST);
+end;
+
+
+function fixViewportForScale (): Boolean;
+var
+  nx0, ny0, nw, nh: Integer;
+begin
+  result := false;
+  if (g_dbg_scale <> 1.0) then
+  begin
+    result := true;
+    nx0 := round(sX-(gPlayerScreenSize.X-(sWidth*g_dbg_scale))/2/g_dbg_scale);
+    ny0 := round(sY-(gPlayerScreenSize.Y-(sHeight*g_dbg_scale))/2/g_dbg_scale);
+    nw := round(sWidth/g_dbg_scale);
+    nh := round(sHeight/g_dbg_scale);
+    sX := nx0;
+    sY := ny0;
+    sWidth := nw;
+    sHeight := nh;
+  end;
+end;
+
+
+// setup sX, sY, sWidth, sHeight, and transformation matrix before calling this!
+// WARNING! this WILL CALL `glTranslatef()`, but won't restore matrices!
+procedure renderMapInternal (backXOfs, backYOfs: Integer; setTransMatrix: Boolean);
+type
+  TDrawCB = procedure ();
+
+var
+  hasAmbient: Boolean;
+  ambColor: TDFColor;
+  doAmbient: Boolean = false;
+
+  procedure drawPanelType (profname: AnsiString; panType: DWord; doDraw: Boolean);
+  var
+    tagmask: Integer;
+    pan: TPanel;
+  begin
+    if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin(profname);
+    if gdbg_map_use_accel_render then
+    begin
+      tagmask := panelTypeToTag(panType);
+      while (gDrawPanelList.count > 0) do
+      begin
+        pan := TPanel(gDrawPanelList.front());
+        if ((pan.tag and tagmask) = 0) then break;
+        if doDraw then pan.Draw(doAmbient, ambColor);
+        gDrawPanelList.popFront();
+      end;
+    end
+    else
+    begin
+      if doDraw then g_Map_DrawPanels(panType, hasAmbient, ambColor);
+    end;
+    if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
+  end;
+
+  procedure drawOther (profname: AnsiString; cb: TDrawCB);
+  begin
+    if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin(profname);
+    if assigned(cb) then cb();
+    if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
+  end;
+
+begin
+  if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin('total');
+
+  // our accelerated renderer will collect all panels to gDrawPanelList
+  // we can use panel tag to render level parts (see GridTagXXX in g_map.pas)
+  if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin('collect');
+  if gdbg_map_use_accel_render then
+  begin
+    g_Map_CollectDrawPanels(sX, sY, sWidth, sHeight);
+  end;
+  if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
+
+  if (profileFrameDraw <> nil) then profileFrameDraw.sectionBegin('skyback');
+  g_Map_DrawBack(backXOfs, backYOfs);
+  if (profileFrameDraw <> nil) then profileFrameDraw.sectionEnd();
+
+  if setTransMatrix then
+  begin
+    //if (g_dbg_scale <> 1.0) then glTranslatef(0.0, -0.375/2, 0);
+    glScalef(g_dbg_scale, g_dbg_scale, 1.0);
+    glTranslatef(-sX, -sY, 0);
+  end;
+
+  // rendering mode
+  ambColor := gCurrentMap['light_ambient'].rgba;
+  hasAmbient := (not ambColor.isOpaque) or (not ambColor.isBlack);
+
+  {
+  if hasAmbient then
+  begin
+    //writeln('color: (', ambColor.r, ',', ambColor.g, ',', ambColor.b, ',', ambColor.a, ')');
+    glColor4ub(ambColor.r, ambColor.g, ambColor.b, ambColor.a);
+    glClear(GL_COLOR_BUFFER_BIT);
+  end;
+  }
+  //writeln('color: (', ambColor.r, ',', ambColor.g, ',', ambColor.b, ',', ambColor.a, ')');
+
+
+  drawPanelType('*back', PANEL_BACK, g_rlayer_back);
+  drawPanelType('*step', PANEL_STEP, g_rlayer_step);
+  drawOther('items', @g_Items_Draw);
+  drawOther('weapons', @g_Weapon_Draw);
+  drawOther('shells', @g_Player_DrawShells);
+  drawOther('drawall', @g_Player_DrawAll);
+  drawOther('corpses', @g_Player_DrawCorpses);
+  drawPanelType('*wall', PANEL_WALL, g_rlayer_wall);
+  drawOther('monsters', @g_Monsters_Draw);
+  drawOther('itemdrop', @g_Items_DrawDrop);
+  drawPanelType('*door', PANEL_CLOSEDOOR, g_rlayer_door);
+  drawOther('gfx', @g_GFX_Draw);
+  drawOther('flags', @g_Map_DrawFlags);
+  drawPanelType('*acid1', PANEL_ACID1, g_rlayer_acid1);
+  drawPanelType('*acid2', PANEL_ACID2, g_rlayer_acid2);
+  drawPanelType('*water', PANEL_WATER, g_rlayer_water);
+  drawOther('dynlights', @renderDynLightsInternal);
+
+  if hasAmbient {and ((not g_playerLight) or (not gwin_has_stencil) or (g_dynLightCount < 1))} then
+  begin
+    renderAmbientQuad(hasAmbient, ambColor);
+  end;
+
+  doAmbient := true;
+  drawPanelType('*fore', PANEL_FORE, g_rlayer_fore);
+
+
+  if g_debug_HealthBar then
+  begin
+    g_Monsters_DrawHealth();
+    g_Player_DrawHealth();
+  end;
+
+  if (profileFrameDraw <> nil) then profileFrameDraw.mainEnd(); // map rendering
+end;
+
+
+procedure DrawMapView(x, y, w, h: Integer);
+
+var
+  bx, by: Integer;
+begin
+  glPushMatrix();
+
+  bx := Round(x/(gMapInfo.Width - w)*(gBackSize.X - w));
+  by := Round(y/(gMapInfo.Height - h)*(gBackSize.Y - h));
+
+  sX := x;
+  sY := y;
+  sWidth := w;
+  sHeight := h;
+
+  fixViewportForScale();
+  renderMapInternal(-bx, -by, true);
+
+  glPopMatrix();
+end;
+
+
+procedure DrawPlayer(p: TPlayer);
+var
+  px, py, a, b, c, d, i, fX, fY: Integer;
+  camObj: TObj;
+  //R: TRect;
+begin
+  if (p = nil) or (p.FDummy) then
+  begin
+    glPushMatrix();
+    g_Map_DrawBack(0, 0);
+    glPopMatrix();
+    Exit;
+  end;
+
+  if (profileFrameDraw = nil) then profileFrameDraw := TProfiler.Create('RENDER', g_profile_history_size);
+  if (profileFrameDraw <> nil) then profileFrameDraw.mainBegin(g_profile_frame_draw);
+
+  gPlayerDrawn := p;
+
+  glPushMatrix();
+
+  camObj := p.getCameraObj();
+  camObj.lerp(gLerpFactor, fX, fY);
+  px := fX + PLAYER_RECT_CX;
+  py := fY + PLAYER_RECT_CY+nlerp(p.SlopeOld, camObj.slopeUpLeft, gLerpFactor);
+
+  if (g_dbg_scale = 1.0) and (not g_dbg_ignore_bounds) then
+  begin
+    if (px > (gPlayerScreenSize.X div 2)) then a := -px+(gPlayerScreenSize.X div 2) else a := 0;
+    if (py > (gPlayerScreenSize.Y div 2)) then b := -py+(gPlayerScreenSize.Y div 2) else b := 0;
+
+    if (px > gMapInfo.Width-(gPlayerScreenSize.X div 2)) then a := -gMapInfo.Width+gPlayerScreenSize.X;
+    if (py > gMapInfo.Height-(gPlayerScreenSize.Y div 2)) then b := -gMapInfo.Height+gPlayerScreenSize.Y;
+
+         if (gMapInfo.Width = gPlayerScreenSize.X) then a := 0
+    else if (gMapInfo.Width < gPlayerScreenSize.X) then
+    begin
+      // hcenter
+      a := (gPlayerScreenSize.X-gMapInfo.Width) div 2;
+    end;
+
+         if (gMapInfo.Height = gPlayerScreenSize.Y) then b := 0
+    else if (gMapInfo.Height < gPlayerScreenSize.Y) then
+    begin
+      // vcenter
+      b := (gPlayerScreenSize.Y-gMapInfo.Height) div 2;
+    end;
+  end
+  else
+  begin
+    // scaled, ignore level bounds
+    a := -px+(gPlayerScreenSize.X div 2);
+    b := -py+(gPlayerScreenSize.Y div 2);
+  end;
+
+  sX := -a;
+  sY := -b;
+  sWidth := gPlayerScreenSize.X;
+  sHeight := gPlayerScreenSize.Y;
+  fixViewportForScale();
+
+  i := py - (sY + sHeight div 2);
+  if (p.IncCam > 0) then
+  begin
+    // clamp to level bounds
+    if (sY - p.IncCam < 0) then
+      p.IncCam := nclamp(sY, 0, 120);
+    // clamp around player position
+    if (i > 0) then
+      p.IncCam := nclamp(p.IncCam, 0, max(0, 120 - i));
+  end
+  else if (p.IncCam < 0) then
+  begin
+    // clamp to level bounds
+    if (sY + sHeight - p.IncCam > gMapInfo.Height) then
+      p.IncCam := nclamp(sY + sHeight - gMapInfo.Height, -120, 0);
+    // clamp around player position
+    if (i < 0) then
+      p.IncCam := nclamp(p.IncCam, min(0, -120 - i), 0);
+  end;
+
+  sY := sY - nlerp(p.IncCamOld, p.IncCam, gLerpFactor);
+
+  if (not g_dbg_ignore_bounds) then
+  begin
+    if (sX+sWidth > gMapInfo.Width) then sX := gMapInfo.Width-sWidth;
+    if (sY+sHeight > gMapInfo.Height) then sY := gMapInfo.Height-sHeight;
+    if (sX < 0) then sX := 0;
+    if (sY < 0) then sY := 0;
+  end;
+
+  if (gBackSize.X <= gPlayerScreenSize.X) or (gMapInfo.Width <= sWidth) then c := 0 else c := trunc((gBackSize.X-gPlayerScreenSize.X)*sX/(gMapInfo.Width-sWidth));
+  if (gBackSize.Y <= gPlayerScreenSize.Y) or (gMapInfo.Height <= sHeight) then d := 0 else d := trunc((gBackSize.Y-gPlayerScreenSize.Y)*sY/(gMapInfo.Height-sHeight));
+
+  //r_smallmap_h: 0: left; 1: center; 2: right
+  //r_smallmap_v: 0: top; 1: center; 2: bottom
+  // horiz small map?
+  if (gMapInfo.Width = sWidth) then
+  begin
+    sX := 0;
+  end
+  else if (gMapInfo.Width < sWidth) then
+  begin
+    case r_smallmap_h of
+      1: sX := -((sWidth-gMapInfo.Width) div 2); // center
+      2: sX := -(sWidth-gMapInfo.Width); // right
+      else sX := 0; // left
+    end;
+  end;
+  // vert small map?
+  if (gMapInfo.Height = sHeight) then
+  begin
+    sY := 0;
+  end
+  else if (gMapInfo.Height < sHeight) then
+  begin
+    case r_smallmap_v of
+      1: sY := -((sHeight-gMapInfo.Height) div 2); // center
+      2: sY := -(sHeight-gMapInfo.Height); // bottom
+      else sY := 0; // top
+    end;
+  end;
+
+  p.viewPortX := sX;
+  p.viewPortY := sY;
+  p.viewPortW := sWidth;
+  p.viewPortH := sHeight;
+
+{$IFDEF ENABLE_HOLMES}
+  if (p = gPlayer1) then
+  begin
+    g_Holmes_plrViewPos(sX, sY);
+    g_Holmes_plrViewSize(sWidth, sHeight);
+  end;
+{$ENDIF}
+
+  renderMapInternal(-c, -d, true);
+
+  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;
+
+  {
+  for a := 0 to High(gCollideMap) do
+    for b := 0 to High(gCollideMap[a]) do
+    begin
+      d := 0;
+      if ByteBool(gCollideMap[a, b] and MARK_WALL) then
+        d := d + 1;
+      if ByteBool(gCollideMap[a, b] and MARK_DOOR) then
+        d := d + 2;
+
+      case d of
+        1: e_DrawPoint(1, b, a, 200, 200, 200);
+        2: e_DrawPoint(1, b, a, 64, 64, 255);
+        3: e_DrawPoint(1, b, a, 255, 0, 255);
+      end;
+    end;
+  }
+
+  glPopMatrix();
+
+  p.DrawPain();
+  p.DrawPickup();
+  p.DrawRulez();
+  if gShowMap then DrawMinimap(p, _TRect(0, 0, 128, 128));
+  if g_Debug_Player then
+    g_Player_DrawDebug(p);
+  p.DrawGUI();
+end;
+
+procedure drawProfilers ();
+var
+  px: Integer = -1;
+  py: Integer = -1;
+begin
+  if g_profile_frame_draw and (profileFrameDraw <> nil) then px := px-drawProfiles(px, py, profileFrameDraw);
+  if g_profile_collision and (profMapCollision <> nil) then begin px := px-drawProfiles(px, py, profMapCollision); py -= calcProfilesHeight(profMonsLOS); end;
+  if g_profile_los and (profMonsLOS <> nil) then begin px := px-drawProfiles(px, py, profMonsLOS); py -= calcProfilesHeight(profMonsLOS); end;
+end;
+
+procedure r_Game_Draw();
+var
+  ID: DWORD;
+  w, h: Word;
+  ww, hh: Byte;
+  Time: Int64;
+  back: string;
+  plView1, plView2: TPlayer;
+  Split: Boolean;
+begin
+  if gExit = EXIT_QUIT then Exit;
+
+  Time := sys_GetTicks() {div 1000};
+  FPSCounter := FPSCounter+1;
+  if Time - FPSTime >= 1000 then
+  begin
+    FPS := FPSCounter;
+    FPSCounter := 0;
+    FPSTime := Time;
+  end;
+
+  e_SetRendertarget(True);
+  e_SetViewPort(0, 0, gScreenWidth, gScreenHeight);
+
+  if gGameOn or (gState = STATE_FOLD) then
+  begin
+    if (gPlayer1 <> nil) and (gPlayer2 <> nil) then
+    begin
+      gSpectMode := SPECT_NONE;
+      if not gRevertPlayers then
+      begin
+        plView1 := gPlayer1;
+        plView2 := gPlayer2;
+      end
+      else
+      begin
+        plView1 := gPlayer2;
+        plView2 := gPlayer1;
+      end;
+    end
+    else
+      if (gPlayer1 <> nil) or (gPlayer2 <> nil) then
+      begin
+        gSpectMode := SPECT_NONE;
+        if gPlayer2 = nil then
+          plView1 := gPlayer1
+        else
+          plView1 := gPlayer2;
+        plView2 := nil;
+      end
+      else
+      begin
+        plView1 := nil;
+        plView2 := nil;
+      end;
+
+    if (plView1 = nil) and (plView2 = nil) and (gSpectMode = SPECT_NONE) then
+      gSpectMode := SPECT_STATS;
+
+    if gSpectMode = SPECT_PLAYERS then
+      if gPlayers <> nil then
+      begin
+        plView1 := GetActivePlayer_ByID(gSpectPID1);
+        if plView1 = nil then
+        begin
+          gSpectPID1 := GetActivePlayerID_Next();
+          plView1 := GetActivePlayer_ByID(gSpectPID1);
+        end;
+        if gSpectViewTwo then
+        begin
+          plView2 := GetActivePlayer_ByID(gSpectPID2);
+          if plView2 = nil then
+          begin
+            gSpectPID2 := GetActivePlayerID_Next();
+            plView2 := GetActivePlayer_ByID(gSpectPID2);
+          end;
+        end;
+      end;
+
+    if gSpectMode = SPECT_MAPVIEW then
+    begin
+    // Режим просмотра карты
+      Split := False;
+      e_SetViewPort(0, 0, gScreenWidth, gScreenHeight);
+      DrawMapView(gSpectX, gSpectY, gScreenWidth, gScreenHeight);
+      gHearPoint1.Active := True;
+      gHearPoint1.Coords.X := gScreenWidth div 2 + gSpectX;
+      gHearPoint1.Coords.Y := gScreenHeight div 2 + gSpectY;
+      gHearPoint2.Active := False;
+    end
+    else
+    begin
+      Split := (plView1 <> nil) and (plView2 <> nil);
+
+    // Точки слуха игроков
+      if plView1 <> nil then
+      begin
+        gHearPoint1.Active := True;
+        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 + PLAYER_RECT.Width;
+        gHearPoint2.Coords.Y := plView2.GameY + PLAYER_RECT.Height DIV 2;
+      end else
+        gHearPoint2.Active := False;
+
+    // Размер экранов игроков:
+      gPlayerScreenSize.X := gScreenWidth-196;
+      if Split then
+      begin
+        gPlayerScreenSize.Y := gScreenHeight div 2;
+        if gScreenHeight mod 2 = 0 then
+          Dec(gPlayerScreenSize.Y);
+      end
+      else
+        gPlayerScreenSize.Y := gScreenHeight;
+
+      if Split then
+        if gScreenHeight mod 2 = 0 then
+          e_SetViewPort(0, gPlayerScreenSize.Y+2, gPlayerScreenSize.X+196, gPlayerScreenSize.Y)
+        else
+          e_SetViewPort(0, gPlayerScreenSize.Y+1, gPlayerScreenSize.X+196, gPlayerScreenSize.Y);
+
+      DrawPlayer(plView1);
+      gPlayer1ScreenCoord.X := sX;
+      gPlayer1ScreenCoord.Y := sY;
+
+      if Split then
+      begin
+        e_SetViewPort(0, 0, gPlayerScreenSize.X+196, gPlayerScreenSize.Y);
+
+        DrawPlayer(plView2);
+        gPlayer2ScreenCoord.X := sX;
+        gPlayer2ScreenCoord.Y := sY;
+      end;
+
+      e_SetViewPort(0, 0, gScreenWidth, gScreenHeight);
+
+      if Split then
+        e_DrawLine(2, 0, gScreenHeight div 2, gScreenWidth, gScreenHeight div 2, 0, 0, 0);
+    end;
+
+{$IFDEF ENABLE_HOLMES}
+    // draw inspector
+    if (g_holmes_enabled) then g_Holmes_Draw();
+{$ENDIF}
+
+    if MessageText <> '' then
+    begin
+      w := 0;
+      h := 0;
+      e_CharFont_GetSizeFmt(gMenuFont, MessageText, w, h);
+      if Split then
+        e_CharFont_PrintFmt(gMenuFont, (gScreenWidth div 2)-(w div 2),
+                        (gScreenHeight div 2)-(h div 2), MessageText)
+      else
+        e_CharFont_PrintFmt(gMenuFont, (gScreenWidth div 2)-(w div 2),
+                  Round(gScreenHeight / 2.75)-(h div 2), MessageText);
+    end;
+
+    if IsDrawStat or (gSpectMode = SPECT_STATS) then
+      DrawStat();
+
+    if gSpectHUD and (not gChatShow) and (gSpectMode <> SPECT_NONE) and (not gSpectAuto) then
+    begin
+    // Draw spectator GUI
+      ww := 0;
+      hh := 0;
+      e_TextureFontGetSize(gStdFont, ww, hh);
+      case gSpectMode of
+        SPECT_STATS:
+          e_TextureFontPrintEx(0, gScreenHeight - (hh+2)*2, 'MODE: Stats', gStdFont, 255, 255, 255, 1);
+        SPECT_MAPVIEW:
+          e_TextureFontPrintEx(0, gScreenHeight - (hh+2)*2, 'MODE: Observe Map', gStdFont, 255, 255, 255, 1);
+        SPECT_PLAYERS:
+          e_TextureFontPrintEx(0, gScreenHeight - (hh+2)*2, 'MODE: Watch Players', gStdFont, 255, 255, 255, 1);
+      end;
+      e_TextureFontPrintEx(2*ww, gScreenHeight - (hh+2), '< jump >', gStdFont, 255, 255, 255, 1);
+      if gSpectMode = SPECT_STATS then
+      begin
+        e_TextureFontPrintEx(16*ww, gScreenHeight - (hh+2)*2, 'Autoview', gStdFont, 255, 255, 255, 1);
+        e_TextureFontPrintEx(16*ww, gScreenHeight - (hh+2), '< fire >', gStdFont, 255, 255, 255, 1);
+      end;
+      if gSpectMode = SPECT_MAPVIEW then
+      begin
+        e_TextureFontPrintEx(22*ww, gScreenHeight - (hh+2)*2, '[-]', gStdFont, 255, 255, 255, 1);
+        e_TextureFontPrintEx(26*ww, gScreenHeight - (hh+2)*2, 'Step ' + IntToStr(gSpectStep), gStdFont, 255, 255, 255, 1);
+        e_TextureFontPrintEx(34*ww, gScreenHeight - (hh+2)*2, '[+]', gStdFont, 255, 255, 255, 1);
+        e_TextureFontPrintEx(18*ww, gScreenHeight - (hh+2), '<prev weap>', gStdFont, 255, 255, 255, 1);
+        e_TextureFontPrintEx(30*ww, gScreenHeight - (hh+2), '<next weap>', gStdFont, 255, 255, 255, 1);
+      end;
+      if gSpectMode = SPECT_PLAYERS then
+      begin
+        e_TextureFontPrintEx(22*ww, gScreenHeight - (hh+2)*2, 'Player 1', gStdFont, 255, 255, 255, 1);
+        e_TextureFontPrintEx(20*ww, gScreenHeight - (hh+2), '<left/right>', gStdFont, 255, 255, 255, 1);
+        if gSpectViewTwo then
+        begin
+          e_TextureFontPrintEx(37*ww, gScreenHeight - (hh+2)*2, 'Player 2', gStdFont, 255, 255, 255, 1);
+          e_TextureFontPrintEx(34*ww, gScreenHeight - (hh+2), '<prev w/next w>', gStdFont, 255, 255, 255, 1);
+          e_TextureFontPrintEx(52*ww, gScreenHeight - (hh+2)*2, '2x View', gStdFont, 255, 255, 255, 1);
+          e_TextureFontPrintEx(51*ww, gScreenHeight - (hh+2), '<up/down>', gStdFont, 255, 255, 255, 1);
+        end
+        else
+        begin
+          e_TextureFontPrintEx(35*ww, gScreenHeight - (hh+2)*2, '2x View', gStdFont, 255, 255, 255, 1);
+          e_TextureFontPrintEx(34*ww, gScreenHeight - (hh+2), '<up/down>', gStdFont, 255, 255, 255, 1);
+        end;
+      end;
+    end;
+  end;
+
+  if gPauseMain and gGameOn and (g_ActiveWindow = nil) then
+  begin
+    //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
+    e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
+
+    e_CharFont_GetSize(gMenuFont, _lc[I_MENU_PAUSE], w, h);
+    e_CharFont_Print(gMenuFont, (gScreenWidth div 2)-(w div 2),
+                    (gScreenHeight div 2)-(h div 2), _lc[I_MENU_PAUSE]);
+  end;
+
+  if not gGameOn then
+  begin
+    if (gState = STATE_MENU) then
+    begin
+      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
+      begin
+        //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
+        e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
+      end
+      else
+      begin
+        // F3 at titlepic will show game loading dialog
+        if e_KeyPressed(IK_F3) then
+        begin
+          g_Menu_Show_LoadMenu(true);
+          if (g_ActiveWindow <> nil) then e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
+        end;
+      end;
+    end;
+
+    if gState = STATE_FOLD then
+    begin
+      e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 0, 0, 0, EndingGameCounter);
+    end;
+
+    if gState = STATE_INTERCUSTOM then
+    begin
+      if gLastMap and (gGameSettings.GameMode = GM_COOP) then
+      begin
+        back := 'TEXTURE_endpic';
+        if not g_Texture_Get(back, ID) then
+          back := _lc[I_TEXTURE_ENDPIC];
+      end
+      else
+        back := 'INTER';
+
+      DrawMenuBackground(back);
+
+      DrawCustomStat();
+
+      if g_ActiveWindow <> nil then
+      begin
+        //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
+        e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
+      end;
+    end;
+
+    if gState = STATE_INTERSINGLE then
+    begin
+      if EndingGameCounter > 0 then
+      begin
+        e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 0, 0, 0, EndingGameCounter);
+      end
+      else
+      begin
+        back := 'INTER';
+
+        DrawMenuBackground(back);
+
+        DrawSingleStat();
+
+        if g_ActiveWindow <> nil then
+        begin
+          //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
+          e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
+        end;
+      end;
+    end;
+
+    if gState = STATE_ENDPIC then
+    begin
+      ID := DWORD(-1);
+      if g_Texture_Get('TEXTURE_endpic', ID) then DrawMenuBackground('TEXTURE_endpic')
+      else DrawMenuBackground(_lc[I_TEXTURE_ENDPIC]);
+
+      if g_ActiveWindow <> nil then
+      begin
+        //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
+        e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
+      end;
+    end;
+
+    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);
+//      end;
+      DrawMenuBackground('MENU_BACKGROUND');
+      e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
+      g_Serverlist_Draw(slCurrent, slTable);
+    end;
+  end;
+
+  if g_ActiveWindow <> nil then
+  begin
+    if gGameOn then
+    begin
+      //e_DrawFillQuad(0, 0, gScreenWidth-1, gScreenHeight-1, 48, 48, 48, 180);
+      e_DarkenQuadWH(0, 0, gScreenWidth, gScreenHeight, 150);
+    end;
+    g_ActiveWindow.Draw();
+  end;
+
+{$IFNDEF HEADLESS}
+  r_Console_Draw();
+{$ENDIF}
+
+  if g_debug_Sounds and gGameOn then
+  begin
+    for w := 0 to High(e_SoundsArray) do
+      for h := 0 to e_SoundsArray[w].nRefs do
+        e_DrawPoint(1, w+100, h+100, 255, 0, 0);
+  end;
+
+  if gShowFPS then
+  begin
+    e_TextureFontPrint(0, 0, Format('FPS: %d', [FPS]), gStdFont);
+    e_TextureFontPrint(0, 16, Format('UPS: %d', [UPS]), gStdFont);
+  end;
+
+  if gGameOn and gShowTime then
+    drawTime(gScreenWidth-72, gScreenHeight-16);
+
+  if gGameOn then drawProfilers();
+
+  // TODO: draw this after the FBO and remap mouse click coordinates
+
+{$IFDEF ENABLE_HOLMES}
+  g_Holmes_DrawUI();
+{$ENDIF}
+
+  // blit framebuffer to screen
+
+  e_SetRendertarget(False);
+  e_SetViewPort(0, 0, gWinSizeX, gWinSizeY);
+  e_BlitFramebuffer(gWinSizeX, gWinSizeY);
+
+  // draw the overlay stuff on top of it
+
+  g_Touch_Draw;
+end;
+
+end.