DEADSOFTWARE

render: completely remove opengl calls form game code
[d2df-sdl.git] / src / game / g_game.pas
index 6b1e238f6f7b5928175a440d04d9b73a1923d865..058726b985185b32c201368d88c38e5013bdcb01 100644 (file)
@@ -20,7 +20,7 @@ interface
 uses
   SysUtils, Classes,
   MAPDEF,
-  g_basic, g_player, e_graphics, g_res_downloader,
+  g_base, g_basic, g_player, r_graphics, g_res_downloader,
   g_sound, g_gui, utils, md5, mempool, xprofiler,
   g_touch, g_weapons;
 
@@ -32,6 +32,7 @@ type
     GoalLimit: Word;
     WarmupTime: Word;
     SpawnInvul: Word;
+    ItemRespawnTime: Word;
     MaxLives: Byte;
     Options: LongWord;
     WAD: String;
@@ -83,7 +84,7 @@ procedure g_Game_Free (freeTextures: Boolean=true);
 procedure g_Game_LoadData();
 procedure g_Game_FreeData();
 procedure g_Game_Update();
-procedure g_Game_Draw();
+procedure g_Game_PreUpdate();
 procedure g_Game_Quit();
 procedure g_Game_SetupScreenSize();
 procedure g_Game_ChangeResolution(newWidth, newHeight: Word; nowFull, nowMax: Boolean);
@@ -132,6 +133,8 @@ procedure g_SimpleError(Text: String);
 function  g_Game_IsTestMap(): Boolean;
 procedure g_Game_DeleteTestMap();
 procedure GameCVars(P: SSArray);
+procedure PlayerSettingsCVars(P: SSArray);
+procedure SystemCommands(P: SSArray);
 procedure GameCommands(P: SSArray);
 procedure GameCheats(P: SSArray);
 procedure DebugCommands(P: SSArray);
@@ -140,8 +143,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); }
 
@@ -172,14 +177,17 @@ const
   EXIT_ENDLEVELSINGLE  = 4;
   EXIT_ENDLEVELCUSTOM  = 5;
 
-  GAME_OPTION_RESERVED     = 1;
-  GAME_OPTION_TEAMDAMAGE   = 2;
-  GAME_OPTION_ALLOWEXIT    = 4;
-  GAME_OPTION_WEAPONSTAY   = 8;
-  GAME_OPTION_MONSTERS     = 16;
-  GAME_OPTION_BOTVSPLAYER  = 32;
-  GAME_OPTION_BOTVSMONSTER = 64;
-  GAME_OPTION_DMKEYS       = 128;
+  GAME_OPTION_RESERVED          = 1;
+  GAME_OPTION_TEAMDAMAGE        = 2;
+  GAME_OPTION_ALLOWEXIT         = 4;
+  GAME_OPTION_WEAPONSTAY        = 8;
+  GAME_OPTION_MONSTERS          = 16;
+  GAME_OPTION_BOTVSPLAYER       = 32;
+  GAME_OPTION_BOTVSMONSTER      = 64;
+  GAME_OPTION_DMKEYS            = 128;
+  GAME_OPTION_TEAMHITTRACE      = 256;
+  GAME_OPTION_TEAMHITPROJECTILE = 512;
+  GAME_OPTION_TEAMABSORBDAMAGE = 1024;
 
   STATE_NONE        = 0;
   STATE_MENU        = 1;
@@ -237,6 +245,7 @@ var
   gPlayer2: TPlayer = nil;
   gPlayerDrawn: TPlayer = nil;
   gTime: LongWord;
+  gLerpFactor: Single = 1.0;
   gSwitchGameMode: Byte = GM_DM;
   gHearPoint1, gHearPoint2: THearPoint;
   gSoundEffectsDF: Boolean = False;
@@ -262,6 +271,7 @@ var
   gShowFPS: Boolean = False;
   gShowGoals: Boolean = True;
   gShowStat: Boolean = True;
+  gShowPIDs: Boolean = False;
   gShowKillMsg: Boolean = True;
   gShowLives: Boolean = True;
   gShowPing: Boolean = False;
@@ -370,65 +380,74 @@ 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
 
 uses
-{$INCLUDE ../nogl/noGLuses.inc}
 {$IFDEF ENABLE_HOLMES}
   g_holmes,
 {$ENDIF}
   e_texture, e_res, g_textures, g_window, g_menu,
-  e_input, e_log, g_console, g_items, g_map, g_panel,
+  e_input, e_log, g_console, r_console, g_items, g_map, g_panel,
   g_playermodel, g_gfx, g_options, Math,
   g_triggers, g_monsters, e_sound, CONFIG,
-  g_language, g_net, g_main,
+  g_language, g_net, g_main, g_phys,
   ENet, e_msg, g_netmsg, g_netmaster,
-  sfs, wadreader, g_system;
-
-
-var
-  hasPBarGfx: Boolean = false;
+  sfs, wadreader, g_system, r_playermodel;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
 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;
@@ -489,75 +508,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;
@@ -571,23 +537,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;
@@ -877,12 +834,10 @@ begin
   MegaWAD.endpic := cfg.ReadStr('megawad', 'endpic', '');
   if MegaWAD.endpic <> '' then
   begin
-    TEXTUREFILTER := GL_LINEAR;
     s := e_GetResourcePath(WadDirs, MegaWAD.endpic, WAD);
-    g_Texture_CreateWADEx('TEXTURE_endpic', s);
-    TEXTUREFILTER := GL_NEAREST;
+    g_Texture_CreateWADEx('TEXTURE_endpic', s, gTextureFilter);
   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);
@@ -973,7 +928,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;
@@ -990,16 +945,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
@@ -1008,7 +963,7 @@ begin
               gState := STATE_MENU;
             end else
             begin
-              // Îáíîâëÿåì ñïèñîê ñåðâåðîâ
+              // Обновляем список серверов
               slReturnPressed := True;
               if g_Net_Slist_Fetch(slCurrent) then
               begin
@@ -1024,14 +979,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;
@@ -1043,7 +998,7 @@ begin
 
         CustomStat.PlayerStat := nil;
 
-      // Ñòàòèñòèêà èãðîêîâ:
+      // Статистика игроков:
         if gPlayers <> nil then
         begin
           for a := 0 to High(gPlayers) do
@@ -1083,7 +1038,7 @@ begin
         if not g_Game_IsClient then g_Player_ResetReady;
         gInterReadyCount := 0;
 
-      // Çàòóõàþùèé ýêðàí:
+      // Затухающий экран:
         EndingGameCounter := 255;
         gState := STATE_FOLD;
         gInterTime := 0;
@@ -1093,16 +1048,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;
@@ -1111,7 +1066,7 @@ begin
 
         g_Game_ExecuteEvent('onmapend');
 
-      // Åñòü åùå êàðòû:
+      // Есть еще карты:
         if gNextMap <> '' then
           begin
             gMusic.SetByName('MUSIC_INTERMUS');
@@ -1121,235 +1076,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;
-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.GoalLimit]);
-      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.GoalLimit]);
-      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.GoalLimit]);
-      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].Goals),
-                           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;
-            // Èìÿ
-            e_TextureFontPrintEx(x+16, _y, Name, 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;
-        // Öâåò èãðîêà
-        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, Name, 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;
@@ -1365,12 +1105,10 @@ begin
   sfsGCDisable(); // temporary disable removing of temporary volumes
 
   try
-    TEXTUREFILTER := GL_LINEAR;
-    g_Texture_CreateWADEx('MENU_BACKGROUND', GameWAD+':TEXTURES\TITLE');
-    g_Texture_CreateWADEx('INTER', GameWAD+':TEXTURES\INTER');
-    g_Texture_CreateWADEx('ENDGAME_EN', GameWAD+':TEXTURES\ENDGAME_EN');
-    g_Texture_CreateWADEx('ENDGAME_RU', GameWAD+':TEXTURES\ENDGAME_RU');
-    TEXTUREFILTER := GL_NEAREST;
+    g_Texture_CreateWADEx('MENU_BACKGROUND', GameWAD+':TEXTURES\TITLE', gTextureFilter);
+    g_Texture_CreateWADEx('INTER', GameWAD+':TEXTURES\INTER', gTextureFilter);
+    g_Texture_CreateWADEx('ENDGAME_EN', GameWAD+':TEXTURES\ENDGAME_EN', gTextureFilter);
+    g_Texture_CreateWADEx('ENDGAME_RU', GameWAD+':TEXTURES\ENDGAME_RU', gTextureFilter);
 
     LoadStdFont('STDTXT', 'STDFONT', gStdFont);
     LoadFont('MENUTXT', 'MENUFONT', gMenuFont);
@@ -1381,10 +1119,11 @@ begin
     g_Game_SetLoadingText('', 0, False);
 
     g_Game_SetLoadingText(_lc[I_LOAD_CONSOLE], 0, False);
+    r_Console_Init;
     g_Console_Init();
 
     g_Game_SetLoadingText(_lc[I_LOAD_MODELS], 0, False);
-    g_PlayerModel_LoadData();
+    r_PlayerModel_Initialize;
 
     // load models from all possible wad types, in all known directories
     // this does a loosy job (linear search, ooph!), but meh
@@ -1490,25 +1229,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;
@@ -1626,13 +1346,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
@@ -1653,13 +1373,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;
@@ -1667,7 +1387,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);
@@ -1712,6 +1432,17 @@ begin
   MC_SEND_CheatRequest(NET_CHEAT_READY);
 end;
 
+procedure g_Game_PreUpdate();
+begin
+  // these are in separate PreUpdate functions because they can interact during Update()
+  // and are synced over the net
+  // we don't care that much about corpses and gibs
+  g_Player_PreUpdate();
+  g_Monsters_PreUpdate();
+  g_Items_PreUpdate();
+  g_Weapon_PreUpdate();
+end;
+
 procedure g_Game_Update();
 var
   Msg: g_gui.TMessage;
@@ -1751,10 +1482,10 @@ begin
   g_ResetDynlights();
   framePool.reset();
 
-// Ïîðà âûêëþ÷àòü èãðó:
+// Пора выключать игру:
   if gExit = EXIT_QUIT then
     Exit;
-// Èãðà çàêîí÷èëàñü - îáðàáàòûâàåì:
+// Игра закончилась - обрабатываем:
   if gExit <> 0 then
   begin
     EndGame();
@@ -1762,10 +1493,11 @@ begin
       Exit;
   end;
 
-  // ×èòàåì êëàâèàòóðó è äæîéñòèê, åñëè îêíî àêòèâíî
+  // Читаем клавиатуру и джойстик, если окно активно
   // no need to, as we'll do it in event handler
 
-// Îáíîâëÿåì êîíñîëü (äâèæåíèå è ñîîáùåíèÿ):
+// Обновляем консоль (движение и сообщения):
+  r_Console_Update;
   g_Console_Update();
 
   if (NetMode = NET_NONE) and (g_Game_IsNet) and (gGameOn or (gState in [STATE_FOLD, STATE_INTERCUSTOM])) then
@@ -1779,10 +1511,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
@@ -1810,19 +1542,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');
@@ -1830,7 +1562,7 @@ begin
                 gState := STATE_MENU;
               end else
               begin
-              // Ôèíàëüíàÿ êàðòèíêà:
+              // Финальная картинка:
                 g_Game_ExecuteEvent('onwadend');
                 g_Game_Free();
                 if not gMusic.SetByName('MUSIC_endmus') then
@@ -1865,11 +1597,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
                 InterReadyTime := -1;
@@ -1886,7 +1618,7 @@ begin
                 gState := STATE_INTERCUSTOM;
                 e_UnpressAllKeys();
               end
-            else // Çàêîí÷èëàñü ïîñëåäíÿÿ êàðòà â Îäèíî÷íîé èãðå
+            else // Закончилась последняя карта в Одиночной игре
               begin
                 gMusic.SetByName('MUSIC_INTERMUS');
                 gMusic.Play();
@@ -1899,9 +1631,9 @@ begin
           DecMin(EndingGameCounter, 6, 0);
       end;
 
-    STATE_ENDPIC: // Êàðòèíêà îêîí÷àíèÿ ìåãàÂàäà
+    STATE_ENDPIC: // Картинка окончания мегаВада
       begin
-        if gMapOnce then // Ýòî áûë òåñò
+        if gMapOnce then // Это был тест
         begin
           gExit := EXIT_SIMPLE;
           Exit;
@@ -1912,17 +1644,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
@@ -1930,19 +1662,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
@@ -1952,19 +1684,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.GoalLimit > 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
@@ -1972,11 +1704,11 @@ begin
           end
         else
           if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
-          begin // Â CTF/TDM âûáèðàåì êîìàíäó ñ íàèáîëüøèì ñ÷åòîì
+          begin // В CTF/TDM выбираем команду с наибольшим счетом
             b := Max(gTeamStat[TEAM_RED].Goals, gTeamStat[TEAM_BLUE].Goals);
           end;
 
-      // Ëèìèò ïîáåä íàáðàí => êîíåö óðîâíÿ:
+      // Лимит побед набран => конец уровня:
         if b >= gGameSettings.GoalLimit then
         begin
           g_Game_NextLevel();
@@ -1984,7 +1716,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
@@ -1999,7 +1731,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
@@ -2158,7 +1890,7 @@ begin
       end;
     end;
 
-  // Îáíîâëÿåì âñå îñòàëüíîå:
+  // Обновляем все остальное:
     g_Map_Update();
     g_Items_Update();
     g_Triggers_Update();
@@ -2244,7 +1976,7 @@ begin
     end;
   end; // if gameOn ...
 
-// Àêòèâíî îêíî èíòåðôåéñà - ïåðåäàåì êëàâèøè åìó:
+// Активно окно интерфейса - передаем клавиши ему:
   if g_ActiveWindow <> nil then
   begin
     w := e_GetFirstKeyPressed();
@@ -2256,11 +1988,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);
@@ -2269,7 +2001,7 @@ begin
       g_ActiveWindow := nil;
     end;
 
-  // Íóæíî ñìåíèòü ÿçûê:
+  // Нужно сменить язык:
     if gLanguageChange then
     begin
       //e_WriteLog('Read language file', MSG_NOTIFY);
@@ -2282,7 +2014,7 @@ begin
     end;
   end;
 
-// Ãîðÿ÷àÿ êëàâèøà äëÿ âûçîâà ìåíþ âûõîäà èç èãðû (F10):
+// Горячая клавиша для вызова меню выхода из игры (F10):
   if e_KeyPressed(IK_F10) and
      gGameOn and
      (not gConsoleShow) and
@@ -2293,7 +2025,7 @@ begin
 
   Time := sys_GetTicks() {div 1000};
 
-// Îáðàáîòêà îòëîæåííûõ ñîáûòèé:
+// Обработка отложенных событий:
   if gDelayedEvents <> nil then
     for a := 0 to High(gDelayedEvents) do
       if gDelayedEvents[a].Pending and
@@ -2322,7 +2054,7 @@ begin
         gDelayedEvents[a].Pending := False;
       end;
 
-// Êàæäóþ ñåêóíäó îáíîâëÿåì ñ÷åò÷èê îáíîâëåíèé:
+// Каждую секунду обновляем счетчик обновлений:
   UPSCounter := UPSCounter + 1;
   if Time - UPSTime >= 1000 then
   begin
@@ -2543,6 +2275,29 @@ begin
   DataLoaded := True;
 end;
 
+procedure g_Game_Quit();
+begin
+  g_Game_StopAllSounds(True);
+  gMusic.Free();
+  g_Game_FreeData();
+  r_PlayerModel_Finalize;
+  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;
@@ -2636,1638 +2391,87 @@ 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;
+procedure g_FatalError(Text: String);
 begin
-  e_TextureFontGetSize(gStdFont, ww2, hh2);
-
-  sys_HandleInput;
+  g_Console_Add(Format(_lc[I_FATAL_ERROR], [Text]), True);
+  e_WriteLog(Format(_lc[I_FATAL_ERROR], [Text]), TMsgType.Warning);
 
-  if g_Console_Action(ACTION_SCORES) then
-  begin
-    if not gStatsPressed then
-    begin
-      gStatsOff := not gStatsOff;
-      gStatsPressed := True;
-    end;
-  end
-  else
-    gStatsPressed := False;
+  gExit := EXIT_SIMPLE;
+  if gGameOn then EndGame;
+end;
 
-  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;
+procedure g_SimpleError(Text: String);
+begin
+  g_Console_Add(Format(_lc[I_SIMPLE_ERROR], [Text]), True);
+  e_WriteLog(Format(_lc[I_SIMPLE_ERROR], [Text]), TMsgType.Warning);
+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
+procedure g_Game_SetupScreenSize();
+const
+  RES_FACTOR = 4.0 / 3.0;
+var
+  s: Single;
+  rf: Single;
+  bw, bh: Word;
+begin
+// Размер экранов игроков:
+  gPlayerScreenSize.X := gScreenWidth-196;
+  if (gPlayer1 <> nil) and (gPlayer2 <> nil) then
+    gPlayerScreenSize.Y := gScreenHeight div 2
   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);
+    gPlayerScreenSize.Y := gScreenHeight;
 
-  if g_Game_IsNet then
+// Размер заднего плана:
+  if BackID <> DWORD(-1) 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:
+    s := SKY_STRETCH;
+    if (gScreenWidth*s > gMapInfo.Width) or
+       (gScreenHeight*s > gMapInfo.Height) then
     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:
+      gBackSize.X := gScreenWidth;
+      gBackSize.Y := gScreenHeight;
+    end
+    else
     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);
+      e_GetTextureSize(BackID, @bw, @bh);
+      rf := Single(bw) / Single(bh);
+      if (rf > RES_FACTOR) then bw := Round(Single(bh) * RES_FACTOR)
+      else if (rf < RES_FACTOR) then bh := Round(Single(bw) / RES_FACTOR);
+      s := Max(gScreenWidth / bw, gScreenHeight / bh);
+      if (s < 1.0) then s := 1.0;
+      gBackSize.X := Round(bw*s);
+      gBackSize.Y := Round(bh*s);
     end;
   end;
+end;
 
-  if CustomStat.GameMode in [GM_TDM, GM_CTF] then
-  begin
-    _y := _y+16+16;
+procedure g_Game_ChangeResolution(newWidth, newHeight: Word; nowFull, nowMax: Boolean);
+begin
+  sys_SetDisplayMode(newWidth, newHeight, gBPP, nowFull, nowMax);
+end;
 
-    with CustomStat do
-      if TeamStat[TEAM_RED].Goals > TeamStat[TEAM_BLUE].Goals then s1 := _lc[I_GAME_WIN_RED]
-        else if TeamStat[TEAM_BLUE].Goals > TeamStat[TEAM_RED].Goals then s1 := _lc[I_GAME_WIN_BLUE]
-          else s1 := _lc[I_GAME_WIN_DRAW];
+procedure g_Game_AddPlayer(Team: Byte = TEAM_NONE);
+begin
+  if ((not gGameOn) and (gState <> STATE_INTERCUSTOM))
+  or (not (gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT])) then
+    Exit;
 
-    e_TextureFontPrintEx(x+8+(w div 2)-(Length(s1)*ww2 div 2), _y, s1, gStdFont, 255, 255, 255, 1);
-    _y := _y+40;
+  if (gGameSettings.MaxLives > 0) and (gLMSRespawn = LMS_RESPAWN_NONE) then
+    Exit;
 
-    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].Goals),
-                             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].Goals),
-                             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: Integer;
-  //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();
-
-  px := p.GameX + PLAYER_RECT_CX;
-  py := p.GameY + PLAYER_RECT_CY+p.Obj.slopeUpLeft;
-
-  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 - p.IncCam;
-
-  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;
-
-  if p.FSpectator then
-    e_TextureFontPrintEx(p.GameX + PLAYER_RECT_CX - 4,
-                         p.GameY + PLAYER_RECT_CY - 4,
-                         'X', gStdFont, 255, 255, 255, 1, True);
-  {
-  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;
-
-  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 = 1) 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}
-  g_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();
-
-{$IFDEF ENABLE_HOLMES}
-  g_Holmes_DrawUI();
-{$ENDIF}
-
-  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);
-  e_WriteLog(Format(_lc[I_FATAL_ERROR], [Text]), TMsgType.Warning);
-
-  gExit := EXIT_SIMPLE;
-end;
-
-procedure g_SimpleError(Text: String);
-begin
-  g_Console_Add(Format(_lc[I_SIMPLE_ERROR], [Text]), True);
-  e_WriteLog(Format(_lc[I_SIMPLE_ERROR], [Text]), TMsgType.Warning);
-end;
-
-procedure g_Game_SetupScreenSize();
-const
-  RES_FACTOR = 4.0 / 3.0;
-var
-  s: Single;
-  rf: Single;
-  bw, bh: Word;
-begin
-// Ðàçìåð ýêðàíîâ èãðîêîâ:
-  gPlayerScreenSize.X := gScreenWidth-196;
-  if (gPlayer1 <> nil) and (gPlayer2 <> nil) then
-    gPlayerScreenSize.Y := gScreenHeight div 2
-  else
-    gPlayerScreenSize.Y := gScreenHeight;
-
-// Ðàçìåð çàäíåãî ïëàíà:
-  if BackID <> DWORD(-1) then
-  begin
-    s := SKY_STRETCH;
-    if (gScreenWidth*s > gMapInfo.Width) or
-       (gScreenHeight*s > gMapInfo.Height) then
-    begin
-      gBackSize.X := gScreenWidth;
-      gBackSize.Y := gScreenHeight;
-    end
-    else
-    begin
-      e_GetTextureSize(BackID, @bw, @bh);
-      rf := Single(bw) / Single(bh);
-      if (rf > RES_FACTOR) then bw := Round(Single(bh) * RES_FACTOR)
-      else if (rf < RES_FACTOR) then bh := Round(Single(bw) / RES_FACTOR);
-      s := Max(gScreenWidth / bw, gScreenHeight / bh);
-      if (s < 1.0) then s := 1.0;
-      gBackSize.X := Round(bw*s);
-      gBackSize.Y := Round(bh*s);
-    end;
-  end;
-end;
-
-procedure g_Game_ChangeResolution(newWidth, newHeight: Word; nowFull, nowMax: Boolean);
-begin
-  sys_SetDisplayMode(newWidth, newHeight, gBPP, nowFull, nowMax);
-end;
-
-procedure g_Game_AddPlayer(Team: Byte = TEAM_NONE);
-begin
-  if ((not gGameOn) and (gState <> STATE_INTERCUSTOM))
-  or (not (gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT])) then
-    Exit;
   if gPlayer1 = nil then
   begin
     if g_Game_IsClient then
     begin
       if NetPlrUID1 > -1 then
-      begin
         MC_SEND_CheatRequest(NET_CHEAT_SPECTATE);
-        gPlayer1 := g_Player_Get(NetPlrUID1);
-      end;
       Exit;
     end;
 
     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));
@@ -4297,7 +2501,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));
@@ -4334,8 +2538,12 @@ begin
       g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [Pl.Name]), True);
       g_Player_Remove(Pl.UID);
       g_Net_Slist_ServerPlayerLeaves();
-    end else
+    end
+    else
+    begin
+      gSpectLatchPID2 := Pl.UID;
       gPlayer2 := nil;
+    end;
     Exit;
   end;
   Pl := gPlayer1;
@@ -4350,6 +2558,7 @@ begin
       g_Net_Slist_ServerPlayerLeaves();
     end else
     begin
+      gSpectLatchPID1 := Pl.UID;
       gPlayer1 := nil;
       MC_SEND_CheatRequest(NET_CHEAT_SPECTATE);
     end;
@@ -4382,7 +2591,7 @@ begin
 
   g_Game_ClearLoading();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   FillByte(gGameSettings, SizeOf(TGameSettings), 0);
   gAimLine := False;
   gShowMap := False;
@@ -4391,14 +2600,21 @@ begin
   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_ALLOWEXIT;
   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_MONSTERS;
   gGameSettings.Options := gGameSettings.Options + GAME_OPTION_BOTVSMONSTER;
+  gGameSettings.Options := gGameSettings.Options + GAME_OPTION_TEAMHITPROJECTILE;
+  gGameSettings.Options := gGameSettings.Options + GAME_OPTION_TEAMHITTRACE;
   gSwitchGameMode := GM_SINGLE;
 
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
+
   g_Game_ExecuteEvent('ongamestart');
 
-// Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
+// Установка размеров окон игроков:
   g_Game_SetupScreenSize();
 
-// Ñîçäàíèå ïåðâîãî èãðîêà:
+// Создание первого игрока:
   gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                            gPlayer1Settings.Color,
                                            gPlayer1Settings.Team, False));
@@ -4411,7 +2627,7 @@ begin
   gPlayer1.Name := gPlayer1Settings.Name;
   nPl := 1;
 
-// Ñîçäàíèå âòîðîãî èãðîêà, åñëè åñòü:
+// Создание второго игрока, если есть:
   if TwoPlayers then
   begin
     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
@@ -4427,7 +2643,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;
@@ -4435,10 +2651,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;
@@ -4456,7 +2672,7 @@ begin
 
   g_Game_ClearLoading();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   gGameSettings.GameType := GT_CUSTOM;
   gGameSettings.GameMode := GameMode;
   gSwitchGameMode := GameMode;
@@ -4472,12 +2688,17 @@ begin
   gAimLine := False;
   gShowMap := False;
 
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
+
   g_Game_ExecuteEvent('ongamestart');
 
-// Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
+// Установка размеров окон игроков:
   g_Game_SetupScreenSize();
 
-// Ðåæèì íàáëþäàòåëÿ:
+// Ð ÐµÐ¶Ð¸Ð¼ Ð½Ð°Ð±Ð»Ñ\8eдаÑ\82елÑ\8f:
   if nPlayers = 0 then
   begin
     gPlayer1 := nil;
@@ -4487,7 +2708,7 @@ begin
   nPl := 0;
   if nPlayers >= 1 then
   begin
-  // Ñîçäàíèå ïåðâîãî èãðîêà:
+  // Создание первого игрока:
     gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                              gPlayer1Settings.Color,
                                              gPlayer1Settings.Team, False));
@@ -4503,7 +2724,7 @@ begin
 
   if nPlayers >= 2 then
   begin
-  // Ñîçäàíèå âòîðîãî èãðîêà:
+  // Создание второго игрока:
     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
                                              gPlayer2Settings.Color,
                                              gPlayer2Settings.Team, False));
@@ -4517,14 +2738,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) +
@@ -4535,10 +2756,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;
@@ -4555,7 +2776,7 @@ begin
 
   g_Game_ClearLoading();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   gGameSettings.GameType := GT_SERVER;
   gGameSettings.GameMode := GameMode;
   gSwitchGameMode := GameMode;
@@ -4571,12 +2792,17 @@ begin
   gAimLine := False;
   gShowMap := False;
 
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
+
   g_Game_ExecuteEvent('ongamestart');
 
-// Óñòàíîâêà ðàçìåðîâ îêíà èãðîêà
+// Установка размеров окна игрока
   g_Game_SetupScreenSize();
 
-// Ðåæèì íàáëþäàòåëÿ:
+// Ð ÐµÐ¶Ð¸Ð¼ Ð½Ð°Ð±Ð»Ñ\8eдаÑ\82елÑ\8f:
   if nPlayers = 0 then
   begin
     gPlayer1 := nil;
@@ -4585,7 +2811,7 @@ begin
 
   if nPlayers >= 1 then
   begin
-  // Ñîçäàíèå ïåðâîãî èãðîêà:
+  // Создание первого игрока:
     gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                              gPlayer1Settings.Color,
                                              gPlayer1Settings.Team, False));
@@ -4600,7 +2826,7 @@ begin
 
   if nPlayers >= 2 then
   begin
-  // Ñîçäàíèå âòîðîãî èãðîêà:
+  // Создание второго игрока:
     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
                                              gPlayer2Settings.Color,
                                              gPlayer2Settings.Team, False));
@@ -4617,18 +2843,18 @@ 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]));
     Exit;
   end;
 
-  g_Net_Slist_Set(NetSlistIP, NetSlistPort, NetSlistList);
+  g_Net_Slist_Set(NetMasterList);
 
   g_Net_Slist_ServerStarted();
 
-// Çàãðóçêà è çàïóñê êàðòû:
+// Загрузка и запуск карты:
   if not g_Game_StartMap(false{asMegawad}, Map, True) then
   begin
     g_Net_Slist_ServerClosed();
@@ -4636,7 +2862,7 @@ begin
     Exit;
   end;
 
-// Íåò òî÷åê ïîÿâëåíèÿ:
+// Нет точек появления:
   if (g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) +
       g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) +
       g_Map_GetPointCount(RESPAWNPOINT_DM) +
@@ -4648,7 +2874,7 @@ begin
     Exit;
   end;
 
-// Íàñòðîéêè èãðîêîâ è áîòîâ:
+// Настройки игроков и ботов:
   g_Player_Init();
 
   g_Net_Slist_ServerMapStarted();
@@ -4675,7 +2901,7 @@ begin
 
   g_Game_ClearLoading();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   gGameSettings.GameType := GT_CLIENT;
 
   gCoopTotalMonstersKilled := 0;
@@ -4687,7 +2913,7 @@ begin
 
   g_Game_ExecuteEvent('ongamestart');
 
-// Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
+// Установка размеров окон игроков:
   g_Game_SetupScreenSize();
 
   NetState := NET_STATE_AUTH;
@@ -4697,7 +2923,12 @@ begin
   // create (or update) map/resource databases
   g_Res_CreateDatabases(true);
 
-// Ñòàðòóåì êëèåíò
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
+
+// Стартуем клиент
   if not g_Net_Connect(Addr, Port) then
   begin
     g_FatalError(_lc[I_NET_MSG] + _lc[I_NET_ERR_CONN]);
@@ -4836,9 +3067,6 @@ begin
     Exit;
   end;
 
-  gLMSRespawn := LMS_RESPAWN_NONE;
-  gLMSRespawnTime := 0;
-
   g_Player_Init();
   NetState := NET_STATE_GAME;
   MC_SEND_FullStateRequest;
@@ -4855,7 +3083,7 @@ begin
   g_Game_ClearLoading();
 
   Force := gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF];
-  // Åñëè óðîâåíü çàâåðøèëñÿ ïî òðèããåðó Âûõîä, íå î÷èùàòü èíâåíòàðü
+  // Если уровень завершился по триггеру Выход, не очищать инвентарь
   if gExitByTrigger then
   begin
     Force := False;
@@ -4972,7 +3200,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;
@@ -4989,8 +3217,8 @@ begin
   NetTimeToUpdate := 1;
   NetTimeToReliable := 0;
   NetTimeToMaster := NetMasterRate;
-  gLMSRespawn := LMS_RESPAWN_NONE;
-  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
   gMissionFailed := False;
   gNextMap := '';
 
@@ -5008,22 +3236,28 @@ begin
 
   g_Game_SpectateCenterView();
 
-  if (gGameSettings.MaxLives > 0) and (gGameSettings.WarmupTime > 0) then
+  if g_Game_IsServer then
   begin
-    gLMSRespawn := LMS_RESPAWN_WARMUP;
-    gLMSRespawnTime := gTime + gGameSettings.WarmupTime*1000;
-    gLMSSoftSpawn := True;
-    if NetMode = NET_SERVER then
-      MH_SEND_GameEvent(NET_EV_LMS_WARMUP, (gLMSRespawnTime - gTime) div 1000)
+    if (gGameSettings.MaxLives > 0) and (gGameSettings.WarmupTime > 0) then
+    begin
+      gLMSRespawn := LMS_RESPAWN_WARMUP;
+      gLMSRespawnTime := gTime + gGameSettings.WarmupTime*1000;
+      gLMSSoftSpawn := True;
+      if g_Game_IsNet then
+        MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime);
+    end
     else
-      g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
+    begin
+      gLMSRespawn := LMS_RESPAWN_NONE;
+      gLMSRespawnTime := 0;
+    end;
   end;
 
   if NetMode = NET_SERVER then
   begin
     MH_SEND_GameEvent(NET_EV_MAPSTART, gGameSettings.GameMode, Map);
 
-  // Ìàñòåðñåðâåð
+  // Мастерсервер
     g_Net_Slist_ServerMapStarted();
 
     if NetClients <> nil then
@@ -5076,10 +3310,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
@@ -5165,13 +3399,6 @@ end;
 procedure g_Game_RestartRound(NoMapRestart: Boolean = False);
 var
   i, n, nb, nr: Integer;
-
-  function monRespawn (mon: TMonster): Boolean;
-  begin
-    result := false; // don't stop
-    if not mon.FNoRespawn then mon.Respawn();
-  end;
-
 begin
   if not g_Game_IsServer then Exit;
   if gLMSRespawn = LMS_RESPAWN_NONE then Exit;
@@ -5197,12 +3424,14 @@ begin
         else if gPlayers[i].Team = TEAM_BLUE then Inc(nb)
       end;
 
-  if (n < 2) or ((gGameSettings.GameMode = GM_TDM) and ((nr = 0) or (nb = 0))) then
+  if (n < 1) or ((gGameSettings.GameMode = GM_TDM) and ((nr = 0) or (nb = 0))) then
   begin
     // wait a second until the fuckers finally decide to join
     gLMSRespawn := LMS_RESPAWN_WARMUP;
-    gLMSRespawnTime := gTime + 1000;
+    gLMSRespawnTime := gTime + gGameSettings.WarmupTime*1000;
     gLMSSoftSpawn := NoMapRestart;
+    if g_Game_IsNet then
+      MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime);
     Exit;
   end;
 
@@ -5232,17 +3461,14 @@ begin
       gPlayers[i].Frags := 0;
       gPlayers[i].RecallState;
     end;
-    if (gPlayer1 = nil) and (gLMSPID1 > 0) then
-      gPlayer1 := g_Player_Get(gLMSPID1);
-    if (gPlayer2 = nil) and (gLMSPID2 > 0) then
-      gPlayer2 := g_Player_Get(gLMSPID2);
+    if (gPlayer1 = nil) and (gSpectLatchPID1 > 0) then
+      gPlayer1 := g_Player_Get(gSpectLatchPID1);
+    if (gPlayer2 = nil) and (gSpectLatchPID2 > 0) then
+      gPlayer2 := g_Player_Get(gSpectLatchPID2);
   end;
 
   g_Items_RestartRound();
 
-
-  g_Mons_ForEach(monRespawn);
-
   gLMSSoftSpawn := False;
 end;
 
@@ -5332,7 +3558,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);
@@ -5340,7 +3566,7 @@ begin
   //CopyMemory(@MapName[0], @gMapToDelete[1], Min(16, Length(gMapToDelete)));
 
 {
-// Èìÿ êàðòû íå ñòàíäàðòíîå òåñòîâîå:
+// Имя карты не стандартное тестовое:
   if MapName <> TEST_MAP_NAME then
     Exit;
 
@@ -5349,14 +3575,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('');
 
@@ -5364,7 +3590,7 @@ begin
       for a := 0 to High(MapList) do
         if MapList[a] = MapName then
         begin
-        // Óäàëÿåì è ñîõðàíÿåì:
+        // Удаляем и сохраняем:
           WAD.RemoveResource('', MapName);
           WAD.SaveTo(WadName);
           Break;
@@ -5381,65 +3607,62 @@ procedure GameCVars(P: SSArray);
 var
   a, b: Integer;
   stat: TPlayerStatArray;
-  cmd, s: string;
-begin
-  stat := nil;
-  cmd := LowerCase(P[0]);
-  if (cmd = 'g_friendlyfire') and not g_Game_IsClient then
+  cmd: string;
+
+  procedure ParseGameFlag(Flag: LongWord; OffMsg, OnMsg: TStrings_Locale; OnMapChange: Boolean = False);
+  var
+    x: Boolean;
   begin
-    with gGameSettings do
+    if Length(P) > 1 then
     begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
-      begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_TEAMDAMAGE
-        else
-          Options := Options and (not GAME_OPTION_TEAMDAMAGE);
-      end;
+      x := P[1] = '1';
 
-      if (LongBool(Options and GAME_OPTION_TEAMDAMAGE)) then
-        g_Console_Add(_lc[I_MSG_FRIENDLY_FIRE_ON])
+      if x then
+        gsGameFlags := gsGameFlags or Flag
       else
-        g_Console_Add(_lc[I_MSG_FRIENDLY_FIRE_OFF]);
+        gsGameFlags := gsGameFlags and (not Flag);
 
-      if g_Game_IsNet then MH_SEND_GameSettings;
-    end;
-  end
-  else if (cmd = 'g_weaponstay') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+      if g_Game_IsServer then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_WEAPONSTAY
+        if x then
+          gGameSettings.Options := gGameSettings.Options or Flag
         else
-          Options := Options and (not GAME_OPTION_WEAPONSTAY);
+          gGameSettings.Options := gGameSettings.Options and (not Flag);
+        if g_Game_IsNet then MH_SEND_GameSettings;
       end;
+    end;
 
-      if (LongBool(Options and GAME_OPTION_WEAPONSTAY)) then
-        g_Console_Add(_lc[I_MSG_WEAPONSTAY_ON])
-      else
-        g_Console_Add(_lc[I_MSG_WEAPONSTAY_OFF]);
+    if LongBool(gsGameFlags and Flag) then
+      g_Console_Add(_lc[OnMsg])
+    else
+      g_Console_Add(_lc[OffMsg]);
 
-      if g_Game_IsNet then MH_SEND_GameSettings;
-    end;
-  end
-  else if cmd = 'g_gamemode' then
+    if OnMapChange and g_Game_IsServer then
+      g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
+  end;
+
+begin
+  stat := nil;
+  cmd := LowerCase(P[0]);
+
+  if cmd = 'g_gamemode' then
   begin
-    a := g_Game_TextToMode(P[1]);
-    if a = GM_SINGLE then a := GM_COOP;
-    if (Length(P) > 1) and (a <> GM_NONE) and (not g_Game_IsClient) then
+    if (Length(P) > 1) then
     begin
-      gSwitchGameMode := a;
-      if (gGameOn and (gGameSettings.GameMode = GM_SINGLE)) or
-         (gState = STATE_INTERSINGLE) then
-        gSwitchGameMode := GM_SINGLE;
-      if not gGameOn then
-        gGameSettings.GameMode := gSwitchGameMode;
+      a := g_Game_TextToMode(P[1]);
+      if a = GM_SINGLE then a := GM_COOP;
+      gsGameMode := g_Game_ModeToText(a);
+      if g_Game_IsServer then
+      begin
+        gSwitchGameMode := a;
+        if (gGameOn and (gGameSettings.GameMode = GM_SINGLE)) or
+           (gState = STATE_INTERSINGLE) then
+          gSwitchGameMode := GM_SINGLE;
+        if not gGameOn then
+          gGameSettings.GameMode := gSwitchGameMode;
+      end;
     end;
+
     if gSwitchGameMode = gGameSettings.GameMode then
       g_Console_Add(Format(_lc[I_MSG_GAMEMODE_CURRENT],
                           [g_Game_ModeToText(gGameSettings.GameMode)]))
@@ -5448,455 +3671,378 @@ begin
                           [g_Game_ModeToText(gGameSettings.GameMode),
                            g_Game_ModeToText(gSwitchGameMode)]));
   end
-  else if (cmd = 'g_allow_exit') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
-      begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_ALLOWEXIT
-        else
-          Options := Options and (not GAME_OPTION_ALLOWEXIT);
-      end;
-
-      if (LongBool(Options and GAME_OPTION_ALLOWEXIT)) then
-        g_Console_Add(_lc[I_MSG_ALLOWEXIT_ON])
-      else
-        g_Console_Add(_lc[I_MSG_ALLOWEXIT_OFF]);
-      g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
-
-      if g_Game_IsNet then MH_SEND_GameSettings;
-    end;
-  end
-  else if (cmd = 'g_allow_monsters') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
-      begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_MONSTERS
-        else
-          Options := Options and (not GAME_OPTION_MONSTERS);
-      end;
-
-      if (LongBool(Options and GAME_OPTION_MONSTERS)) then
-        g_Console_Add(_lc[I_MSG_ALLOWMON_ON])
-      else
-        g_Console_Add(_lc[I_MSG_ALLOWMON_OFF]);
-      g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
-
-      if g_Game_IsNet then MH_SEND_GameSettings;
-    end;
-  end
-  else if (cmd = 'g_bot_vsplayers') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
-      begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_BOTVSPLAYER
-        else
-          Options := Options and (not GAME_OPTION_BOTVSPLAYER);
-      end;
-
-      if (LongBool(Options and GAME_OPTION_BOTVSPLAYER)) then
-        g_Console_Add(_lc[I_MSG_BOTSVSPLAYERS_ON])
-      else
-        g_Console_Add(_lc[I_MSG_BOTSVSPLAYERS_OFF]);
-
-      if g_Game_IsNet then MH_SEND_GameSettings;
-    end;
-  end
-  else if (cmd = 'g_bot_vsmonsters') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
-      begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_BOTVSMONSTER
-        else
-          Options := Options and (not GAME_OPTION_BOTVSMONSTER);
-      end;
-
-      if (LongBool(Options and GAME_OPTION_BOTVSMONSTER)) then
-        g_Console_Add(_lc[I_MSG_BOTSVSMONSTERS_ON])
-      else
-        g_Console_Add(_lc[I_MSG_BOTSVSMONSTERS_OFF]);
-
-      if g_Game_IsNet then MH_SEND_GameSettings;
-    end;
-  end
-  else if (cmd = 'g_dm_keys') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
-      begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_DMKEYS
-        else
-          Options := Options and (not GAME_OPTION_DMKEYS);
-      end;
-
-      if (LongBool(Options and GAME_OPTION_DMKEYS)) then
-        g_Console_Add(_lc[I_MSG_DMKEYS_ON])
-      else
-        g_Console_Add(_lc[I_MSG_DMKEYS_OFF]);
-
-      if g_Game_IsNet then MH_SEND_GameSettings;
-    end;
-  end
-  else if (cmd = 'g_warmuptime') and not g_Game_IsClient then
-  begin
-    if Length(P) > 1 then
-    begin
-      if StrToIntDef(P[1], gGameSettings.WarmupTime) = 0 then
-        gGameSettings.WarmupTime := 30
-      else
-        gGameSettings.WarmupTime := StrToIntDef(P[1], gGameSettings.WarmupTime);
-    end;
-
-    g_Console_Add(Format(_lc[I_MSG_WARMUP],
-                 [gGameSettings.WarmupTime]));
-    g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
-  end
-  else if (cmd = 'g_spawn_invul') and not g_Game_IsClient then
-  begin
-    if Length(P) > 1 then
-    begin
-      if StrToIntDef(P[1], gGameSettings.SpawnInvul) = 0 then
-        gGameSettings.SpawnInvul := 0
-      else
-        gGameSettings.SpawnInvul := StrToIntDef(P[1], gGameSettings.SpawnInvul);
-    end;
-
-    g_Console_Add(Format(_lc[I_MSG_SPAWNINVUL],
-                 [gGameSettings.SpawnInvul]));
-    g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
-  end
-  else if cmd = 'net_interp' then
+  else if cmd = 'g_friendlyfire' then
   begin
-    if (Length(P) > 1) then
-      NetInterpLevel := StrToIntDef(P[1], NetInterpLevel);
-    g_Console_Add('net_interp = ' + IntToStr(NetInterpLevel));
+    ParseGameFlag(GAME_OPTION_TEAMDAMAGE, I_MSG_FRIENDLY_FIRE_OFF, I_MSG_FRIENDLY_FIRE_ON);
   end
-  else if cmd = 'net_forceplayerupdate' then
+  else if cmd = 'g_friendly_absorb_damage' then
   begin
-    if (Length(P) > 1) and ((P[1] = '1') or (P[1] = '0')) then
-      NetForcePlayerUpdate := (P[1][1] = '1');
-
-    if NetForcePlayerUpdate then
-      g_Console_Add('net_forceplayerupdate = 1')
-    else
-      g_Console_Add('net_forceplayerupdate = 0');
+    ParseGameFlag(GAME_OPTION_TEAMABSORBDAMAGE, I_MSG_FRIENDLY_FIRE_OFF, I_MSG_FRIENDLY_FIRE_ON);
   end
-  else if cmd = 'net_predictself' then
+  else if cmd = 'g_friendly_hit_trace' then
   begin
-    if (Length(P) > 1) and
-       ((P[1] = '1') or (P[1] = '0')) then
-      NetPredictSelf := (P[1][1] = '1');
-
-    if NetPredictSelf then
-      g_Console_Add('net_predictself = 1')
-    else
-      g_Console_Add('net_predictself = 0');
+    ParseGameFlag(GAME_OPTION_TEAMHITTRACE, I_MSG_FRIENDLY_FIRE_OFF, I_MSG_FRIENDLY_FIRE_ON);
   end
-  else if cmd = 'sv_name' then
+  else if cmd = 'g_friendly_hit_projectile' then
   begin
-    if (Length(P) > 1) and (Length(P[1]) > 0) then
-    begin
-      NetServerName := P[1];
-      if Length(NetServerName) > 64 then
-        SetLength(NetServerName, 64);
-      g_Net_Slist_ServerRenamed();
-    end;
-
-    g_Console_Add(cmd + ' = "' + NetServerName + '"');
+    ParseGameFlag(GAME_OPTION_TEAMHITPROJECTILE, I_MSG_FRIENDLY_FIRE_OFF, I_MSG_FRIENDLY_FIRE_ON);
   end
-  else if cmd = 'sv_passwd' then
+  else if cmd = 'g_weaponstay' then
   begin
-    if (Length(P) > 1) and (Length(P[1]) > 0) then
-    begin
-      NetPassword := P[1];
-      if Length(NetPassword) > 24 then
-        SetLength(NetPassword, 24);
-      g_Net_Slist_ServerRenamed();
-    end;
-
-    g_Console_Add(cmd + ' = "' + AnsiLowerCase(NetPassword) + '"');
+    ParseGameFlag(GAME_OPTION_WEAPONSTAY, I_MSG_WEAPONSTAY_OFF, I_MSG_WEAPONSTAY_ON);
   end
-  else if cmd = 'sv_maxplrs' then
+  else if cmd = 'g_allow_exit' then
   begin
-    if (Length(P) > 1) then
-    begin
-      NetMaxClients := Min(Max(StrToIntDef(P[1], NetMaxClients), 1), NET_MAXCLIENTS);
-      if g_Game_IsServer and g_Game_IsNet then
-      begin
-        b := 0;
-        for a := 0 to High(NetClients) do
-        begin
-          if NetClients[a].Used then
-          begin
-            Inc(b);
-            if b > NetMaxClients then
-            begin
-              s := g_Player_Get(NetClients[a].Player).Name;
-              enet_peer_disconnect(NetClients[a].Peer, NET_DISC_FULL);
-              g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
-              MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
-            end;
-          end;
-        end;
-        g_Net_Slist_ServerRenamed();
-      end;
-    end;
-
-    g_Console_Add(cmd + ' = ' + IntToStr(NetMaxClients));
+    ParseGameFlag(GAME_OPTION_ALLOWEXIT, I_MSG_ALLOWEXIT_OFF, I_MSG_ALLOWEXIT_ON, True);
   end
-  else if cmd = 'sv_public' then
+  else if cmd = 'g_allow_monsters' then
   begin
-    if (Length(P) > 1) then
-    begin
-      NetUseMaster := StrToIntDef(P[1], Byte(NetUseMaster)) > 0;
-      if NetUseMaster then g_Net_Slist_Public() else g_Net_Slist_Private();
-    end;
-
-    g_Console_Add(cmd + ' = ' + IntToStr(Byte(NetUseMaster)));
+    ParseGameFlag(GAME_OPTION_MONSTERS, I_MSG_ALLOWMON_OFF, I_MSG_ALLOWMON_ON, True);
   end
-  else if cmd = 'sv_intertime' then
+  else if cmd = 'g_bot_vsplayers' then
   begin
-    if (Length(P) > 1) then
-      gDefInterTime := Min(Max(StrToIntDef(P[1], gDefInterTime), -1), 120);
-
-    g_Console_Add(cmd + ' = ' + IntToStr(gDefInterTime));
+    ParseGameFlag(GAME_OPTION_BOTVSPLAYER, I_MSG_BOTSVSPLAYERS_OFF, I_MSG_BOTSVSPLAYERS_ON);
   end
-  else if cmd = 'p1_name' then
+  else if cmd = 'g_bot_vsmonsters' then
   begin
-    if (Length(P) > 1) then
-    begin
-      gPlayer1Settings.Name := b_Text_Unformat(P[1]);
-      if g_Game_IsClient then
-        MC_SEND_PlayerSettings
-      else if gGameOn and (gPlayer1 <> nil) then
-      begin
-        gPlayer1.Name := b_Text_Unformat(P[1]);
-        if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer1.UID);
-      end;
-    end;
+    ParseGameFlag(GAME_OPTION_BOTVSMONSTER, I_MSG_BOTSVSMONSTERS_OFF, I_MSG_BOTSVSMONSTERS_ON);
   end
-  else if cmd = 'p2_name' then
-  begin
-    if (Length(P) > 1) then
-    begin
-      gPlayer2Settings.Name := b_Text_Unformat(P[1]);
-      if g_Game_IsClient then
-        MC_SEND_PlayerSettings
-      else if gGameOn and (gPlayer2 <> nil) then
-      begin
-        gPlayer2.Name := b_Text_Unformat(P[1]);
-        if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer2.UID);
-      end;
-    end;
+  else if cmd = 'g_dm_keys' then
+  begin
+    ParseGameFlag(GAME_OPTION_DMKEYS, I_MSG_DMKEYS_OFF, I_MSG_DMKEYS_ON, True);
   end
-  else if cmd = 'p1_color' then
+  else if cmd = 'g_gameflags' then
   begin
-    if Length(P) > 3 then
+    if Length(P) > 1 then
     begin
-      gPlayer1Settings.Color := _RGB(EnsureRange(StrToIntDef(P[1], 0), 0, 255),
-                                     EnsureRange(StrToIntDef(P[2], 0), 0, 255),
-                                     EnsureRange(StrToIntDef(P[3], 0), 0, 255));
-      if g_Game_IsClient then
-        MC_SEND_PlayerSettings
-      else if gGameOn and (gPlayer1 <> nil) then
+      gsGameFlags := StrToDWordDef(P[1], gsGameFlags);
+      if g_Game_IsServer then
       begin
-        gPlayer1.SetColor(gPlayer1Settings.Color);
-        if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer1.UID);
+        gGameSettings.Options := gsGameFlags;
+        if g_Game_IsNet then MH_SEND_GameSettings;
       end;
     end;
+
+    g_Console_Add(Format('%s %u', [cmd, gsGameFlags]));
   end
-  else if cmd = 'p2_color' then
+  else if cmd = 'g_warmup_time' then
   begin
-    if Length(P) > 3 then
+    if Length(P) > 1 then
     begin
-      gPlayer2Settings.Color := _RGB(EnsureRange(StrToIntDef(P[1], 0), 0, 255),
-                                     EnsureRange(StrToIntDef(P[2], 0), 0, 255),
-                                     EnsureRange(StrToIntDef(P[3], 0), 0, 255));
-      if g_Game_IsClient then
-        MC_SEND_PlayerSettings
-      else if gGameOn and (gPlayer2 <> nil) then
+      gsWarmupTime := nclamp(StrToIntDef(P[1], gsWarmupTime), 0, $FFFF);
+      if g_Game_IsServer then
       begin
-        gPlayer2.SetColor(gPlayer2Settings.Color);
-        if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer2.UID);
+        gGameSettings.WarmupTime := gsWarmupTime;
+        // extend warmup if it's already going
+        if gLMSRespawn = LMS_RESPAWN_WARMUP then
+        begin
+          gLMSRespawnTime := gTime + gsWarmupTime * 1000;
+          if g_Game_IsNet then MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime);
+        end;
+        if g_Game_IsNet then MH_SEND_GameSettings;
       end;
     end;
+
+    g_Console_Add(Format(_lc[I_MSG_WARMUP], [Integer(gsWarmupTime)]));
+    if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
   end
-  else if cmd = 'p1_model' then
+  else if cmd = 'g_spawn_invul' then
   begin
-    if (Length(P) > 1) then
+    if Length(P) > 1 then
     begin
-      gPlayer1Settings.Model := P[1];
-      if g_Game_IsClient then
-        MC_SEND_PlayerSettings
-      else if gGameOn and (gPlayer1 <> nil) then
+      gsSpawnInvul := nclamp(StrToIntDef(P[1], gsSpawnInvul), 0, $FFFF);
+      if g_Game_IsServer then
       begin
-        gPlayer1.FActualModelName := gPlayer1Settings.Model;
-        gPlayer1.SetModel(gPlayer1Settings.Model);
-        if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer1.UID);
+        gGameSettings.SpawnInvul := gsSpawnInvul;
+        if g_Game_IsNet then MH_SEND_GameSettings;
       end;
     end;
+
+    g_Console_Add(Format('%s %d', [cmd, Integer(gsSpawnInvul)]));
   end
-  else if cmd = 'p2_model' then
+  else if cmd = 'g_item_respawn_time' then
   begin
-    if (Length(P) > 1) then
+    if Length(P) > 1 then
     begin
-      gPlayer2Settings.Model := P[1];
-      if g_Game_IsClient then
-        MC_SEND_PlayerSettings
-      else if gGameOn and (gPlayer2 <> nil) then
+      gsItemRespawnTime := nclamp(StrToIntDef(P[1], gsItemRespawnTime), 0, $FFFF);
+      if g_Game_IsServer then
       begin
-        gPlayer2.FActualModelName := gPlayer2Settings.Model;
-        gPlayer2.SetModel(gPlayer2Settings.Model);
-        if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer2.UID);
+        gGameSettings.ItemRespawnTime := gsItemRespawnTime;
+        if g_Game_IsNet then MH_SEND_GameSettings;
       end;
     end;
-  end
-  else if cmd = 'r_showscore' then
-  begin
-    if (Length(P) > 1) and
-       ((P[1] = '1') or (P[1] = '0')) then
-      gShowGoals := (P[1][1] = '1');
 
-    if gShowGoals then
-      g_Console_Add(_lc[I_MSG_SCORE_ON])
-    else
-      g_Console_Add(_lc[I_MSG_SCORE_OFF]);
+    g_Console_Add(Format('%s %d', [cmd, Integer(gsItemRespawnTime)]));
+    if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
   end
-  else if cmd = 'r_showstat' then
+  else if cmd = 'sv_intertime' then
   begin
-    if (Length(P) > 1) and
-       ((P[1] = '1') or (P[1] = '0')) then
-      gShowStat := (P[1][1] = '1');
+    if (Length(P) > 1) then
+      gDefInterTime := Min(Max(StrToIntDef(P[1], gDefInterTime), -1), 120);
 
-    if gShowStat then
-      g_Console_Add(_lc[I_MSG_STATS_ON])
-    else
-      g_Console_Add(_lc[I_MSG_STATS_OFF]);
+    g_Console_Add(cmd + ' = ' + IntToStr(gDefInterTime));
   end
-  else if cmd = 'r_showkillmsg' then
+  else if cmd = 'g_max_particles' then
   begin
-    if (Length(P) > 1) and
-       ((P[1] = '1') or (P[1] = '0')) then
-      gShowKillMsg := (P[1][1] = '1');
-
-    if gShowKillMsg then
-      g_Console_Add(_lc[I_MSG_KILL_MSGS_ON])
+    if Length(p) = 2 then
+    begin
+      a := Max(0, StrToInt(p[1]));
+      g_GFX_SetMax(a)
+    end
+    else if Length(p) = 1 then
+    begin
+      e_LogWritefln('%s', [g_GFX_GetMax()])
+    end
     else
-      g_Console_Add(_lc[I_MSG_KILL_MSGS_OFF]);
+    begin
+      e_LogWritefln('usage: %s <n>', [cmd])
+    end
   end
-  else if cmd = 'r_showlives' then
+  else if cmd = 'g_max_shells' then
   begin
-    if (Length(P) > 1) and
-       ((P[1] = '1') or (P[1] = '0')) then
-      gShowLives := (P[1][1] = '1');
-
-    if gShowLives then
-      g_Console_Add(_lc[I_MSG_LIVES_ON])
+    if Length(p) = 2 then
+    begin
+      a := Max(0, StrToInt(p[1]));
+      g_Shells_SetMax(a)
+    end
+    else if Length(p) = 1 then
+    begin
+      e_LogWritefln('%s', [g_Shells_GetMax()])
+    end
     else
-      g_Console_Add(_lc[I_MSG_LIVES_OFF]);
+    begin
+      e_LogWritefln('usage: %s <n>', [cmd])
+    end
   end
-  else if cmd = 'r_showspect' then
+  else if cmd = 'g_max_gibs' then
   begin
-    if (Length(P) > 1) and
-       ((P[1] = '1') or (P[1] = '0')) then
-      gSpectHUD := (P[1][1] = '1');
-
-    if gSpectHUD then
-      g_Console_Add(_lc[I_MSG_SPECT_HUD_ON])
+    if Length(p) = 2 then
+    begin
+      a := Max(0, StrToInt(p[1]));
+      g_Gibs_SetMax(a)
+    end
+    else if Length(p) = 1 then
+    begin
+      e_LogWritefln('%s', [g_Gibs_GetMax()])
+    end
     else
-      g_Console_Add(_lc[I_MSG_SPECT_HUD_OFF]);
+    begin
+      e_LogWritefln('usage: %s <n>', [cmd])
+    end
   end
-  else if cmd = 'r_showping' then
+  else if cmd = 'g_max_corpses' then
   begin
-    if (Length(P) > 1) and
-       ((P[1] = '1') or (P[1] = '0')) then
-      gShowPing := (P[1][1] = '1');
-
-    if gShowPing then
-      g_Console_Add(_lc[I_MSG_PING_ON])
+    if Length(p) = 2 then
+    begin
+      a := Max(0, StrToInt(p[1]));
+      g_Corpses_SetMax(a)
+    end
+    else if Length(p) = 1 then
+    begin
+      e_LogWritefln('%s', [g_Corpses_GetMax()])
+    end
     else
-      g_Console_Add(_lc[I_MSG_PING_OFF]);
+    begin
+      e_LogWritefln('usage: %s <n>', [cmd])
+    end
   end
-  else if (cmd = 'g_scorelimit') and not g_Game_IsClient then
+  else if cmd = 'g_scorelimit' then
   begin
     if Length(P) > 1 then
     begin
-      if StrToIntDef(P[1], gGameSettings.GoalLimit) = 0 then
-        gGameSettings.GoalLimit := 0
-      else
-        begin
-          b := 0;
-
-          if gGameSettings.GameMode = GM_DM then
-            begin // DM
-              stat := g_Player_GetStats();
-              if stat <> nil then
-                for a := 0 to High(stat) do
-                  if stat[a].Frags > b then
-                    b := stat[a].Frags;
-            end
-          else // TDM/CTF
-            b := Max(gTeamStat[TEAM_RED].Goals, gTeamStat[TEAM_BLUE].Goals);
-
-          gGameSettings.GoalLimit := Max(StrToIntDef(P[1], gGameSettings.GoalLimit), b);
-        end;
+      gsGoalLimit := nclamp(StrToIntDef(P[1], gsGoalLimit), 0, $FFFF);
 
-      if g_Game_IsNet then MH_SEND_GameSettings;
+      if g_Game_IsServer then
+      begin
+        b := 0;
+        if gGameSettings.GameMode = GM_DM then
+        begin // DM
+          stat := g_Player_GetStats();
+          if stat <> nil then
+            for a := 0 to High(stat) do
+              if stat[a].Frags > b then
+                b := stat[a].Frags;
+        end
+        else // TDM/CTF
+          b := Max(gTeamStat[TEAM_RED].Goals, gTeamStat[TEAM_BLUE].Goals);
+
+        // if someone has a higher score, set it to that instead
+        gsGoalLimit := max(gsGoalLimit, b);
+        gGameSettings.GoalLimit := gsGoalLimit;
+        if g_Game_IsNet then MH_SEND_GameSettings;
+      end;
     end;
 
-    g_Console_Add(Format(_lc[I_MSG_SCORE_LIMIT], [gGameSettings.GoalLimit]));
+    g_Console_Add(Format(_lc[I_MSG_SCORE_LIMIT], [Integer(gsGoalLimit)]));
   end
-  else if (cmd = 'g_timelimit') and not g_Game_IsClient then
+  else if cmd = 'g_timelimit' then
   begin
-    if (Length(P) > 1) and (StrToIntDef(P[1], -1) >= 0) then
-      gGameSettings.TimeLimit := StrToIntDef(P[1], -1);
-
+    if Length(P) > 1 then
+    begin
+      gsTimeLimit := nclamp(StrToIntDef(P[1], gsTimeLimit), 0, $FFFF);
+      if g_Game_IsServer then
+      begin
+        gGameSettings.TimeLimit := gsTimeLimit;
+        if g_Game_IsNet then MH_SEND_GameSettings;
+      end;
+    end;
     g_Console_Add(Format(_lc[I_MSG_TIME_LIMIT],
-                         [gGameSettings.TimeLimit div 3600,
-                         (gGameSettings.TimeLimit div 60) mod 60,
-                          gGameSettings.TimeLimit mod 60]));
-    if g_Game_IsNet then MH_SEND_GameSettings;
+                         [gsTimeLimit div 3600,
+                         (gsTimeLimit div 60) mod 60,
+                          gsTimeLimit mod 60]));
   end
-  else if (cmd = 'g_maxlives') and not g_Game_IsClient then
+  else if cmd = 'g_maxlives' then
   begin
     if Length(P) > 1 then
     begin
-      if StrToIntDef(P[1], gGameSettings.MaxLives) = 0 then
-        gGameSettings.MaxLives := 0
-      else
+      gsMaxLives := nclamp(StrToIntDef(P[1], gsMaxLives), 0, $FFFF);
+      if g_Game_IsServer then
       begin
-        b := 0;
-        stat := g_Player_GetStats();
-        if stat <> nil then
-          for a := 0 to High(stat) do
-            if stat[a].Lives > b then
-              b := stat[a].Lives;
-        gGameSettings.MaxLives :=
-          Max(StrToIntDef(P[1], gGameSettings.MaxLives), b);
+        gGameSettings.MaxLives := gsMaxLives;
+        if g_Game_IsNet then MH_SEND_GameSettings;
       end;
     end;
 
-    g_Console_Add(Format(_lc[I_MSG_LIVES],
-                         [gGameSettings.MaxLives]));
-    if g_Game_IsNet then MH_SEND_GameSettings;
+    g_Console_Add(Format(_lc[I_MSG_LIVES], [Integer(gsMaxLives)]));
+  end;
+end;
+
+procedure PlayerSettingsCVars(P: SSArray);
+var
+  cmd: string;
+  team: Byte;
+
+  function ParseTeam(s: string): Byte;
+  begin
+    result := 0;
+    case s of
+      'red', '1':  result := TEAM_RED;
+      'blue', '2': result := TEAM_BLUE;
+      else         result := TEAM_NONE;
+    end;
+  end;
+begin
+  cmd := LowerCase(P[0]);
+  case cmd of
+    'p1_name':
+      begin
+        if (Length(P) > 1) then
+        begin
+          gPlayer1Settings.Name := b_Text_Unformat(P[1]);
+          if g_Game_IsClient then
+            MC_SEND_PlayerSettings
+          else if gGameOn and (gPlayer1 <> nil) then
+          begin
+            gPlayer1.Name := b_Text_Unformat(P[1]);
+            if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer1.UID);
+          end;
+        end;
+      end;
+    'p2_name':
+      begin
+        if (Length(P) > 1) then
+        begin
+          gPlayer2Settings.Name := b_Text_Unformat(P[1]);
+          if g_Game_IsClient then
+            MC_SEND_PlayerSettings
+          else if gGameOn and (gPlayer2 <> nil) then
+          begin
+            gPlayer2.Name := b_Text_Unformat(P[1]);
+            if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer2.UID);
+          end;
+        end;
+      end;
+    'p1_color':
+      begin
+        if Length(P) > 3 then
+        begin
+          gPlayer1Settings.Color := _RGB(EnsureRange(StrToIntDef(P[1], 0), 0, 255),
+                                         EnsureRange(StrToIntDef(P[2], 0), 0, 255),
+                                         EnsureRange(StrToIntDef(P[3], 0), 0, 255));
+          if g_Game_IsClient then
+            MC_SEND_PlayerSettings
+          else if gGameOn and (gPlayer1 <> nil) then
+          begin
+            gPlayer1.SetColor(gPlayer1Settings.Color);
+            if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer1.UID);
+          end;
+        end;
+      end;
+    'p2_color':
+      begin
+        if Length(P) > 3 then
+        begin
+          gPlayer2Settings.Color := _RGB(EnsureRange(StrToIntDef(P[1], 0), 0, 255),
+                                         EnsureRange(StrToIntDef(P[2], 0), 0, 255),
+                                         EnsureRange(StrToIntDef(P[3], 0), 0, 255));
+          if g_Game_IsClient then
+            MC_SEND_PlayerSettings
+          else if gGameOn and (gPlayer2 <> nil) then
+          begin
+            gPlayer2.SetColor(gPlayer2Settings.Color);
+            if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer2.UID);
+          end;
+        end;
+      end;
+    'p1_model':
+      begin
+        if (Length(P) > 1) then
+        begin
+          gPlayer1Settings.Model := P[1];
+          if g_Game_IsClient then
+            MC_SEND_PlayerSettings
+          else if gGameOn and (gPlayer1 <> nil) then
+          begin
+            gPlayer1.FActualModelName := gPlayer1Settings.Model;
+            gPlayer1.SetModel(gPlayer1Settings.Model);
+            if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer1.UID);
+          end;
+        end;
+      end;
+    'p2_model':
+      begin
+        if (Length(P) > 1) then
+        begin
+          gPlayer2Settings.Model := P[1];
+          if g_Game_IsClient then
+            MC_SEND_PlayerSettings
+          else if gGameOn and (gPlayer2 <> nil) then
+          begin
+            gPlayer2.FActualModelName := gPlayer2Settings.Model;
+            gPlayer2.SetModel(gPlayer2Settings.Model);
+            if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer2.UID);
+          end;
+        end;
+      end;
+    'p1_team':
+      begin
+        // TODO: switch teams if in game or store this separately
+        if (Length(P) > 1) then
+        begin
+          team := ParseTeam(P[1]);
+          if team = TEAM_NONE then
+            g_Console_Add('expected ''red'', ''blue'', 1 or 2')
+          else if not gGameOn and not g_Game_IsNet then
+            gPlayer1Settings.Team := team
+          else
+            g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
+        end;
+      end;
+    'p2_team':
+      begin
+        // TODO: switch teams if in game or store this separately
+        if (Length(P) > 1) then
+        begin
+          team := ParseTeam(P[1]);
+          if team = TEAM_NONE then
+            g_Console_Add('expected ''red'', ''blue'', 1 or 2')
+          else if not gGameOn and not g_Game_IsNet then
+            gPlayer2Settings.Team := team
+          else
+            g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
+        end;
+      end;
   end;
 end;
 
@@ -5921,7 +4067,7 @@ var
   //pt: TDFPoint;
   mon: TMonster;
 begin
-// Êîìàíäû îòëàäî÷íîãî ðåæèìà:
+// Команды отладочного режима:
   if {gDebugMode}conIsCheatsEnabled then
   begin
     cmd := LowerCase(P[0]);
@@ -6270,17 +4416,10 @@ var
   listen: LongWord;
   found: Boolean;
 begin
-// Îáùèå êîìàíäû:
+// Общие команды:
   cmd := LowerCase(P[0]);
   chstr := '';
-  if (cmd = 'quit') or
-     (cmd = 'exit') then
-  begin
-    g_Game_Free();
-    g_Game_Quit();
-    Exit;
-  end
-  else if cmd = 'pause' then
+  if cmd = 'pause' then
   begin
     if (g_ActiveWindow = nil) then
       g_Game_Pause(not gPauseMain);
@@ -6329,7 +4468,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;
@@ -6337,7 +4476,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
@@ -6373,6 +4512,34 @@ begin
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
+  else if cmd = 'kick_pid' then
+  begin
+    if g_Game_IsServer and g_Game_IsNet then
+    begin
+      if Length(P) < 2 then
+      begin
+        g_Console_Add('kick_pid <player ID>');
+        Exit;
+      end;
+      if P[1] = '' then
+      begin
+        g_Console_Add('kick_pid <player ID>');
+        Exit;
+      end;
+
+      a := StrToIntDef(P[1], 0);
+      pl := g_Net_Client_ByPlayer(a);
+      if (pl <> nil) and pl^.Used and (pl^.Peer <> nil) then
+      begin
+        s := g_Net_ClientName_ByID(pl^.ID);
+        enet_peer_disconnect(pl^.Peer, NET_DISC_KICK);
+        g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
+        MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
+        g_Net_Slist_ServerPlayerLeaves();
+      end;
+    end else
+      g_Console_Add(_lc[I_MSG_SERVERONLY]);
+  end
   else if cmd = 'ban' then
   begin
     if g_Game_IsServer and g_Game_IsNet then
@@ -6431,6 +4598,35 @@ begin
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
+  else if cmd = 'ban_pid' then
+  begin
+    if g_Game_IsServer and g_Game_IsNet then
+    begin
+      if Length(P) < 2 then
+      begin
+        g_Console_Add('ban_pid <player ID>');
+        Exit;
+      end;
+      if P[1] = '' then
+      begin
+        g_Console_Add('ban_pid <player ID>');
+        Exit;
+      end;
+
+      a := StrToIntDef(P[1], 0);
+      pl := g_Net_Client_ByPlayer(a);
+      if (pl <> nil) and pl^.Used and (pl^.Peer <> nil) then
+      begin
+        s := g_Net_ClientName_ByID(pl^.ID);
+        g_Net_BanHost(pl^.Peer^.address.host, False);
+        enet_peer_disconnect(pl^.Peer, NET_DISC_TEMPBAN);
+        g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
+        MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
+        g_Net_Slist_ServerPlayerLeaves();
+      end;
+    end else
+      g_Console_Add(_lc[I_MSG_SERVERONLY]);
+  end
   else if cmd = 'permban' then
   begin
     if g_Game_IsServer and g_Game_IsNet then
@@ -6491,6 +4687,57 @@ begin
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
+  else if cmd = 'permban_pid' then
+  begin
+    if g_Game_IsServer and g_Game_IsNet then
+    begin
+      if Length(P) < 2 then
+      begin
+        g_Console_Add('permban_pid <player ID>');
+        Exit;
+      end;
+      if P[1] = '' then
+      begin
+        g_Console_Add('permban_pid <player ID>');
+        Exit;
+      end;
+
+      a := StrToIntDef(P[1], 0);
+      pl := g_Net_Client_ByPlayer(a);
+      if (pl <> nil) and pl^.Used and (pl^.Peer <> nil) then
+      begin
+        s := g_Net_ClientName_ByID(pl^.ID);
+        g_Net_BanHost(pl^.Peer^.address.host);
+        enet_peer_disconnect(pl^.Peer, NET_DISC_BAN);
+        g_Net_SaveBanList();
+        g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
+        MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
+        g_Net_Slist_ServerPlayerLeaves();
+      end;
+    end else
+      g_Console_Add(_lc[I_MSG_SERVERONLY]);
+  end
+  else if cmd = 'permban_ip' then
+  begin
+    if g_Game_IsServer and g_Game_IsNet then
+    begin
+      if Length(P) < 2 then
+      begin
+        g_Console_Add('permban_ip <IP address>');
+        Exit;
+      end;
+      if P[1] = '' then
+      begin
+        g_Console_Add('permban_ip <IP address>');
+        Exit;
+      end;
+
+      g_Net_BanHost(P[1]);
+      g_Net_SaveBanList();
+      g_Console_Add(Format(_lc[I_PLAYER_BAN], [P[1]]));
+    end else
+      g_Console_Add(_lc[I_MSG_SERVERONLY]);
+  end
   else if cmd = 'unban' then
   begin
     if g_Game_IsServer and g_Game_IsNet then
@@ -6699,7 +4946,8 @@ begin
         g_Game_Free();
         with gGameSettings do
         begin
-          GameMode := g_Game_TextToMode(gcGameMode);
+          Options := gsGameFlags;
+          GameMode := g_Game_TextToMode(gsGameMode);
           if gSwitchGameMode <> GM_NONE then
             GameMode := gSwitchGameMode;
           if GameMode = GM_NONE then GameMode := GM_DM;
@@ -6753,7 +5001,8 @@ begin
         g_Game_Free();
         with gGameSettings do
         begin
-          GameMode := g_Game_TextToMode(gcGameMode);
+          Options := gsGameFlags;
+          GameMode := g_Game_TextToMode(gsGameMode);
           if gSwitchGameMode <> GM_NONE then GameMode := gSwitchGameMode;
           if GameMode = GM_NONE then GameMode := GM_DM;
           if GameMode = GM_SINGLE then GameMode := GM_COOP;
@@ -7125,7 +5374,7 @@ begin
         gSelectWeapon[b, a] := True
     end
   end
-// Êîìàíäû Ñâîåé èãðû:
+// Команды Своей игры:
   else if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
   begin
     if cmd = 'bot_addred' then
@@ -7229,7 +5478,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],
@@ -7330,6 +5579,73 @@ begin
   end;
 end;
 
+procedure SystemCommands(P: SSArray);
+var
+  cmd: string;
+begin
+  cmd := LowerCase(P[0]);
+  case cmd of
+    'exit', 'quit':
+      begin
+        g_Game_Free();
+        g_Game_Quit();
+      end;
+    'r_reset':
+      begin
+        gRC_Width := Max(1, gRC_Width);
+        gRC_Height := Max(1, gRC_Height);
+        gBPP := Max(1, gBPP);
+        if sys_SetDisplayMode(gRC_Width, gRC_Height, gBPP, gRC_FullScreen, gRC_Maximized) = True then
+          e_LogWriteln('resolution changed')
+        else
+          e_LogWriteln('resolution not changed');
+        sys_EnableVSync(gVSync);
+      end;
+    'r_maxfps':
+      begin
+        if Length(p) = 2 then
+        begin
+          gMaxFPS := StrToIntDef(p[1], gMaxFPS);
+          if gMaxFPS > 0 then
+            gFrameTime := 1000 div gMaxFPS
+          else
+            gFrameTime := 0;
+        end;
+        e_LogWritefln('r_maxfps %d', [gMaxFPS]);
+      end;
+    'g_language':
+      begin
+        if Length(p) = 2 then
+        begin
+          gAskLanguage := true;
+          gLanguage := LANGUAGE_ENGLISH;
+          case LowerCase(p[1]) of
+            'english':
+               begin
+                 gAskLanguage := false;
+                 gLanguage := LANGUAGE_ENGLISH;
+               end;
+            'russian':
+               begin
+                 gAskLanguage := false;
+                 gLanguage := LANGUAGE_RUSSIAN;
+               end;
+            'ask':
+               begin
+                 gAskLanguage := true;
+                 gLanguage := LANGUAGE_ENGLISH;
+               end;
+          end;
+          g_Language_Set(gLanguage);
+        end
+        else
+        begin
+          e_LogWritefln('usage: %s <English|Russian|Ask>', [cmd]);
+        end
+      end;
+  end;
+end;
+
 procedure g_TakeScreenShot(Filename: string = '');
   var s: TStream; t: TDateTime; dir, date, name: String;
 begin
@@ -7378,14 +5694,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;
@@ -7422,7 +5738,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
@@ -7433,13 +5749,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;
@@ -7684,8 +6000,7 @@ begin
   case gAnnouncer of
     ANNOUNCE_NONE:
       Exit;
-    ANNOUNCE_ME,
-    ANNOUNCE_MEPLUS:
+    ANNOUNCE_ME:
       if not g_Game_IsWatchedPlayer(SpawnerUID) then
         Exit;
   end;
@@ -7807,7 +6122,7 @@ end;
 procedure g_Game_SetDebugMode();
 begin
   gDebugMode := True;
-// ×èòû (äàæå â ñâîåé èãðå):
+// Читы (даже в своей игре):
   gCheats := True;
 end;
 
@@ -7821,7 +6136,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
@@ -7896,7 +6211,7 @@ begin
     if (s[1] = '-') and (Length(s) > 1) then
     begin
       if (s[2] = '-') and (Length(s) > 2) then
-        begin // Îäèíî÷íûé ïàðàìåòð
+        begin // Одиночный параметр
           SetLength(pars, Length(pars) + 1);
           with pars[High(pars)] do
           begin
@@ -7906,7 +6221,7 @@ begin
         end
       else
         if (i < ParamCount) then
-        begin // Ïàðàìåòð ñî çíà÷åíèåì
+        begin // Параметр со значением
           Inc(i);
           SetLength(pars, Length(pars) + 1);
           with pars[High(pars)] do
@@ -8020,11 +6335,9 @@ begin
   // Options:
     s := Find_Param_Value(pars, '-opt');
     if (s = '') then
-      Opt := GAME_OPTION_ALLOWEXIT or GAME_OPTION_BOTVSPLAYER or GAME_OPTION_BOTVSMONSTER
+      Opt := gsGameFlags
     else
       Opt := StrToIntDef(s, 0);
-    if Opt = 0 then
-      Opt := GAME_OPTION_ALLOWEXIT or GAME_OPTION_BOTVSPLAYER or GAME_OPTION_BOTVSMONSTER;
 
   // Close after map:
     s := Find_Param_Value(pars, '--close');
@@ -8136,9 +6449,6 @@ begin
 
   conRegVar('dbg_ignore_level_bounds', @g_dbg_ignore_bounds, 'ignore level bounds', '',  false);
 
-  conRegVar('r_scale', @g_dbg_scale, 0.01, 100.0, 'render scale', '',  false);
-  conRegVar('r_resolution_scale', @r_pixel_scale, 0.01, 100.0, 'upscale factor', '', false);
-
   conRegVar('light_enabled', @gwin_k8_enable_light_experiments, 'enable/disable dynamic lighting', 'lighting');
   conRegVar('light_player_halo', @g_playerLight, 'enable/disable player halo', 'player light halo');
 
@@ -8147,4 +6457,11 @@ begin
 
   conRegVar('r_showfps', @gShowFPS, 'draw fps counter', 'draw fps counter');
   conRegVar('r_showtime', @gShowTime, 'show game time', 'show game time');
+  conRegVar('r_showping', @gShowPing, 'show ping', 'show ping');
+  conRegVar('r_showscore', @gShowGoals, 'show score', 'show score');
+  conRegVar('r_showkillmsg', @gShowKillMsg, 'show kill log', 'show kill log');
+  conRegVar('r_showlives', @gShowLives, 'show lives', 'show lives');
+  conRegVar('r_showspect', @gSpectHUD, 'show spectator hud', 'show spectator hud');
+  conRegVar('r_showstat', @gShowStat, 'show stats', 'show stats');
+  conRegVar('r_showpids', @gShowPIDs, 'show PIDs', 'show PIDs');
 end.