DEADSOFTWARE

render: use only r_render to access render
[d2df-sdl.git] / src / game / g_game.pas
index 2084d8876e43323c661c78b7f741a01567defe89..fcd73a7bb8147add3027353f37401c4ea3791c8d 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, g_res_downloader,
   g_sound, g_gui, utils, md5, mempool, xprofiler,
   g_touch, g_weapons;
 
@@ -31,6 +31,8 @@ type
     TimeLimit: Word;
     GoalLimit: Word;
     WarmupTime: Word;
+    SpawnInvul: Word;
+    ItemRespawnTime: Word;
     MaxLives: Byte;
     Options: LongWord;
     WAD: String;
@@ -82,10 +84,8 @@ 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);
 function  g_Game_ModeToText(Mode: Byte): string;
 function  g_Game_TextToMode(Mode: string): Byte;
 procedure g_Game_ExecuteEvent(Name: String);
@@ -101,9 +101,8 @@ procedure g_Game_StartClient(Addr: String; Port: Word; PW: String);
 procedure g_Game_Restart();
 procedure g_Game_RestartLevel();
 procedure g_Game_RestartRound(NoMapRestart: Boolean = False);
-procedure g_Game_ClientWAD(NewWAD: String; WHash: TMD5Digest);
-procedure g_Game_SaveOptions();
-function  g_Game_StartMap(Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
+function  g_Game_ClientWAD (NewWAD: String; const WHash: TMD5Digest): AnsiString;
+function  g_Game_StartMap(asMegawad: Boolean; Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
 procedure g_Game_ChangeMap(const MapPath: String);
 procedure g_Game_ExitLevel(const Map: AnsiString);
 function  g_Game_GetFirstMap(WAD: String): String;
@@ -126,12 +125,16 @@ procedure g_Game_Announce_KillCombo(Param: Integer);
 procedure g_Game_Announce_BodyKill(SpawnerUID: Word);
 procedure g_Game_StartVote(Command, Initiator: string);
 procedure g_Game_CheckVote;
-procedure g_TakeScreenShot();
+{$IFNDEF HEADLESS}
+  procedure g_TakeScreenShot(Filename: string = '');
+{$ENDIF}
 procedure g_FatalError(Text: String);
 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,13 @@ 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 KeyPress (K: Word);
+procedure CharPress (C: AnsiChar);
 
 { procedure SetWinPause(Enable: Boolean); }
 
@@ -172,13 +180,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_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;
@@ -221,8 +233,9 @@ const
   DEFAULT_PLAYERS = 1;
 {$ENDIF}
 
+  STATFILE_VERSION = $03;
+
 var
-  gStdFont: DWORD;
   gGameSettings: TGameSettings;
   gPlayer1Settings: TPlayerSettings;
   gPlayer2Settings: TPlayerSettings;
@@ -234,11 +247,12 @@ var
   gPlayer2: TPlayer = nil;
   gPlayerDrawn: TPlayer = nil;
   gTime: LongWord;
+  gLerpFactor: Single = 1.0;
   gSwitchGameMode: Byte = GM_DM;
   gHearPoint1, gHearPoint2: THearPoint;
   gSoundEffectsDF: Boolean = False;
   gSoundTriggerTime: Word = 0;
-  gAnnouncer: Byte = ANNOUNCE_NONE;
+  gAnnouncer: Integer = ANNOUNCE_NONE;
   goodsnd: array[0..3] of TPlayableSound;
   killsnd: array[0..3] of TPlayableSound;
   hahasnd: array[0..2] of TPlayableSound;
@@ -259,6 +273,7 @@ var
   gShowFPS: Boolean = False;
   gShowGoals: Boolean = True;
   gShowStat: Boolean = True;
+  gShowPIDs: Boolean = False;
   gShowKillMsg: Boolean = True;
   gShowLives: Boolean = True;
   gShowPing: Boolean = False;
@@ -287,12 +302,10 @@ var
   gMapToDelete: String;
   gTempDelete: Boolean = False;
   gLastMap: Boolean = False;
-  gWinPosX, gWinPosY: Integer;
-  gWinSizeX, gWinSizeY: Integer;
-  gWinFrameX, gWinFrameY, gWinCaption: Integer;
-  gWinActive: Boolean = True; // by default window is active, lol
+  gScreenWidth: Word;
+  gScreenHeight: Word;
   gResolutionChange: Boolean = False;
-  gRC_Width, gRC_Height: Word;
+  gRC_Width, gRC_Height: Integer;
   gRC_FullScreen, gRC_Maximized: Boolean;
   gLanguageChange: Boolean = False;
   gDebugMode: Boolean = False;
@@ -361,6 +374,7 @@ var
   g_rlayer_water: Boolean = true;
   g_rlayer_fore: Boolean = true;
 
+  wNeedTimeReset: Boolean = false;
 
 procedure g_ResetDynlights ();
 procedure g_AddDynLight (x, y, radius: Integer; r, g, b, a: Single);
@@ -369,64 +383,421 @@ 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;
+    LoadingStat: TLoadingStat;
+    MessageText: String;
+    IsDrawStat: Boolean;
+    EndingGameCounter: Byte;
+    UPS: Word;
+    g_playerLight: Boolean;
+    g_dynLights: array of TDynLight = nil;
+    g_dynLightCount: Integer = 0;
+    EndPicPath: AnsiString; // full path, used by render
 
 implementation
 
 uses
-{$INCLUDE ../nogl/noGLuses.inc}
-{$IFDEF ENABLE_HOLMES}
-  g_holmes,
-{$ENDIF}
-  e_texture, g_textures, g_main, g_window, g_menu,
+  {$IFDEF ENABLE_HOLMES}
+    g_holmes,
+  {$ENDIF}
+  {$IFNDEF HEADLESS}
+    r_render,
+  {$ENDIF}
+  e_res, g_window, g_menu,
   e_input, e_log, g_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_language, g_net, g_phys,
   ENet, e_msg, g_netmsg, g_netmaster,
-  sfs, wadreader;
+  sfs, wadreader, g_system, r_playermodel;
 
+  var
+    charbuff: packed array [0..15] of AnsiChar = (
+      ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
+    );
 
+function Translit (const S: AnsiString): AnsiString;
 var
-  hasPBarGfx: Boolean = false;
-
+  i: Integer;
+begin
+  Result := S;
+  for i := 1 to Length(Result) do
+  begin
+    case Result[i] of
+      #$C9: Result[i] := 'Q';
+      #$D6: Result[i] := 'W';
+      #$D3: Result[i] := 'E';
+      #$CA: Result[i] := 'R';
+      #$C5: Result[i] := 'T';
+      #$CD: Result[i] := 'Y';
+      #$C3: Result[i] := 'U';
+      #$D8: Result[i] := 'I';
+      #$D9: Result[i] := 'O';
+      #$C7: Result[i] := 'P';
+      #$D5: Result[i] := '['; //Chr(219);
+      #$DA: Result[i] := ']'; //Chr(221);
+      #$D4: Result[i] := 'A';
+      #$DB: Result[i] := 'S';
+      #$C2: Result[i] := 'D';
+      #$C0: Result[i] := 'F';
+      #$CF: Result[i] := 'G';
+      #$D0: Result[i] := 'H';
+      #$CE: Result[i] := 'J';
+      #$CB: Result[i] := 'K';
+      #$C4: Result[i] := 'L';
+      #$C6: Result[i] := ';'; //Chr(186);
+      #$DD: Result[i] := #39; //Chr(222);
+      #$DF: Result[i] := 'Z';
+      #$D7: Result[i] := 'X';
+      #$D1: Result[i] := 'C';
+      #$CC: Result[i] := 'V';
+      #$C8: Result[i] := 'B';
+      #$D2: Result[i] := 'N';
+      #$DC: Result[i] := 'M';
+      #$C1: Result[i] := ','; //Chr(188);
+      #$DE: Result[i] := '.'; //Chr(190);
+    end;
+  end;
+end;
 
-// ////////////////////////////////////////////////////////////////////////// //
-function gPause (): Boolean; inline; begin result := gPauseMain or gPauseHolmes; end;
 
+function CheckCheat (ct: TStrings_Locale; eofs: Integer=0): Boolean;
+var
+  ls1, ls2: string;
+begin
+  ls1 :=          CheatEng[ct];
+  ls2 := Translit(CheatRus[ct]);
+  if length(ls1) = 0 then ls1 := '~';
+  if length(ls2) = 0 then ls2 := '~';
+  result :=
+    (Copy(charbuff, 17-Length(ls1)-eofs, Length(ls1)) = ls1) or
+    (Translit(Copy(charbuff, 17-Length(ls1)-eofs, Length(ls1))) = ls1) or
+    (Copy(charbuff, 17-Length(ls2)-eofs, Length(ls2)) = ls2) or
+    (Translit(Copy(charbuff, 17-Length(ls2)-eofs, Length(ls2))) = ls2);
+  {
+  if ct = I_GAME_CHEAT_JETPACK then
+  begin
+    e_WriteLog('ls1: ['+ls1+']', MSG_NOTIFY);
+    e_WriteLog('ls2: ['+ls2+']', MSG_NOTIFY);
+    e_WriteLog('bf0: ['+Copy(charbuff, 17-Length(ls1)-eofs, Length(ls1))+']', MSG_NOTIFY);
+    e_WriteLog('bf1: ['+Translit(Copy(charbuff, 17-Length(ls1)-eofs, Length(ls1)))+']', MSG_NOTIFY);
+    e_WriteLog('bf2: ['+Copy(charbuff, 17-Length(ls2)-eofs, Length(ls2))+']', MSG_NOTIFY);
+    e_WriteLog('bf3: ['+Translit(Copy(charbuff, 17-Length(ls2)-eofs, Length(ls2)))+']', MSG_NOTIFY);
+  end;
+  }
+end;
 
-// ////////////////////////////////////////////////////////////////////////// //
-function conIsCheatsEnabled (): Boolean; inline;
+procedure Cheat ();
+const
+  CHEAT_DAMAGE = 500;
+label
+  Cheated;
+var
+  s, s2: string;
+  c: ShortString;
+  a: Integer;
 begin
-  result := false;
-  if g_Game_IsNet then exit;
-  if not gDebugMode then
+  {
+  if (not gGameOn) or (not gCheats) or ((gGameSettings.GameType <> GT_SINGLE) and
+    (gGameSettings.GameMode <> GM_COOP) and (not gDebugMode))
+    or g_Game_IsNet then Exit;
+  }
+  if not gGameOn then exit;
+  if not conIsCheatsEnabled then exit;
+
+  s := 'SOUND_GAME_RADIO';
+
+  //
+  if CheckCheat(I_GAME_CHEAT_GODMODE) 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;
+    if gPlayer1 <> nil then gPlayer1.GodMode := not gPlayer1.GodMode;
+    if gPlayer2 <> nil then gPlayer2.GodMode := not gPlayer2.GodMode;
+    goto Cheated;
   end;
-  result := true;
+  // RAMBO
+  if CheckCheat(I_GAME_CHEAT_WEAPONS) then
+  begin
+    if gPlayer1 <> nil then gPlayer1.AllRulez(False);
+    if gPlayer2 <> nil then gPlayer2.AllRulez(False);
+    goto Cheated;
+  end;
+  // TANK
+  if CheckCheat(I_GAME_CHEAT_HEALTH) then
+  begin
+    if gPlayer1 <> nil then gPlayer1.AllRulez(True);
+    if gPlayer2 <> nil then gPlayer2.AllRulez(True);
+    goto Cheated;
+  end;
+  // IDDQD
+  if CheckCheat(I_GAME_CHEAT_DEATH) then
+  begin
+    if gPlayer1 <> nil then gPlayer1.Damage(CHEAT_DAMAGE, 0, 0, 0, HIT_TRAP);
+    if gPlayer2 <> nil then gPlayer2.Damage(CHEAT_DAMAGE, 0, 0, 0, HIT_TRAP);
+    s := 'SOUND_MONSTER_HAHA';
+    goto Cheated;
+  end;
+  //
+  if CheckCheat(I_GAME_CHEAT_DOORS) then
+  begin
+    g_Triggers_OpenAll();
+    goto Cheated;
+  end;
+  // GOODBYE
+  if CheckCheat(I_GAME_CHEAT_NEXTMAP) then
+  begin
+    if gTriggers <> nil then
+      for a := 0 to High(gTriggers) do
+        if gTriggers[a].TriggerType = TRIGGER_EXIT then
+        begin
+          gExitByTrigger := True;
+          //g_Game_ExitLevel(gTriggers[a].Data.MapName);
+          g_Game_ExitLevel(gTriggers[a].tgcMap);
+          Break;
+        end;
+    goto Cheated;
+  end;
+  //
+  s2 := Copy(charbuff, 15, 2);
+  if CheckCheat(I_GAME_CHEAT_CHANGEMAP, 2) and (s2[1] >= '0') and (s2[1] <= '9') and (s2[2] >= '0') and (s2[2] <= '9') then
+  begin
+    if g_Map_Exist(gGameSettings.WAD + ':\MAP' + s2) then
+    begin
+      c := 'MAP' + s2;
+      g_Game_ExitLevel(c);
+    end;
+    goto Cheated;
+  end;
+  //
+  if CheckCheat(I_GAME_CHEAT_FLY) then
+  begin
+    gFly := not gFly;
+    goto Cheated;
+  end;
+  // BULLFROG
+  if CheckCheat(I_GAME_CHEAT_JUMPS) then
+  begin
+    VEL_JUMP := 30-VEL_JUMP;
+    goto Cheated;
+  end;
+  // FORMULA1
+  if CheckCheat(I_GAME_CHEAT_SPEED) then
+  begin
+    MAX_RUNVEL := 32-MAX_RUNVEL;
+    goto Cheated;
+  end;
+  // CONDOM
+  if CheckCheat(I_GAME_CHEAT_SUIT) then
+  begin
+    if gPlayer1 <> nil then gPlayer1.GiveItem(ITEM_SUIT);
+    if gPlayer2 <> nil then gPlayer2.GiveItem(ITEM_SUIT);
+    goto Cheated;
+  end;
+  //
+  if CheckCheat(I_GAME_CHEAT_AIR) then
+  begin
+    if gPlayer1 <> nil then gPlayer1.GiveItem(ITEM_OXYGEN);
+    if gPlayer2 <> nil then gPlayer2.GiveItem(ITEM_OXYGEN);
+    goto Cheated;
+  end;
+  // PURELOVE
+  if CheckCheat(I_GAME_CHEAT_BERSERK) then
+  begin
+    if gPlayer1 <> nil then gPlayer1.GiveItem(ITEM_MEDKIT_BLACK);
+    if gPlayer2 <> nil then gPlayer2.GiveItem(ITEM_MEDKIT_BLACK);
+    goto Cheated;
+  end;
+  //
+  if CheckCheat(I_GAME_CHEAT_JETPACK) then
+  begin
+    if gPlayer1 <> nil then gPlayer1.GiveItem(ITEM_JETPACK);
+    if gPlayer2 <> nil then gPlayer2.GiveItem(ITEM_JETPACK);
+    goto Cheated;
+  end;
+  // CASPER
+  if CheckCheat(I_GAME_CHEAT_NOCLIP) then
+  begin
+    if gPlayer1 <> nil then gPlayer1.SwitchNoClip;
+    if gPlayer2 <> nil then gPlayer2.SwitchNoClip;
+    goto Cheated;
+  end;
+  //
+  if CheckCheat(I_GAME_CHEAT_NOTARGET) then
+  begin
+    if gPlayer1 <> nil then gPlayer1.NoTarget := not gPlayer1.NoTarget;
+    if gPlayer2 <> nil then gPlayer2.NoTarget := not gPlayer2.NoTarget;
+    goto Cheated;
+  end;
+  // INFERNO
+  if CheckCheat(I_GAME_CHEAT_NORELOAD) then
+  begin
+    if gPlayer1 <> nil then gPlayer1.NoReload := not gPlayer1.NoReload;
+    if gPlayer2 <> nil then gPlayer2.NoReload := not gPlayer2.NoReload;
+    goto Cheated;
+  end;
+  if CheckCheat(I_GAME_CHEAT_AIMLINE) then
+  begin
+    gAimLine := not gAimLine;
+    goto Cheated;
+  end;
+  if CheckCheat(I_GAME_CHEAT_AUTOMAP) then
+  begin
+    gShowMap := not gShowMap;
+    goto Cheated;
+  end;
+  Exit;
+
+Cheated:
+  g_Sound_PlayEx(s);
 end;
 
 
-// ////////////////////////////////////////////////////////////////////////// //
+procedure KeyPress (K: Word);
+{$IFNDEF HEADLESS}
 var
-  profileFrameDraw: TProfiler = nil;
+  Msg: g_gui.TMessage;
+{$ENDIF}
+begin
+{$IFNDEF HEADLESS}
+  case K of
+    VK_ESCAPE: // <Esc>:
+      begin
+        if (g_ActiveWindow <> nil) then
+        begin
+          Msg.Msg := WM_KEYDOWN;
+          Msg.WParam := VK_ESCAPE;
+          g_ActiveWindow.OnMessage(Msg);
+          if (not g_Game_IsNet) and (g_ActiveWindow = nil) then g_Game_Pause(false); //Fn loves to do this
+        end
+        else if (gState <> STATE_FOLD) then
+        begin
+          if gGameOn or (gState = STATE_INTERSINGLE) or (gState = STATE_INTERCUSTOM) then
+          begin
+            g_Game_InGameMenu(True);
+          end
+          else if (gExit = 0) and (gState <> STATE_SLIST) then
+          begin
+            if (gState <> STATE_MENU) then
+            begin
+              if (NetMode <> NET_NONE) then
+              begin
+                g_Game_StopAllSounds(True);
+                g_Game_Free;
+                gState := STATE_MENU;
+                Exit;
+              end;
+            end;
+            g_GUI_ShowWindow('MainMenu');
+            g_Sound_PlayEx('MENU_OPEN');
+          end;
+        end;
+      end;
 
+    IK_F2, IK_F3, IK_F4, IK_F5, IK_F6, IK_F7, IK_F10:
+      begin // <F2> .. <F6> � <F12>
+        if gGameOn and (not gConsoleShow) and (not gChatShow) then
+        begin
+          while (g_ActiveWindow <> nil) do g_GUI_HideWindow(False);
+          if (not g_Game_IsNet) then g_Game_Pause(True);
+          case K of
+            IK_F2: g_Menu_Show_SaveMenu();
+            IK_F3: g_Menu_Show_LoadMenu();
+            IK_F4: g_Menu_Show_GameSetGame();
+            IK_F5: g_Menu_Show_OptionsVideo();
+            IK_F6: g_Menu_Show_OptionsSound();
+            IK_F7: g_Menu_Show_EndGameMenu();
+            IK_F10: g_Menu_Show_QuitGameMenu();
+          end;
+        end;
+      end;
 
-// ////////////////////////////////////////////////////////////////////////// //
-type
-  TDynLight = record
-    x, y, radius: Integer;
-    r, g, b, a: Single;
-    exploCount: Integer;
-    exploRadius: Integer;
+    else
+      begin
+        gJustChatted := False;
+        if gConsoleShow or gChatShow then
+        begin
+          g_Console_Control(K);
+        end
+        else if (g_ActiveWindow <> nil) then
+        begin
+          Msg.Msg := WM_KEYDOWN;
+          Msg.WParam := K;
+          g_ActiveWindow.OnMessage(Msg);
+        end
+        else if (gState = STATE_MENU) then
+        begin
+          g_GUI_ShowWindow('MainMenu');
+          g_Sound_PlayEx('MENU_OPEN');
+        end;
+      end;
   end;
+{$ENDIF}
+end;
 
+procedure CharPress (C: AnsiChar);
 var
-  g_dynLights: array of TDynLight = nil;
-  g_dynLightCount: Integer = 0;
-  g_playerLight: Boolean = false;
+  Msg: g_gui.TMessage;
+  a: Integer;
+begin
+  if gConsoleShow or gChatShow then
+  begin
+    g_Console_Char(C)
+  end
+  else if (g_ActiveWindow <> nil) then
+  begin
+    Msg.Msg := WM_CHAR;
+    Msg.WParam := Ord(C);
+    g_ActiveWindow.OnMessage(Msg);
+  end
+  else
+  begin
+    for a := 0 to 14 do charbuff[a] := charbuff[a+1];
+    charbuff[15] := upcase1251(C);
+    Cheat();
+  end;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+function gPause (): Boolean; inline; begin result := gPauseMain or gPauseHolmes; end;
 
 procedure g_ResetDynlights ();
 var
@@ -488,75 +859,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;
@@ -570,41 +888,19 @@ 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;
+  StatDate: string = '';
   MegaWAD: record
     info: TMegaWADInfo;
     endpic: String;
     endmus: String;
-    res: record
-      text: Array of ShortString;
-      anim: Array of ShortString;
-      pic: Array of ShortString;
-      mus: Array of ShortString;
-    end;
-    triggers: Array of record
-      event: ShortString;
-      actions: Array of record
-        action, p1, p2: Integer;
-      end;
-    end;
-    cur_trigger: Integer;
-    cur_action: Integer;
   end;
-  //InterPic: String;
   InterText: record
     lines: SSArray;
     img: String;
@@ -643,6 +939,68 @@ begin
       end;
 end;
 
+// saves a shitty CSV containing the game stats passed to it
+procedure SaveGameStat(Stat: TEndCustomGameStat; Path: string);
+var 
+  s: TextFile;
+  dir, fname, map, mode, etime: String;
+  I: Integer;
+begin
+  try
+    dir := e_GetWriteableDir(StatsDirs);
+    // stats are placed in stats/yy/mm/dd/*.csv
+    fname := e_CatPath(dir, Path);
+    ForceDirectories(fname); // ensure yy/mm/dd exists within the stats dir
+    fname := e_CatPath(fname, StatFilename + '.csv');
+    AssignFile(s, fname);
+    try
+      Rewrite(s);
+      // line 1: stats ver, datetime, server name, map name, game mode, time limit, score limit, dmflags, game time, num players
+      if g_Game_IsNet then fname := NetServerName else fname := '';
+      map := g_ExtractWadNameNoPath(gMapInfo.Map) + ':/' + g_ExtractFileName(gMapInfo.Map);
+      mode := g_Game_ModeToText(Stat.GameMode);
+      etime := Format('%d:%.2d:%.2d', [
+        Stat.GameTime div 1000 div 3600,
+        (Stat.GameTime div 1000 div 60) mod 60,
+        Stat.GameTime div 1000 mod 60
+      ]);
+      WriteLn(s, 'stats_ver,datetime,server,map,mode,timelimit,scorelimit,dmflags,time,num_players');
+      WriteLn(s, Format('%d,%s,%s,%s,%s,%u,%u,%u,%s,%d', [
+        STATFILE_VERSION,
+        StatDate,
+        dquoteStr(fname),
+        dquoteStr(map),
+        mode,
+        gGameSettings.TimeLimit,
+        gGameSettings.GoalLimit,
+        gGameSettings.Options,
+        etime,
+        Length(Stat.PlayerStat)
+      ]));
+      // line 2: game specific shit
+      //   if it's a team game: red score, blue score
+      //   if it's a coop game: monsters killed, monsters total, secrets found, secrets total
+      //   otherwise nothing
+      if Stat.GameMode in [GM_TDM, GM_CTF] then
+        WriteLn(s, 
+          Format('red_score,blue_score' + LineEnding + '%d,%d', [Stat.TeamStat[TEAM_RED].Goals, Stat.TeamStat[TEAM_BLUE].Goals]))
+      else if Stat.GameMode in [GM_COOP, GM_SINGLE] then
+        WriteLn(s,
+          Format('mon_killed,mon_total,secrets_found,secrets_total' + LineEnding + '%d,%d,%d,%d',[gCoopMonstersKilled, gTotalMonsters, gCoopSecretsFound, gSecretsCount]));
+      // lines 3-...: team, player name, frags, deaths
+      WriteLn(s, 'team,name,frags,deaths');
+      for I := Low(Stat.PlayerStat) to High(Stat.PlayerStat) do
+        with Stat.PlayerStat[I] do
+          WriteLn(s, Format('%d,%s,%d,%d', [Team, dquoteStr(Name), Frags, Deaths]));
+    except
+      g_Console_Add(Format(_lc[I_CONSOLE_ERROR_WRITE], [fname]));
+    end;
+  except
+    g_Console_Add('could not create gamestats file "' + fname + '"');
+  end;
+  CloseFile(s);
+end;
+
 function g_Game_ModeToText(Mode: Byte): string;
 begin
   Result := '';
@@ -731,48 +1089,31 @@ begin
   FreeMem(p);
 end;
 
-procedure g_Game_FreeWAD();
-var
-  a: Integer;
-begin
-  for a := 0 to High(MegaWAD.res.pic) do
-    if MegaWAD.res.pic[a] <> '' then
-      g_Texture_Delete(MegaWAD.res.pic[a]);
-
-  for a := 0 to High(MegaWAD.res.mus) do
-    if MegaWAD.res.mus[a] <> '' then
-      g_Sound_Delete(MegaWAD.res.mus[a]);
-
-  MegaWAD.res.pic := nil;
-  MegaWAD.res.text := nil;
-  MegaWAD.res.anim := nil;
-  MegaWAD.res.mus := nil;
-  MegaWAD.triggers := nil;
-
-  g_Texture_Delete('TEXTURE_endpic');
-  g_Sound_Delete('MUSIC_endmus');
-
-  ZeroMemory(@MegaWAD, SizeOf(MegaWAD));
-  gGameSettings.WAD := '';
-end;
+  procedure g_Game_FreeWAD;
+  begin
+    EndPicPath := '';
+    g_Sound_Delete('MUSIC_endmus');
+    ZeroMemory(@MegaWAD, SizeOf(MegaWAD));
+    gGameSettings.WAD := '';
+  end;
 
 procedure g_Game_LoadWAD(WAD: string);
 var
   w: TWADFile;
   cfg: TConfig;
   p: Pointer;
-  {b, }len: Integer;
-  s: string;
+  len: Integer;
+  s: AnsiString;
 begin
   g_Game_FreeWAD();
   gGameSettings.WAD := WAD;
   if not (gGameSettings.GameMode in [GM_COOP, GM_SINGLE]) then
     Exit;
 
-  MegaWAD.info := g_Game_GetMegaWADInfo(MapsDir + WAD);
+  MegaWAD.info := g_Game_GetMegaWADInfo(WAD);
 
   w := TWADFile.Create();
-  w.ReadFile(MapsDir + WAD);
+  w.ReadFile(WAD);
 
   if not w.GetResource('INTERSCRIPT', p, len) then
   begin
@@ -782,47 +1123,16 @@ begin
 
   cfg := TConfig.CreateMem(p, len);
 
- {b := 1;
- while True do
- begin
-  s := cfg.ReadStr('pic', 'pic'+IntToStr(b), '');
-  if s = '' then Break;
-  b := b+1;
-
-  SetLength(MegaWAD.res.pic, Length(MegaWAD.res.pic)+1);
-  MegaWAD.res.pic[High(MegaWAD.res.pic)] := s;
-
-  g_Texture_CreateWADEx(s, s);
- end;
-
- b := 1;
- while True do
- begin
-  s := cfg.ReadStr('mus', 'mus'+IntToStr(b), '');
-  if s = '' then Break;
-  b := b+1;
-
-  SetLength(MegaWAD.res.mus, Length(MegaWAD.res.mus)+1);
-  MegaWAD.res.mus[High(MegaWAD.res.mus)] := s;
-
-  g_Music_CreateWADEx(s, s);
- end;}
-
+  EndPicPath := '';
   MegaWAD.endpic := cfg.ReadStr('megawad', 'endpic', '');
   if MegaWAD.endpic <> '' then
-  begin
-    s := g_ExtractWadName(MegaWAD.endpic);
-    if s = '' then s := MapsDir+WAD else s := GameDir+'/wads/';
-    TEXTUREFILTER := GL_LINEAR;
-    g_Texture_CreateWADEx('TEXTURE_endpic', s+MegaWAD.endpic);
-    TEXTUREFILTER := GL_NEAREST;
-  end;
-  MegaWAD.endmus := cfg.ReadStr('megawad', 'endmus', 'Standart.wad:D2DMUS\ÊÎÍÅÖ');
+    EndPicPath := e_GetResourcePath(WadDirs, MegaWAD.endpic, WAD);
+
+  MegaWAD.endmus := cfg.ReadStr('megawad', 'endmus', 'Standart.wad:D2DMUS\КОНЕЦ');
   if MegaWAD.endmus <> '' then
   begin
-    s := g_ExtractWadName(MegaWAD.endmus);
-    if s = '' then s := MapsDir+WAD else s := GameDir+'/wads/';
-    g_Sound_CreateWADEx('MUSIC_endmus', s+MegaWAD.endmus, True);
+    s := e_GetResourcePath(WadDirs, MegaWAD.endmus, WAD);
+    g_Sound_CreateWADEx('MUSIC_endmus', s, True);
   end;
 
   cfg.Free();
@@ -894,7 +1204,7 @@ begin
   gDelayedEvents[n].DENum := Num;
   gDelayedEvents[n].DEStr := Str;
   if DEType = DE_GLOBEVENT then
-    gDelayedEvents[n].Time := (GetTimer() {div 1000}) + Time
+    gDelayedEvents[n].Time := (GetTickCount64() {div 1000}) + Time
   else
     gDelayedEvents[n].Time := gTime + Time;
   Result := n;
@@ -904,11 +1214,12 @@ procedure EndGame();
 var
   a: Integer;
   FileName: string;
+  t: TDateTime;
 begin
   if g_Game_IsNet and g_Game_IsServer then
     MH_SEND_GameEvent(NET_EV_MAPEND, Byte(gMissionFailed));
 
-// Ñòîï èãðà:
+// Стоп игра:
   gPauseMain := false;
   gPauseHolmes := false;
   gGameOn := false;
@@ -925,16 +1236,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
@@ -943,7 +1254,7 @@ begin
               gState := STATE_MENU;
             end else
             begin
-              // Îáíîâëÿåì ñïèñîê ñåðâåðîâ
+              // Обновляем список серверов
               slReturnPressed := True;
               if g_Net_Slist_Fetch(slCurrent) then
               begin
@@ -959,14 +1270,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;
@@ -978,7 +1289,7 @@ begin
 
         CustomStat.PlayerStat := nil;
 
-      // Ñòàòèñòèêà èãðîêîâ:
+      // Статистика игроков:
         if gPlayers <> nil then
         begin
           for a := 0 to High(gPlayers) do
@@ -999,13 +1310,26 @@ begin
             end;
 
           SortGameStat(CustomStat.PlayerStat);
+
+          if (gSaveStats or gScreenshotStats) and (Length(CustomStat.PlayerStat) > 1) then
+          begin
+            t := Now;
+            if g_Game_IsNet then StatFilename := NetServerName else StatFilename := 'local';
+            StatDate := FormatDateTime('yymmdd_hhnnss', t);
+            StatFilename := StatFilename + '_' + CustomStat.Map + '_' + g_Game_ModeToText(CustomStat.GameMode);
+            StatFilename := sanitizeFilename(StatFilename) + '_' + StatDate;
+            if gSaveStats then
+              SaveGameStat(CustomStat, FormatDateTime('yyyy"/"mm"/"dd', t));
+          end;
+
+          StatShotDone := False;
         end;
 
         g_Game_ExecuteEvent('onmapend');
         if not g_Game_IsClient then g_Player_ResetReady;
         gInterReadyCount := 0;
 
-      // Çàòóõàþùèé ýêðàí:
+      // Затухающий экран:
         EndingGameCounter := 255;
         gState := STATE_FOLD;
         gInterTime := 0;
@@ -1015,16 +1339,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;
@@ -1033,7 +1357,7 @@ begin
 
         g_Game_ExecuteEvent('onmapend');
 
-      // Åñòü åùå êàðòû:
+      // Есть еще карты:
         if gNextMap <> '' then
           begin
             gMusic.SetByName('MUSIC_INTERMUS');
@@ -1043,312 +1367,54 @@ 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;
+procedure g_Game_Init();
 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;
+  gExit := 0;
+  gMapToDelete := '';
+  gTempDelete := False;
 
-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);
+  sfsGCDisable(); // temporary disable removing of temporary volumes
 
-  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);
+  try
+    g_Game_ClearLoading();
+    g_Game_SetLoadingText(Format('Doom 2D: Forever %s', [GAME_VERSION]), 0, False);
+    g_Game_SetLoadingText('', 0, False);
 
-  drawTime(x+w-78, y+8);
+//    g_Game_SetLoadingText(_lc[I_LOAD_MODELS], 0, False);
 
-  wad := g_ExtractWadNameNoPath(gMapInfo.Map);
-  map := g_ExtractFileName(gMapInfo.Map);
-  mapstr := wad + ':\' + map + ' - ' + gMapInfo.Name;
+    gGameOn := false;
+    gPauseMain := false;
+    gPauseHolmes := false;
+    gTime := 0;
 
-  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;
+    {e_MouseInfo.Accel := 1.0;}
 
-    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;
+    g_Game_SetLoadingText(_lc[I_LOAD_GAME_DATA], 0, False);
+    g_Game_LoadData();
 
-    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;
+    g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
+    g_Sound_CreateWADEx('MUSIC_INTERMUS', GameWAD+':MUSIC\INTERMUS', True);
+    g_Sound_CreateWADEx('MUSIC_MENU', GameWAD+':MUSIC\MENU', True);
+    g_Sound_CreateWADEx('MUSIC_ROUNDMUS', GameWAD+':MUSIC\ROUNDMUS', True, True);
+    g_Sound_CreateWADEx('MUSIC_STDENDMUS', GameWAD+':MUSIC\ENDMUS', True);
 
-    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;
-begin
-  gExit := 0;
-  gMapToDelete := '';
-  gTempDelete := False;
-
-  sfsGCDisable(); // temporary disable removing of temporary volumes
-
-  try
-    TEXTUREFILTER := GL_LINEAR;
-    g_Texture_CreateWADEx('MENU_BACKGROUND', GameWAD+':TEXTURES\TITLE');
-    g_Texture_CreateWADEx('INTER', GameWAD+':TEXTURES\INTER');
-    g_Texture_CreateWADEx('ENDGAME_EN', GameWAD+':TEXTURES\ENDGAME_EN');
-    g_Texture_CreateWADEx('ENDGAME_RU', GameWAD+':TEXTURES\ENDGAME_RU');
-    TEXTUREFILTER := GL_NEAREST;
-
-    LoadStdFont('STDTXT', 'STDFONT', gStdFont);
-    LoadFont('MENUTXT', 'MENUFONT', gMenuFont);
-    LoadFont('SMALLTXT', 'SMALLFONT', gMenuSmallFont);
-
-    g_Game_ClearLoading();
-    g_Game_SetLoadingText(Format('Doom 2D: Forever %s', [GAME_VERSION]), 0, False);
-    g_Game_SetLoadingText('', 0, False);
-
-    g_Game_SetLoadingText(_lc[I_LOAD_CONSOLE], 0, False);
-    g_Console_Init();
-
-    g_Game_SetLoadingText(_lc[I_LOAD_MODELS], 0, False);
-    g_PlayerModel_LoadData();
-
-    if FindFirst(ModelsDir+'*.wad', faAnyFile, SR) = 0 then
-      repeat
-        if not g_PlayerModel_Load(ModelsDir+SR.Name) then
-          e_WriteLog(Format('Error loading model %s', [SR.Name]), TMsgType.Warning);
-      until FindNext(SR) <> 0;
-    FindClose(SR);
-
-    if FindFirst(ModelsDir+'*.pk3', faAnyFile, SR) = 0 then
-      repeat
-        if not g_PlayerModel_Load(ModelsDir+SR.Name) then
-          e_WriteLog(Format('Error loading model %s', [SR.Name]), TMsgType.Warning);
-      until FindNext(SR) <> 0;
-    FindClose(SR);
-
-    if FindFirst(ModelsDir+'*.zip', faAnyFile, SR) = 0 then
-      repeat
-        if not g_PlayerModel_Load(ModelsDir+SR.Name) then
-          e_WriteLog(Format('Error loading model %s', [SR.Name]), TMsgType.Warning);
-      until FindNext(SR) <> 0;
-    FindClose(SR);
-
-    gGameOn := false;
-    gPauseMain := false;
-    gPauseHolmes := false;
-    gTime := 0;
-
-    {e_MouseInfo.Accel := 1.0;}
-
-    g_Game_SetLoadingText(_lc[I_LOAD_GAME_DATA], 0, False);
-    g_Game_LoadData();
-
-    g_Game_SetLoadingText(_lc[I_LOAD_MUSIC], 0, False);
-    g_Sound_CreateWADEx('MUSIC_INTERMUS', GameWAD+':MUSIC\INTERMUS', True);
-    g_Sound_CreateWADEx('MUSIC_MENU', GameWAD+':MUSIC\MENU', True);
-    g_Sound_CreateWADEx('MUSIC_ROUNDMUS', GameWAD+':MUSIC\ROUNDMUS', True, True);
-    g_Sound_CreateWADEx('MUSIC_STDENDMUS', GameWAD+':MUSIC\ENDMUS', True);
-
-{$IFNDEF HEADLESS}
-    g_Game_SetLoadingText(_lc[I_LOAD_MENUS], 0, False);
-    g_Menu_Init();
-{$ENDIF}
-
-    gMusic := TMusic.Create();
-    gMusic.SetByName('MUSIC_MENU');
-    gMusic.Play();
+    gMusic := TMusic.Create();
+    gMusic.SetByName('MUSIC_MENU');
+    gMusic.Play();
 
     gGameSettings.WarmupTime := 30;
 
@@ -1392,25 +1458,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;
@@ -1528,13 +1575,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
@@ -1555,13 +1602,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;
@@ -1569,7 +1616,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);
@@ -1614,6 +1661,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;
@@ -1653,10 +1711,10 @@ begin
   g_ResetDynlights();
   framePool.reset();
 
-// Ïîðà âûêëþ÷àòü èãðó:
+// Пора выключать игру:
   if gExit = EXIT_QUIT then
     Exit;
-// Èãðà çàêîí÷èëàñü - îáðàáàòûâàåì:
+// Игра закончилась - обрабатываем:
   if gExit <> 0 then
   begin
     EndGame();
@@ -1664,10 +1722,10 @@ begin
       Exit;
   end;
 
-  // ×èòàåì êëàâèàòóðó è äæîéñòèê, åñëè îêíî àêòèâíî
+  // Читаем клавиатуру и джойстик, если окно активно
   // no need to, as we'll do it in event handler
 
-// Îáíîâëÿåì êîíñîëü (äâèæåíèå è ñîîáùåíèÿ):
+// Обновляем консоль (движение и сообщения):
   g_Console_Update();
 
   if (NetMode = NET_NONE) and (g_Game_IsNet) and (gGameOn or (gState in [STATE_FOLD, STATE_INTERCUSTOM])) then
@@ -1677,11 +1735,14 @@ begin
     Exit;
   end;
 
+  // process master server communications
+  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
@@ -1706,22 +1767,22 @@ begin
             and (not gJustChatted) and (not gConsoleShow) and (not gChatShow)
             and (g_ActiveWindow = nil)
           )
-          or (g_Game_IsNet and ((gInterTime > gInterEndTime) or (gInterReadyCount >= NetClientCount)))
+          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');
@@ -1729,7 +1790,7 @@ begin
                 gState := STATE_MENU;
               end else
               begin
-              // Ôèíàëüíàÿ êàðòèíêà:
+              // Финальная картинка:
                 g_Game_ExecuteEvent('onwadend');
                 g_Game_Free();
                 if not gMusic.SetByName('MUSIC_endmus') then
@@ -1764,11 +1825,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;
@@ -1785,7 +1846,7 @@ begin
                 gState := STATE_INTERCUSTOM;
                 e_UnpressAllKeys();
               end
-            else // Çàêîí÷èëàñü ïîñëåäíÿÿ êàðòà â Îäèíî÷íîé èãðå
+            else // Закончилась последняя карта в Одиночной игре
               begin
                 gMusic.SetByName('MUSIC_INTERMUS');
                 gMusic.Play();
@@ -1798,9 +1859,9 @@ begin
           DecMin(EndingGameCounter, 6, 0);
       end;
 
-    STATE_ENDPIC: // Êàðòèíêà îêîí÷àíèÿ ìåãàÂàäà
+    STATE_ENDPIC: // Картинка окончания мегаВада
       begin
-        if gMapOnce then // Ýòî áûë òåñò
+        if gMapOnce then // Это был тест
         begin
           gExit := EXIT_SIMPLE;
           Exit;
@@ -1811,17 +1872,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
@@ -1829,19 +1890,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
@@ -1851,19 +1912,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
@@ -1871,11 +1932,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();
@@ -1883,7 +1944,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
@@ -1898,7 +1959,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
@@ -2057,7 +2118,7 @@ begin
       end;
     end;
 
-  // Îáíîâëÿåì âñå îñòàëüíîå:
+  // Обновляем все остальное:
     g_Map_Update();
     g_Items_Update();
     g_Triggers_Update();
@@ -2124,19 +2185,18 @@ begin
       // send unexpected platform changes
       g_Map_NetSendInterestingPanels();
 
+      g_Net_Slist_ServerUpdate();
+      {
       if NetUseMaster then
       begin
-        if gTime >= NetTimeToMaster then
+        if (gTime >= NetTimeToMaster) or g_Net_Slist_IsConnectionInProgress then
         begin
-          if (NetMHost = nil) or (NetMPeer = nil) then
-          begin
-            g_Net_Slist_Connect(false); // non-blocking connection to the master
-          end;
-
+          if (not g_Net_Slist_IsConnectionActive) then g_Net_Slist_Connect(false); // non-blocking connection to the master
           g_Net_Slist_Update;
           NetTimeToMaster := gTime + NetMasterRate;
         end;
       end;
+      }
     end
     else if (NetMode = NET_CLIENT) then
     begin
@@ -2144,7 +2204,7 @@ begin
     end;
   end; // if gameOn ...
 
-// Àêòèâíî îêíî èíòåðôåéñà - ïåðåäàåì êëàâèøè åìó:
+// Активно окно интерфейса - передаем клавиши ему:
   if g_ActiveWindow <> nil then
   begin
     w := e_GetFirstKeyPressed();
@@ -2156,20 +2216,22 @@ begin
         g_ActiveWindow.OnMessage(Msg);
       end;
 
-  // Åñëè îíî îò ýòîãî íå çàêðûëîñü, òî îáíîâëÿåì:
+  // Если оно от этого не закрылось, то обновляем:
     if g_ActiveWindow <> nil then
       g_ActiveWindow.Update();
 
-  // Íóæíî ñìåíèòü ðàçðåøåíèå:
+  // Нужно сменить разрешение:
     if gResolutionChange then
     begin
-      e_WriteLog('Changing resolution', TMsgType.Notify);
-      g_Game_ChangeResolution(gRC_Width, gRC_Height, gRC_FullScreen, gRC_Maximized);
+      {$IFNDEF HEADLESS}
+        e_WriteLog('Changing resolution', TMsgType.Notify);
+        r_Render_Apply;
+      {$ENDIF}
       gResolutionChange := False;
       g_ActiveWindow := nil;
     end;
 
-  // Íóæíî ñìåíèòü ÿçûê:
+  // Нужно сменить язык:
     if gLanguageChange then
     begin
       //e_WriteLog('Read language file', MSG_NOTIFY);
@@ -2182,7 +2244,7 @@ begin
     end;
   end;
 
-// Ãîðÿ÷àÿ êëàâèøà äëÿ âûçîâà ìåíþ âûõîäà èç èãðû (F10):
+// Горячая клавиша для вызова меню выхода из игры (F10):
   if e_KeyPressed(IK_F10) and
      gGameOn and
      (not gConsoleShow) and
@@ -2191,9 +2253,9 @@ begin
     KeyPress(IK_F10);
   end;
 
-  Time := GetTimer() {div 1000};
+  Time := GetTickCount64() {div 1000};
 
-// Îáðàáîòêà îòëîæåííûõ ñîáûòèé:
+// Обработка отложенных событий:
   if gDelayedEvents <> nil then
     for a := 0 to High(gDelayedEvents) do
       if gDelayedEvents[a].Pending and
@@ -2222,7 +2284,7 @@ begin
         gDelayedEvents[a].Pending := False;
       end;
 
-// Êàæäóþ ñåêóíäó îáíîâëÿåì ñ÷åò÷èê îáíîâëåíèé:
+// Каждую секунду обновляем счетчик обновлений:
   UPSCounter := UPSCounter + 1;
   if Time - UPSTime >= 1000 then
   begin
@@ -2296,61 +2358,11 @@ begin
 end;
 
 procedure g_Game_LoadData();
-var
-  wl, hl: Integer;
-  wr, hr: Integer;
-  wb, hb: Integer;
-  wm, hm: Integer;
 begin
   if DataLoaded then Exit;
 
   e_WriteLog('Loading game data...', TMsgType.Notify);
 
-  g_Texture_CreateWADEx('NOTEXTURE', GameWAD+':TEXTURES\NOTEXTURE');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_HUD', GameWAD+':TEXTURES\HUD');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_HUDAIR', GameWAD+':TEXTURES\AIRBAR');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_HUDJET', GameWAD+':TEXTURES\JETBAR');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_HUDBG', GameWAD+':TEXTURES\HUDBG');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_ARMORHUD', GameWAD+':TEXTURES\ARMORHUD');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG', GameWAD+':TEXTURES\FLAGHUD_R_BASE');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG_S', GameWAD+':TEXTURES\FLAGHUD_R_STOLEN');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_REDFLAG_D', GameWAD+':TEXTURES\FLAGHUD_R_DROP');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG', GameWAD+':TEXTURES\FLAGHUD_B_BASE');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG_S', GameWAD+':TEXTURES\FLAGHUD_B_STOLEN');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_BLUEFLAG_D', GameWAD+':TEXTURES\FLAGHUD_B_DROP');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_TALKBUBBLE', GameWAD+':TEXTURES\TALKBUBBLE');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_INVULPENTA', GameWAD+':TEXTURES\PENTA');
-  g_Texture_CreateWADEx('TEXTURE_PLAYER_INDICATOR', GameWAD+':TEXTURES\PLRIND');
-
-  hasPBarGfx := true;
-  if not g_Texture_CreateWADEx('UI_GFX_PBAR_LEFT', GameWAD+':TEXTURES\LLEFT') then hasPBarGfx := false;
-  if not g_Texture_CreateWADEx('UI_GFX_PBAR_MARKER', GameWAD+':TEXTURES\LMARKER') then hasPBarGfx := false;
-  if not g_Texture_CreateWADEx('UI_GFX_PBAR_MIDDLE', GameWAD+':TEXTURES\LMIDDLE') then hasPBarGfx := false;
-  if not g_Texture_CreateWADEx('UI_GFX_PBAR_RIGHT', GameWAD+':TEXTURES\LRIGHT') then hasPBarGfx := false;
-
-  if hasPBarGfx then
-  begin
-    g_Texture_GetSize('UI_GFX_PBAR_LEFT', wl, hl);
-    g_Texture_GetSize('UI_GFX_PBAR_RIGHT', wr, hr);
-    g_Texture_GetSize('UI_GFX_PBAR_MIDDLE', wb, hb);
-    g_Texture_GetSize('UI_GFX_PBAR_MARKER', wm, hm);
-    if (wl > 0) and (hl > 0) and (wr > 0) and (hr = hl) and (wb > 0) and (hb = hl) and (wm > 0) and (hm > 0) and (hm <= hl) then
-    begin
-      // yay!
-    end
-    else
-    begin
-      hasPBarGfx := false;
-    end;
-  end;
-
-  g_Frames_CreateWAD(nil, 'FRAMES_TELEPORT', GameWAD+':TEXTURES\TELEPORT', 64, 64, 10, False);
-  g_Frames_CreateWAD(nil, 'FRAMES_PUNCH', GameWAD+':WEAPONS\PUNCH', 64, 64, 4, False);
-  g_Frames_CreateWAD(nil, 'FRAMES_PUNCH_UP', GameWAD+':WEAPONS\PUNCH_UP', 64, 64, 4, False);
-  g_Frames_CreateWAD(nil, 'FRAMES_PUNCH_DN', GameWAD+':WEAPONS\PUNCH_DN', 64, 64, 4, False);
-  g_Frames_CreateWAD(nil, 'FRAMES_PUNCH_BERSERK', GameWAD+':WEAPONS\PUNCHB', 64, 64, 4, False);
-  g_Frames_CreateWAD(nil, 'FRAMES_PUNCH_BERSERK_UP', GameWAD+':WEAPONS\PUNCHB_UP', 64, 64, 4, False);
-  g_Frames_CreateWAD(nil, 'FRAMES_PUNCH_BERSERK_DN', GameWAD+':WEAPONS\PUNCHB_DN', 64, 64, 4, False);
   g_Sound_CreateWADEx('SOUND_GAME_TELEPORT', GameWAD+':SOUNDS\TELEPORT');
   g_Sound_CreateWADEx('SOUND_GAME_NOTELEPORT', GameWAD+':SOUNDS\NOTELEPORT');
   g_Sound_CreateWADEx('SOUND_GAME_SECRET', GameWAD+':SOUNDS\SECRET');
@@ -2443,6 +2455,26 @@ begin
   DataLoaded := True;
 end;
 
+procedure g_Game_Quit();
+begin
+  g_Game_StopAllSounds(True);
+  gMusic.Free();
+  g_Game_FreeData();
+  g_PlayerModel_FreeData();
+{$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;
@@ -2453,25 +2485,6 @@ begin
 
   e_WriteLog('Releasing game data...', TMsgType.Notify);
 
-  g_Texture_Delete('NOTEXTURE');
-  g_Texture_Delete('TEXTURE_PLAYER_HUD');
-  g_Texture_Delete('TEXTURE_PLAYER_HUDBG');
-  g_Texture_Delete('TEXTURE_PLAYER_ARMORHUD');
-  g_Texture_Delete('TEXTURE_PLAYER_REDFLAG');
-  g_Texture_Delete('TEXTURE_PLAYER_REDFLAG_S');
-  g_Texture_Delete('TEXTURE_PLAYER_REDFLAG_D');
-  g_Texture_Delete('TEXTURE_PLAYER_BLUEFLAG');
-  g_Texture_Delete('TEXTURE_PLAYER_BLUEFLAG_S');
-  g_Texture_Delete('TEXTURE_PLAYER_BLUEFLAG_D');
-  g_Texture_Delete('TEXTURE_PLAYER_TALKBUBBLE');
-  g_Texture_Delete('TEXTURE_PLAYER_INVULPENTA');
-  g_Frames_DeleteByName('FRAMES_TELEPORT');
-  g_Frames_DeleteByName('FRAMES_PUNCH');
-  g_Frames_DeleteByName('FRAMES_PUNCH_UP');
-  g_Frames_DeleteByName('FRAMES_PUNCH_DN');
-  g_Frames_DeleteByName('FRAMES_PUNCH_BERSERK');
-  g_Frames_DeleteByName('FRAMES_PUNCH_BERSERK_UP');
-  g_Frames_DeleteByName('FRAMES_PUNCH_BERSERK_DN');
   g_Sound_Delete('SOUND_GAME_TELEPORT');
   g_Sound_Delete('SOUND_GAME_NOTELEPORT');
   g_Sound_Delete('SOUND_GAME_SECRET');
@@ -2536,1708 +2549,93 @@ 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);
+  g_Console_Add(Format(_lc[I_FATAL_ERROR], [Text]), True);
+  e_WriteLog(Format(_lc[I_FATAL_ERROR], [Text]), TMsgType.Warning);
 
-  g_ProcessMessages();
+  gExit := EXIT_SIMPLE;
+  if gGameOn then EndGame;
+end;
 
-  if g_Console_Action(ACTION_SCORES) then
-  begin
-    if not gStatsPressed then
-    begin
-      gStatsOff := not gStatsOff;
-      gStatsPressed := True;
-    end;
-  end
-  else
-    gStatsPressed := False;
+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 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);
+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;
-  end;
-
-  if (gGameSettings.GameMode = GM_COOP) then
-  begin
-    if gMissionFailed then
-      topstr := _lc[I_MENU_INTER_MISSION_FAIL]
-    else
-      topstr := _lc[I_MENU_INTER_LEVEL_COMPLETE];
-  end
-  else
-    topstr := _lc[I_MENU_INTER_ROUND_OVER];
 
-  e_CharFont_GetSize(gMenuFont, topstr, ww1, hh1);
-  e_CharFont_Print(gMenuFont, (gScreenWidth div 2)-(ww1 div 2), 16, topstr);
+  if (gGameSettings.MaxLives > 0) and (gLMSRespawn = LMS_RESPAWN_NONE) then
+    Exit;
 
-  if g_Game_IsNet then
+  if gPlayer1 = nil 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:
+    if g_Game_IsClient then
     begin
-      if gGameSettings.MaxLives = 0 then
-        s1 := _lc[I_GAME_TDM]
-      else
-        s1 := _lc[I_GAME_TLMS];
+      if NetPlrUID1 > -1 then
+        MC_SEND_CheatRequest(NET_CHEAT_SPECTATE);
+      Exit;
     end;
-    GM_CTF: s1 := _lc[I_GAME_CTF];
-    GM_COOP:
+
+    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));
+    if gPlayer1 = nil then
+      g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [1]))
+    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);
+      gPlayer1.Name := gPlayer1Settings.Name;
+      g_Console_Add(Format(_lc[I_PLAYER_JOIN], [gPlayer1.Name]), True);
+      if g_Game_IsServer and g_Game_IsNet then
+        MH_SEND_PlayerCreate(gPlayer1.UID);
+      gPlayer1.Respawn(False, True);
+      g_Net_Slist_ServerPlayerComes();
     end;
-  end;
 
-  if CustomStat.GameMode in [GM_TDM, GM_CTF] then
+    Exit;
+  end;
+  if gPlayer2 = nil then
   begin
-    _y := _y+16+16;
-
-    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];
-
-    e_TextureFontPrintEx(x+8+(w div 2)-(Length(s1)*ww2 div 2), _y, s1, gStdFont, 255, 255, 255, 1);
-    _y := _y+40;
-
-    for t := TEAM_RED to TEAM_BLUE do
+    if g_Game_IsClient then
     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;
+      if NetPlrUID2 > -1 then
+        gPlayer2 := g_Player_Get(NetPlrUID2);
+      Exit;
     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 not (Team in [TEAM_RED, TEAM_BLUE]) then
+      Team := gPlayer2Settings.Team;
 
-        if Spectator then
-          r := 127
-        else
-          r := 255;
+    // Создание второго игрока:
+    gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
+                                             gPlayer2Settings.Color,
+                                             Team, False));
+    if gPlayer2 = nil then
+      g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [2]))
+    else
+    begin
+      gPlayer2.Name := gPlayer2Settings.Name;
+      g_Console_Add(Format(_lc[I_PLAYER_JOIN], [gPlayer2.Name]), True);
+      if g_Game_IsServer and g_Game_IsNet then
+        MH_SEND_PlayerCreate(gPlayer2.UID);
+      gPlayer2.Respawn(False, True);
+      g_Net_Slist_ServerPlayerComes();
+    end;
 
-        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;
+    Exit;
   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, gWinSizeX, gWinSizeY);
-    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;
-
-  if p.IncCam <> 0 then
-  begin
-    if py > gMapInfo.Height-(gPlayerScreenSize.Y div 2) then
-    begin
-      if p.IncCam > 120-(py-(gMapInfo.Height-(gPlayerScreenSize.Y div 2))) then
-      begin
-        p.IncCam := 120-(py-(gMapInfo.Height-(gPlayerScreenSize.Y div 2)));
-      end;
-    end;
-
-    if py < gPlayerScreenSize.Y div 2 then
-    begin
-      if p.IncCam < -120+((gPlayerScreenSize.Y div 2)-py) then
-      begin
-        p.IncCam := -120+((gPlayerScreenSize.Y div 2)-py);
-      end;
-    end;
-
-    if p.IncCam < 0 then
-    begin
-      while (py+(gPlayerScreenSize.Y div 2)-p.IncCam > gMapInfo.Height) and (p.IncCam < 0) do p.IncCam := p.IncCam+1; //Inc(p.IncCam);
-    end;
-
-    if p.IncCam > 0 then
-    begin
-      while (py-(gPlayerScreenSize.Y div 2)-p.IncCam < 0) and (p.IncCam > 0) do p.IncCam := p.IncCam-1; //Dec(p.IncCam);
-    end;
-  end;
-
-       if (px < gPlayerScreenSize.X div 2) or (gMapInfo.Width-gPlayerScreenSize.X <= 256) then c := 0
-  else if (px > gMapInfo.Width-(gPlayerScreenSize.X div 2)) then c := gBackSize.X-gPlayerScreenSize.X
-  else c := round((px-(gPlayerScreenSize.X div 2))/(gMapInfo.Width-gPlayerScreenSize.X)*(gBackSize.X-gPlayerScreenSize.X));
-
-       if (py-p.IncCam <= gPlayerScreenSize.Y div 2) or (gMapInfo.Height-gPlayerScreenSize.Y <= 256) then d := 0
-  else if (py-p.IncCam >= gMapInfo.Height-(gPlayerScreenSize.Y div 2)) then d := gBackSize.Y-gPlayerScreenSize.Y
-  else d := round((py-p.IncCam-(gPlayerScreenSize.Y div 2))/(gMapInfo.Height-gPlayerScreenSize.Y)*(gBackSize.Y-gPlayerScreenSize.Y));
-
-  sX := -a;
-  sY := -(b+p.IncCam);
-  sWidth := gPlayerScreenSize.X;
-  sHeight := gPlayerScreenSize.Y;
-
-  //glTranslatef(a, b+p.IncCam, 0);
-
-  //if (p = gPlayer1) and (g_dbg_scale >= 1.0) then g_Holmes_plrViewSize(sWidth, sHeight);
-
-  //conwritefln('OLD: (%s,%s)-(%s,%s)', [sX, sY, sWidth, sHeight]);
-  fixViewportForScale();
-  //conwritefln('     (%s,%s)-(%s,%s)', [sX, sY, sWidth, sHeight]);
-
-  if (g_dbg_scale <> 1.0) and (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;
-
-    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));
-  end;
-
-  //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 := GetTimer() {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_SaveOptions();
-  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;
-  PushExitEvent();
-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
-  g_Window_SetSize(newWidth, newHeight, nowFull);
-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));
-    if gPlayer1 = nil then
-      g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [1]))
-    else
-    begin
-      gPlayer1.Name := gPlayer1Settings.Name;
-      g_Console_Add(Format(_lc[I_PLAYER_JOIN], [gPlayer1.Name]), True);
-      if g_Game_IsServer and g_Game_IsNet then
-        MH_SEND_PlayerCreate(gPlayer1.UID);
-      gPlayer1.Respawn(False, True);
-
-      if g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
-    end;
-
-    Exit;
-  end;
-  if gPlayer2 = nil then
-  begin
-    if g_Game_IsClient then
-    begin
-      if NetPlrUID2 > -1 then
-        gPlayer2 := g_Player_Get(NetPlrUID2);
-      Exit;
-    end;
-
-    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));
-    if gPlayer2 = nil then
-      g_FatalError(Format(_lc[I_GAME_ERROR_PLAYER_CREATE], [2]))
-    else
-    begin
-      gPlayer2.Name := gPlayer2Settings.Name;
-      g_Console_Add(Format(_lc[I_PLAYER_JOIN], [gPlayer2.Name]), True);
-      if g_Game_IsServer and g_Game_IsNet then
-        MH_SEND_PlayerCreate(gPlayer2.UID);
-      gPlayer2.Respawn(False, True);
-
-      if g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
-    end;
-
-    Exit;
-  end;
-end;
-
-procedure g_Game_RemovePlayer();
+procedure g_Game_RemovePlayer();
 var
   Pl: TPlayer;
 begin
@@ -4253,11 +2651,13 @@ begin
       Pl.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
       g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [Pl.Name]), True);
       g_Player_Remove(Pl.UID);
-
-      if g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
-    end else
+      g_Net_Slist_ServerPlayerLeaves();
+    end
+    else
+    begin
+      gSpectLatchPID2 := Pl.UID;
       gPlayer2 := nil;
+    end;
     Exit;
   end;
   Pl := gPlayer1;
@@ -4269,16 +2669,16 @@ begin
       Pl.Kill(K_SIMPLEKILL, 0, HIT_DISCON);
       g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [Pl.Name]), True);
       g_Player_Remove(Pl.UID);
-
-      if g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
+      g_Net_Slist_ServerPlayerLeaves();
     end else
     begin
+      gSpectLatchPID1 := Pl.UID;
       gPlayer1 := nil;
       MC_SEND_CheatRequest(NET_CHEAT_SPECTATE);
     end;
     Exit;
   end;
+  g_Net_Slist_ServerPlayerLeaves();
 end;
 
 procedure g_Game_Spectate();
@@ -4305,7 +2705,7 @@ begin
 
   g_Game_ClearLoading();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   FillByte(gGameSettings, SizeOf(TGameSettings), 0);
   gAimLine := False;
   gShowMap := False;
@@ -4314,14 +2714,18 @@ 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;
 
-  g_Game_ExecuteEvent('ongamestart');
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
 
-// Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
-  g_Game_SetupScreenSize();
+  g_Game_ExecuteEvent('ongamestart');
 
-// Ñîçäàíèå ïåðâîãî èãðîêà:
+// Создание первого игрока:
   gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                            gPlayer1Settings.Color,
                                            gPlayer1Settings.Team, False));
@@ -4334,7 +2738,7 @@ begin
   gPlayer1.Name := gPlayer1Settings.Name;
   nPl := 1;
 
-// Ñîçäàíèå âòîðîãî èãðîêà, åñëè åñòü:
+// Создание второго игрока, если есть:
   if TwoPlayers then
   begin
     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
@@ -4350,18 +2754,18 @@ begin
     Inc(nPl);
   end;
 
-// Çàãðóçêà è çàïóñê êàðòû:
-  if not g_Game_StartMap(MAP, True) then
+// Загрузка и запуск карты:
+  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;
     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [tmps]));
     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;
@@ -4379,7 +2783,7 @@ begin
 
   g_Game_ClearLoading();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   gGameSettings.GameType := GT_CUSTOM;
   gGameSettings.GameMode := GameMode;
   gSwitchGameMode := GameMode;
@@ -4395,12 +2799,14 @@ begin
   gAimLine := False;
   gShowMap := False;
 
-  g_Game_ExecuteEvent('ongamestart');
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
 
-// Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
-  g_Game_SetupScreenSize();
+  g_Game_ExecuteEvent('ongamestart');
 
-// Ðåæèì íàáëþäàòåëÿ:
+// Ð ÐµÐ¶Ð¸Ð¼ Ð½Ð°Ð±Ð»Ñ\8eдаÑ\82елÑ\8f:
   if nPlayers = 0 then
   begin
     gPlayer1 := nil;
@@ -4410,7 +2816,7 @@ begin
   nPl := 0;
   if nPlayers >= 1 then
   begin
-  // Ñîçäàíèå ïåðâîãî èãðîêà:
+  // Создание первого игрока:
     gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                              gPlayer1Settings.Color,
                                              gPlayer1Settings.Team, False));
@@ -4426,7 +2832,7 @@ begin
 
   if nPlayers >= 2 then
   begin
-  // Ñîçäàíèå âòîðîãî èãðîêà:
+  // Создание второго игрока:
     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
                                              gPlayer2Settings.Color,
                                              gPlayer2Settings.Team, False));
@@ -4440,14 +2846,14 @@ begin
     Inc(nPl);
   end;
 
-// Çàãðóçêà è çàïóñê êàðòû:
-  if not g_Game_StartMap(Map, True) then
+// Загрузка и запуск карты:
+  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) +
@@ -4458,10 +2864,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;
@@ -4472,12 +2878,13 @@ procedure g_Game_StartServer(Map: String; GameMode: Byte;
                              IPAddr: LongWord; Port: Word);
 begin
   g_Game_Free();
+  g_Net_Slist_ServerClosed();
 
   e_WriteLog('Starting net game (server)...', TMsgType.Notify);
 
   g_Game_ClearLoading();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   gGameSettings.GameType := GT_SERVER;
   gGameSettings.GameMode := GameMode;
   gSwitchGameMode := GameMode;
@@ -4493,12 +2900,14 @@ begin
   gAimLine := False;
   gShowMap := False;
 
-  g_Game_ExecuteEvent('ongamestart');
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
 
-// Óñòàíîâêà ðàçìåðîâ îêíà èãðîêà
-  g_Game_SetupScreenSize();
+  g_Game_ExecuteEvent('ongamestart');
 
-// Ðåæèì íàáëþäàòåëÿ:
+// Ð ÐµÐ¶Ð¸Ð¼ Ð½Ð°Ð±Ð»Ñ\8eдаÑ\82елÑ\8f:
   if nPlayers = 0 then
   begin
     gPlayer1 := nil;
@@ -4507,7 +2916,7 @@ begin
 
   if nPlayers >= 1 then
   begin
-  // Ñîçäàíèå ïåðâîãî èãðîêà:
+  // Создание первого игрока:
     gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                              gPlayer1Settings.Color,
                                              gPlayer1Settings.Team, False));
@@ -4522,7 +2931,7 @@ begin
 
   if nPlayers >= 2 then
   begin
-  // Ñîçäàíèå âòîðîãî èãðîêà:
+  // Создание второго игрока:
     gPlayer2 := g_Player_Get(g_Player_Create(gPlayer2Settings.Model,
                                              gPlayer2Settings.Color,
                                              gPlayer2Settings.Team, False));
@@ -4539,36 +2948,41 @@ 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] + _lc[I_NET_ERR_HOST]);
+    g_FatalError(_lc[I_NET_MSG] + Format(_lc[I_NET_ERR_HOST], [Port]));
     Exit;
   end;
 
-  g_Net_Slist_Set(NetSlistIP, NetSlistPort);
+  g_Net_Slist_Set(NetMasterList);
 
-// Çàãðóçêà è çàïóñê êàðòû:
-  if not g_Game_StartMap(Map, True) then
+  g_Net_Slist_ServerStarted();
+
+// Загрузка и запуск карты:
+  if not g_Game_StartMap(false{asMegawad}, Map, True) then
   begin
+    g_Net_Slist_ServerClosed();
     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) +
       g_Map_GetPointCount(RESPAWNPOINT_RED)+
       g_Map_GetPointCount(RESPAWNPOINT_BLUE)) < 1 then
   begin
+    g_Net_Slist_ServerClosed();
     g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
     Exit;
   end;
 
-// Íàñòðîéêè èãðîêîâ è áîòîâ:
+// Настройки игроков и ботов:
   g_Player_Init();
 
+  g_Net_Slist_ServerMapStarted();
   NetState := NET_STATE_GAME;
 end;
 
@@ -4592,7 +3006,7 @@ begin
 
   g_Game_ClearLoading();
 
-// Íàñòðîéêè èãðû:
+// Настройки игры:
   gGameSettings.GameType := GT_CLIENT;
 
   gCoopTotalMonstersKilled := 0;
@@ -4604,13 +3018,19 @@ begin
 
   g_Game_ExecuteEvent('ongamestart');
 
-// Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
-  g_Game_SetupScreenSize();
-
   NetState := NET_STATE_AUTH;
 
   g_Game_SetLoadingText(_lc[I_LOAD_CONNECT], 0, False);
-// Ñòàðòóåì êëèåíò
+
+  // 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]);
@@ -4625,13 +3045,25 @@ begin
   OuterLoop := True;
   while OuterLoop do
   begin
-    while (enet_host_service(NetHost, @NetEvent, 0) > 0) do
+    // fuck! https://www.mail-archive.com/enet-discuss@cubik.org/msg00852.html
+    // tl;dr: on shitdows, we can get -1 sometimes, and it is *NOT* a failure.
+    //        thank you, enet. let's ignore failures altogether then.
+    while (enet_host_service(NetHost, @NetEvent, 50) > 0) do
     begin
       if (NetEvent.kind = ENET_EVENT_TYPE_RECEIVE) then
       begin
+        if (NetEvent.channelID = NET_CHAN_DOWNLOAD_EX) then
+        begin
+          // ignore all download packets, they're processed by separate code
+          enet_packet_destroy(NetEvent.packet);
+          continue;
+        end;
         Ptr := NetEvent.packet^.data;
         if not InMsg.Init(Ptr, NetEvent.packet^.dataLength, True) then
+        begin
+          enet_packet_destroy(NetEvent.packet);
           continue;
+        end;
 
         InMsg.ReadLongWord(); // skip size
         MID := InMsg.ReadByte();
@@ -4654,11 +3086,11 @@ begin
           gGameSettings.Options := InMsg.ReadLongWord();
           T := InMsg.ReadLongWord();
 
-          newResPath := g_Res_SearchSameWAD(MapsDir, WadName, gWADHash);
-          if newResPath = '' then
+          //newResPath := g_Res_SearchSameWAD(MapsDir, WadName, gWADHash);
+          //if newResPath = '' then
           begin
-            g_Game_SetLoadingText(_lc[I_LOAD_DL_RES], 0, False);
-            newResPath := g_Res_DownloadWAD(WadName);
+            //g_Game_SetLoadingText(_lc[I_LOAD_DL_RES], 0, False);
+            newResPath := g_Res_DownloadMapWAD(ExtractFileName(WadName), gWADHash);
             if newResPath = '' then
             begin
               g_FatalError(_lc[I_NET_ERR_HASH]);
@@ -4666,8 +3098,10 @@ begin
               NetState := NET_STATE_NONE;
               Exit;
             end;
+            e_LogWritefln('using downloaded map wad [%s] for [%s]`', [newResPath, WadName], TMsgType.Notify);
           end;
-          newResPath := ExtractRelativePath(MapsDir, newResPath);
+          //newResPath := ExtractRelativePath(MapsDir, newResPath);
+
 
           gPlayer1 := g_Player_Get(g_Player_Create(gPlayer1Settings.Model,
                                                    gPlayer1Settings.Color,
@@ -4686,7 +3120,7 @@ begin
           gPlayer1.UID := NetPlrUID1;
           gPlayer1.Reset(True);
 
-          if not g_Game_StartMap(newResPath + ':\' + Map, True) then
+          if not g_Game_StartMap(false{asMegawad}, newResPath + ':\' + Map, True) then
           begin
             g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WadName + ':\' + Map]));
 
@@ -4721,8 +3155,7 @@ begin
 
     ProcessLoading(true);
 
-    if e_KeyPressed(IK_SPACE) or e_KeyPressed(IK_ESCAPE) or e_KeyPressed(VK_ESCAPE) or
-       e_KeyPressed(JOY0_JUMP) or e_KeyPressed(JOY1_JUMP) or e_KeyPressed(JOY2_JUMP) or e_KeyPressed(JOY3_JUMP) then
+    if g_Net_UserRequestExit() then
     begin
       State := 0;
       break;
@@ -4736,19 +3169,14 @@ begin
     Exit;
   end;
 
-  gLMSRespawn := LMS_RESPAWN_NONE;
-  gLMSRespawnTime := 0;
-
   g_Player_Init();
   NetState := NET_STATE_GAME;
   MC_SEND_FullStateRequest;
   e_WriteLog('NET: Connection successful.', TMsgType.Notify);
 end;
 
-procedure g_Game_SaveOptions();
-begin
-  g_Options_Write_Video(GameDir+'/'+CONFIG_FILENAME);
-end;
+var
+  lastAsMegaWad: Boolean = false;
 
 procedure g_Game_ChangeMap(const MapPath: String);
 var
@@ -4757,13 +3185,13 @@ begin
   g_Game_ClearLoading();
 
   Force := gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF];
-  // Åñëè óðîâåíü çàâåðøèëñÿ ïî òðèããåðó Âûõîä, íå î÷èùàòü èíâåíòàðü
+  // Если уровень завершился по триггеру Выход, не очищать инвентарь
   if gExitByTrigger then
   begin
     Force := False;
     gExitByTrigger := False;
   end;
-  if not g_Game_StartMap(MapPath, Force) then
+  if not g_Game_StartMap(lastAsMegaWad, MapPath, Force) then
     g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [MapPath]));
 end;
 
@@ -4774,17 +3202,19 @@ begin
   if g_Game_IsClient then
     Exit;
   map := g_ExtractFileName(gMapInfo.Map);
+  e_LogWritefln('g_Game_Restart: map = "%s" gCurrentMapFileName = "%s"', [map, gCurrentMapFileName]);
 
   MessageTime := 0;
   gGameOn := False;
   g_Game_ClearLoading();
-  g_Game_StartMap(Map, True, gCurrentMapFileName);
+  g_Game_StartMap(lastAsMegaWad, Map, True, gCurrentMapFileName);
 end;
 
-function g_Game_StartMap(Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
+function g_Game_StartMap (asMegawad: Boolean; Map: String; Force: Boolean = False; const oldMapPath: AnsiString=''): Boolean;
 var
   NewWAD, ResName: String;
   I: Integer;
+  nws: AnsiString;
 begin
   g_Map_Free((Map <> gCurrentMapFileName) and (oldMapPath <> gCurrentMapFileName));
   g_Player_RemoveAllCorpses();
@@ -4802,21 +3232,64 @@ begin
 
   g_Player_ResetTeams();
 
+  lastAsMegaWad := asMegawad;
   if isWadPath(Map) then
   begin
     NewWAD := g_ExtractWadName(Map);
     ResName := g_ExtractFileName(Map);
     if g_Game_IsServer then
     begin
-      gWADHash := MD5File(MapsDir + NewWAD);
-      g_Game_LoadWAD(NewWAD);
-    end else
+      nws := findDiskWad(NewWAD);
+      //writeln('000: Map=[', Map, ']; nws=[', nws, ']; NewWAD=[', NewWAD, ']');
+      if (asMegawad) then
+      begin
+        if (length(nws) = 0) then nws := e_FindWad(MegawadDirs, NewWAD);
+        if (length(nws) = 0) then nws := e_FindWad(MapDirs, NewWAD);
+      end
+      else
+      begin
+        if (length(nws) = 0) then nws := e_FindWad(MapDirs, NewWAD);
+        if (length(nws) = 0) then nws := e_FindWad(MegawadDirs, NewWAD);
+      end;
+      //if (length(nws) = 0) then nws := e_FindWad(MapDownloadDirs, NewWAD);
+      //writeln('001: Map=[', Map, ']; nws=[', nws, ']; NewWAD=[', NewWAD, ']');
+      //nws := NewWAD;
+      if (length(nws) = 0) then
+      begin
+        ResName := ''; // failed
+      end
+      else
+      begin
+        NewWAD := nws;
+        if (g_Game_IsNet) then gWADHash := MD5File(nws);
+        //writeln('********: nws=', nws, ' : Map=', Map, ' : nw=', NewWAD, ' : resname=', ResName);
+        g_Game_LoadWAD(NewWAD);
+      end;
+    end
+    else
+    begin
       // hash received in MC_RECV_GameEvent -> NET_EV_MAPSTART
-      g_Game_ClientWAD(NewWAD, gWADHash);
-  end else
+      NewWAD := g_Game_ClientWAD(NewWAD, gWADHash);
+    end;
+  end
+  else
+  begin
+    NewWAD := gGameSettings.WAD;
     ResName := Map;
+  end;
+
+  gTime := 0;
 
-  Result := g_Map_Load(MapsDir + gGameSettings.WAD + ':\' + ResName);
+  //writeln('********: gsw=', gGameSettings.WAD, '; rn=', ResName);
+  result := false;
+  if (ResName <> '') and (NewWAD <> '') then
+  begin
+    //result := g_Map_Load(gGameSettings.WAD + ':\' + ResName);
+    result := g_Map_Load(NewWAD+':\'+ResName);
+    {$IFNDEF HEADLESS}
+      r_Render_LoadTextures;
+    {$ENDIF}
+  end;
   if Result then
     begin
       g_Player_ResetAll(Force or gLastMap, gGameSettings.GameType = GT_SINGLE);
@@ -4826,13 +3299,13 @@ begin
       gGameOn := True;
 
       DisableCheats();
-      ResetTimer();
+      wNeedTimeReset := True;
 
       if gGameSettings.GameMode = GM_CTF then
       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;
@@ -4846,12 +3319,11 @@ begin
   gExit := 0;
   gPauseMain := false;
   gPauseHolmes := false;
-  gTime := 0;
   NetTimeToUpdate := 1;
   NetTimeToReliable := 0;
   NetTimeToMaster := NetMasterRate;
-  gLMSRespawn := LMS_RESPAWN_NONE;
-  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
   gMissionFailed := False;
   gNextMap := '';
 
@@ -4869,31 +3341,29 @@ 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);
 
-  // Ìàñòåðñåðâåð
-    if NetUseMaster then
-    begin
-      if (NetMHost = nil) or (NetMPeer = nil) then
-      begin
-        // let the connection be blocking here, why not?
-        g_Net_Slist_Connect();
-      end;
-      g_Net_Slist_Update;
-    end;
+  // Мастерсервер
+    g_Net_Slist_ServerMapStarted();
 
     if NetClients <> nil then
       for I := 0 to High(NetClients) do
@@ -4922,11 +3392,11 @@ begin
   g_Game_ExecuteEvent('onmapstart');
 end;
 
-procedure SetFirstLevel();
+procedure SetFirstLevel;
 begin
   gNextMap := '';
 
-  MapList := g_Map_GetMapsList(MapsDir + gGameSettings.WAD);
+  MapList := g_Map_GetMapsList(gGameSettings.WAD);
   if MapList = nil then
     Exit;
 
@@ -4945,16 +3415,16 @@ 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
       g_Player_RememberAll;
 
-    if not g_Map_Exist(MapsDir + gGameSettings.WAD + ':\' + gNextMap) then
+    if not g_Map_Exist(gGameSettings.WAD + ':\' + gNextMap) then
     begin
       gLastMap := True;
       if gGameSettings.GameMode = GM_COOP then
@@ -4963,7 +3433,7 @@ begin
       gStatsPressed := True;
       gNextMap := 'MAP01';
 
-      if not g_Map_Exist(MapsDir + gGameSettings.WAD + ':\' + gNextMap) then
+      if not g_Map_Exist(gGameSettings.WAD + ':\' + gNextMap) then
         g_Game_NextLevel;
 
       if g_Game_IsNet then
@@ -4989,19 +3459,36 @@ begin
   gNextMap := Map;
 end;
 
-procedure g_Game_ClientWAD(NewWAD: String; WHash: TMD5Digest);
+function g_Game_ClientWAD (NewWAD: String; const WHash: TMD5Digest): AnsiString;
 var
-  gWAD: String;
+  gWAD{, xwad}: String;
 begin
-  if LowerCase(NewWAD) = LowerCase(gGameSettings.WAD) then
-    Exit;
-  if not g_Game_IsClient then
+  result := NewWAD;
+  if not g_Game_IsClient then Exit;
+  //e_LogWritefln('*** g_Game_ClientWAD: `%s`', [NewWAD]);
+
+  gWAD := g_Res_DownloadMapWAD(ExtractFileName(NewWAD), WHash);
+  if gWAD = '' then
+  begin
+    result := '';
+    g_Game_Free();
+    g_FatalError(Format(_lc[I_GAME_ERROR_MAP_WAD], [ExtractFileName(NewWAD)]));
     Exit;
+  end;
+
+  e_LogWritefln('using downloaded client map wad [%s] for [%s]', [gWAD, NewWAD], TMsgType.Notify);
+  NewWAD := gWAD;
+
+  g_Game_LoadWAD(NewWAD);
+  result := NewWAD;
+
+  {
+  if LowerCase(NewWAD) = LowerCase(gGameSettings.WAD) then Exit;
   gWAD := g_Res_SearchSameWAD(MapsDir, ExtractFileName(NewWAD), WHash);
   if gWAD = '' then
   begin
     g_Game_SetLoadingText(_lc[I_LOAD_DL_RES], 0, False);
-    gWAD := g_Res_DownloadWAD(ExtractFileName(NewWAD));
+    gWAD := g_Res_DownloadMapWAD(ExtractFileName(NewWAD), WHash);
     if gWAD = '' then
     begin
       g_Game_Free();
@@ -5011,18 +3498,12 @@ begin
   end;
   NewWAD := ExtractRelativePath(MapsDir, gWAD);
   g_Game_LoadWAD(NewWAD);
+  }
 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;
@@ -5048,12 +3529,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;
 
@@ -5083,17 +3566,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;
 
@@ -5121,7 +3601,7 @@ var
 begin
   Result := '';
 
-  MapList := g_Map_GetMapsList(MapsDir + gGameSettings.WAD);
+  MapList := g_Map_GetMapsList(gGameSettings.WAD);
   if MapList = nil then
     Exit;
 
@@ -5143,7 +3623,7 @@ begin
     else
       Result := MapList[MapIndex + 1];
 
-    if not g_Map_Exist(MapsDir + gGameSettings.WAD + ':\' + Result) then Result := Map;
+    if not g_Map_Exist(gGameSettings.WAD + ':\' + Result) then Result := Map;
   end;
 
   MapList := nil;
@@ -5183,7 +3663,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);
@@ -5191,7 +3671,7 @@ begin
   //CopyMemory(@MapName[0], @gMapToDelete[1], Min(16, Length(gMapToDelete)));
 
 {
-// Èìÿ êàðòû íå ñòàíäàðòíîå òåñòîâîå:
+// Имя карты не стандартное тестовое:
   if MapName <> TEST_MAP_NAME then
     Exit;
 
@@ -5200,14 +3680,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('');
 
@@ -5215,7 +3695,7 @@ begin
       for a := 0 to High(MapList) do
         if MapList[a] = MapName then
         begin
-        // Óäàëÿåì è ñîõðàíÿåì:
+        // Удаляем и сохраняем:
           WAD.RemoveResource('', MapName);
           WAD.SaveTo(WadName);
           Break;
@@ -5232,66 +3712,62 @@ procedure GameCVars(P: SSArray);
 var
   a, b: Integer;
   stat: TPlayerStatArray;
-  cmd, s: string;
-  config: TConfig;
-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)]))
@@ -5300,440 +3776,378 @@ begin
                           [g_Game_ModeToText(gGameSettings.GameMode),
                            g_Game_ModeToText(gSwitchGameMode)]));
   end
-  else if (cmd = 'g_allow_exit') and not g_Game_IsClient then
+  else if cmd = 'g_friendlyfire' 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;
+    ParseGameFlag(GAME_OPTION_TEAMDAMAGE, I_MSG_FRIENDLY_FIRE_OFF, I_MSG_FRIENDLY_FIRE_ON);
+  end
+  else if cmd = 'g_friendly_absorb_damage' then
+  begin
+    ParseGameFlag(GAME_OPTION_TEAMABSORBDAMAGE, I_MSG_FRIENDLY_FIRE_OFF, I_MSG_FRIENDLY_FIRE_ON);
+  end
+  else if cmd = 'g_friendly_hit_trace' then
+  begin
+    ParseGameFlag(GAME_OPTION_TEAMHITTRACE, I_MSG_FRIENDLY_FIRE_OFF, I_MSG_FRIENDLY_FIRE_ON);
+  end
+  else if cmd = 'g_friendly_hit_projectile' then
+  begin
+    ParseGameFlag(GAME_OPTION_TEAMHITPROJECTILE, I_MSG_FRIENDLY_FIRE_OFF, I_MSG_FRIENDLY_FIRE_ON);
+  end
+  else if cmd = 'g_weaponstay' then
+  begin
+    ParseGameFlag(GAME_OPTION_WEAPONSTAY, I_MSG_WEAPONSTAY_OFF, I_MSG_WEAPONSTAY_ON);
+  end
+  else if cmd = 'g_allow_exit' then
+  begin
+    ParseGameFlag(GAME_OPTION_ALLOWEXIT, I_MSG_ALLOWEXIT_OFF, I_MSG_ALLOWEXIT_ON, True);
+  end
+  else if cmd = 'g_allow_monsters' then
+  begin
+    ParseGameFlag(GAME_OPTION_MONSTERS, I_MSG_ALLOWMON_OFF, I_MSG_ALLOWMON_ON, True);
+  end
+  else if cmd = 'g_bot_vsplayers' then
+  begin
+    ParseGameFlag(GAME_OPTION_BOTVSPLAYER, I_MSG_BOTSVSPLAYERS_OFF, I_MSG_BOTSVSPLAYERS_ON);
+  end
+  else if cmd = 'g_bot_vsmonsters' then
+  begin
+    ParseGameFlag(GAME_OPTION_BOTVSMONSTER, I_MSG_BOTSVSMONSTERS_OFF, I_MSG_BOTSVSMONSTERS_ON);
+  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 = 'g_allow_monsters') and not g_Game_IsClient then
+  else if cmd = 'g_gameflags' then
   begin
-    with gGameSettings do
+    if Length(P) > 1 then
     begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+      gsGameFlags := StrToDWordDef(P[1], gsGameFlags);
+      if g_Game_IsServer then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_MONSTERS
-        else
-          Options := Options and (not GAME_OPTION_MONSTERS);
+        gGameSettings.Options := gsGameFlags;
+        if g_Game_IsNet then MH_SEND_GameSettings;
       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;
+
+    g_Console_Add(Format('%s %u', [cmd, gsGameFlags]));
   end
-  else if (cmd = 'g_bot_vsplayers') and not g_Game_IsClient then
+  else if cmd = 'g_warmup_time' then
   begin
-    with gGameSettings do
+    if Length(P) > 1 then
     begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+      gsWarmupTime := nclamp(StrToIntDef(P[1], gsWarmupTime), 0, $FFFF);
+      if g_Game_IsServer then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_BOTVSPLAYER
-        else
-          Options := Options and (not GAME_OPTION_BOTVSPLAYER);
+        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;
-
-      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;
+
+    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 = 'g_bot_vsmonsters') and not g_Game_IsClient then
+  else if cmd = 'g_spawn_invul' then
   begin
-    with gGameSettings do
+    if Length(P) > 1 then
     begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+      gsSpawnInvul := nclamp(StrToIntDef(P[1], gsSpawnInvul), 0, $FFFF);
+      if g_Game_IsServer then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_BOTVSMONSTER
-        else
-          Options := Options and (not GAME_OPTION_BOTVSMONSTER);
+        gGameSettings.SpawnInvul := gsSpawnInvul;
+        if g_Game_IsNet then MH_SEND_GameSettings;
       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;
+
+    g_Console_Add(Format('%s %d', [cmd, Integer(gsSpawnInvul)]));
   end
-  else if (cmd = 'g_warmuptime') and not g_Game_IsClient then
+  else if cmd = 'g_item_respawn_time' 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);
+      gsItemRespawnTime := nclamp(StrToIntDef(P[1], gsItemRespawnTime), 0, $FFFF);
+      if g_Game_IsServer then
+      begin
+        gGameSettings.ItemRespawnTime := gsItemRespawnTime;
+        if g_Game_IsNet then MH_SEND_GameSettings;
+      end;
     end;
 
-    g_Console_Add(Format(_lc[I_MSG_WARMUP],
-                 [gGameSettings.WarmupTime]));
-    g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
+    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 = 'net_interp' then
+  else if cmd = 'sv_intertime' then
   begin
     if (Length(P) > 1) then
-      NetInterpLevel := StrToIntDef(P[1], NetInterpLevel);
+      gDefInterTime := Min(Max(StrToIntDef(P[1], gDefInterTime), -1), 120);
 
-    g_Console_Add('net_interp = ' + IntToStr(NetInterpLevel));
-    config := TConfig.CreateFile(GameDir+'/'+CONFIG_FILENAME);
-    config.WriteInt('Client', 'InterpolationSteps', NetInterpLevel);
-    config.SaveFile(GameDir+'/'+CONFIG_FILENAME);
-    config.Free();
+    g_Console_Add(cmd + ' = ' + IntToStr(gDefInterTime));
   end
-  else if cmd = 'net_forceplayerupdate' then
+  else if cmd = 'g_max_particles' 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')
+    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('net_forceplayerupdate = 0');
-    config := TConfig.CreateFile(GameDir+'/'+CONFIG_FILENAME);
-    config.WriteBool('Client', 'ForcePlayerUpdate', NetForcePlayerUpdate);
-    config.SaveFile(GameDir+'/'+CONFIG_FILENAME);
-    config.Free();
+    begin
+      e_LogWritefln('usage: %s <n>', [cmd])
+    end
   end
-  else if cmd = 'net_predictself' then
+  else if cmd = 'g_max_shells' 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')
+    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('net_predictself = 0');
-    config := TConfig.CreateFile(GameDir+'/'+CONFIG_FILENAME);
-    config.WriteBool('Client', 'PredictSelf', NetPredictSelf);
-    config.SaveFile(GameDir+'/'+CONFIG_FILENAME);
-    config.Free();
+    begin
+      e_LogWritefln('usage: %s <n>', [cmd])
+    end
   end
-  else if cmd = 'sv_name' then
+  else if cmd = 'g_max_gibs' then
   begin
-    if (Length(P) > 1) and (Length(P[1]) > 0) then
+    if Length(p) = 2 then
     begin
-      NetServerName := P[1];
-      if Length(NetServerName) > 64 then
-        SetLength(NetServerName, 64);
-      if g_Game_IsServer and g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
-    end;
-
-    g_Console_Add(cmd + ' = "' + NetServerName + '"');
+      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
+    begin
+      e_LogWritefln('usage: %s <n>', [cmd])
+    end
   end
-  else if cmd = 'sv_passwd' then
+  else if cmd = 'g_max_corpses' then
   begin
-    if (Length(P) > 1) and (Length(P[1]) > 0) then
+    if Length(p) = 2 then
     begin
-      NetPassword := P[1];
-      if Length(NetPassword) > 24 then
-        SetLength(NetPassword, 24);
-      if g_Game_IsServer and g_Game_IsNet and NetUseMaster then
-        g_Net_Slist_Update;
-    end;
-
-    g_Console_Add(cmd + ' = "' + AnsiLowerCase(NetPassword) + '"');
+      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
+    begin
+      e_LogWritefln('usage: %s <n>', [cmd])
+    end
   end
-  else if cmd = 'sv_maxplrs' then
+  else if cmd = 'g_scorelimit' then
   begin
-    if (Length(P) > 1) then
+    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
+      gsGoalLimit := nclamp(StrToIntDef(P[1], gsGoalLimit), 0, $FFFF);
+
+      if g_Game_IsServer then
       begin
         b := 0;
-        for a := 0 to High(NetClients) do
-          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;
-        if NetUseMaster then
-          g_Net_Slist_Update;
-      end;
-    end;
-
-    g_Console_Add(cmd + ' = ' + IntToStr(NetMaxClients));
-  end
-  else if cmd = 'sv_public' then
-  begin
-    if (Length(P) > 1) then
-    begin
-      NetUseMaster := StrToIntDef(P[1], Byte(NetUseMaster)) > 0;
-      if g_Game_IsServer and g_Game_IsNet then
-        if NetUseMaster then
-        begin
-          if NetMPeer = nil then g_Net_Slist_Connect();
-          g_Net_Slist_Update();
+        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
-          if NetMPeer <> nil then
-            g_Net_Slist_Disconnect();
-    end;
+        else // TDM/CTF
+          b := Max(gTeamStat[TEAM_RED].Goals, gTeamStat[TEAM_BLUE].Goals);
 
-    g_Console_Add(cmd + ' = ' + IntToStr(Byte(NetUseMaster)));
-  end
-  else if cmd = 'sv_intertime' then
-  begin
-    if (Length(P) > 1) then
-      gDefInterTime := Min(Max(StrToIntDef(P[1], gDefInterTime), -1), 120);
+        // 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(cmd + ' = ' + IntToStr(gDefInterTime));
+    g_Console_Add(Format(_lc[I_MSG_SCORE_LIMIT], [Integer(gsGoalLimit)]));
   end
-  else if cmd = 'p1_name' then
+  else if cmd = 'g_timelimit' then
   begin
-    if (Length(P) > 1) and gGameOn then
+    if Length(P) > 1 then
     begin
-      if g_Game_IsClient then
+      gsTimeLimit := nclamp(StrToIntDef(P[1], gsTimeLimit), 0, $FFFF);
+      if g_Game_IsServer then
       begin
-        gPlayer1Settings.Name := b_Text_Unformat(P[1]);
-        MC_SEND_PlayerSettings;
-      end
-      else
-        if gPlayer1 <> nil then
-        begin
-          gPlayer1.Name := b_Text_Unformat(P[1]);
-          if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer1.UID);
-        end
-        else
-          gPlayer1Settings.Name := b_Text_Unformat(P[1]);
+        gGameSettings.TimeLimit := gsTimeLimit;
+        if g_Game_IsNet then MH_SEND_GameSettings;
+      end;
     end;
+    g_Console_Add(Format(_lc[I_MSG_TIME_LIMIT],
+                         [gsTimeLimit div 3600,
+                         (gsTimeLimit div 60) mod 60,
+                          gsTimeLimit mod 60]));
   end
-  else if cmd = 'p2_name' then
+  else if cmd = 'g_maxlives' then
   begin
-    if (Length(P) > 1) and gGameOn then
+    if Length(P) > 1 then
     begin
-      if g_Game_IsClient then
+      gsMaxLives := nclamp(StrToIntDef(P[1], gsMaxLives), 0, $FFFF);
+      if g_Game_IsServer then
       begin
-        gPlayer2Settings.Name := b_Text_Unformat(P[1]);
-        MC_SEND_PlayerSettings;
-      end
-      else
-        if gPlayer2 <> nil then
-        begin
-          gPlayer2.Name := b_Text_Unformat(P[1]);
-          if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer2.UID);
-        end
-        else
-          gPlayer2Settings.Name := b_Text_Unformat(P[1]);
+        gGameSettings.MaxLives := gsMaxLives;
+        if g_Game_IsNet then MH_SEND_GameSettings;
+      end;
     end;
-  end
-  else if cmd = 'p1_color' then
+
+    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
-    if Length(P) > 3 then
-      if g_Game_IsClient then
+    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
-        gPlayer1Settings.Color := _RGB(EnsureRange(StrToIntDef(P[1], 0), 0, 255),
-                                       EnsureRange(StrToIntDef(P[2], 0), 0, 255),
-                                       EnsureRange(StrToIntDef(P[3], 0), 0, 255));
-        MC_SEND_PlayerSettings;
-      end
-      else
-        if gPlayer1 <> nil then
+        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
-          gPlayer1.Model.SetColor(EnsureRange(StrToIntDef(P[1], 0), 0, 255),
-                                  EnsureRange(StrToIntDef(P[2], 0), 0, 255),
-                                  EnsureRange(StrToIntDef(P[3], 0), 0, 255));
-          if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer1.UID);
-        end
-        else
           gPlayer1Settings.Color := _RGB(EnsureRange(StrToIntDef(P[1], 0), 0, 255),
                                          EnsureRange(StrToIntDef(P[2], 0), 0, 255),
                                          EnsureRange(StrToIntDef(P[3], 0), 0, 255));
-  end
-  else if (cmd = 'p2_color') and not g_Game_IsNet then
-  begin
-    if Length(P) > 3 then
-      if g_Game_IsClient then
+          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
-        gPlayer2Settings.Color := _RGB(EnsureRange(StrToIntDef(P[1], 0), 0, 255),
-                                       EnsureRange(StrToIntDef(P[2], 0), 0, 255),
-                                       EnsureRange(StrToIntDef(P[3], 0), 0, 255));
-        MC_SEND_PlayerSettings;
-      end
-      else
-        if gPlayer2 <> nil then
+        if Length(P) > 3 then
         begin
-          gPlayer2.Model.SetColor(EnsureRange(StrToIntDef(P[1], 0), 0, 255),
-                                  EnsureRange(StrToIntDef(P[2], 0), 0, 255),
-                                  EnsureRange(StrToIntDef(P[3], 0), 0, 255));
-          if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer2.UID);
-        end
-        else
           gPlayer2Settings.Color := _RGB(EnsureRange(StrToIntDef(P[1], 0), 0, 255),
                                          EnsureRange(StrToIntDef(P[2], 0), 0, 255),
                                          EnsureRange(StrToIntDef(P[3], 0), 0, 255));
-  end
-  else if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
-  begin
-    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]);
-    end
-    else if cmd = 'r_showstat' then
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
-        gShowStat := (P[1][1] = '1');
-
-      if gShowStat then
-        g_Console_Add(_lc[I_MSG_STATS_ON])
-      else
-        g_Console_Add(_lc[I_MSG_STATS_OFF]);
-    end
-    else if cmd = 'r_showkillmsg' 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])
-      else
-        g_Console_Add(_lc[I_MSG_KILL_MSGS_OFF]);
-    end
-    else if cmd = 'r_showlives' 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])
-      else
-        g_Console_Add(_lc[I_MSG_LIVES_OFF]);
-    end
-    else if cmd = 'r_showspect' 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])
-      else
-        g_Console_Add(_lc[I_MSG_SPECT_HUD_OFF]);
-    end
-    else if cmd = 'r_showping' 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])
-      else
-        g_Console_Add(_lc[I_MSG_PING_OFF]);
-    end
-    else if (cmd = 'g_scorelimit') and not g_Game_IsClient then
-    begin
-      if Length(P) > 1 then
+          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 StrToIntDef(P[1], gGameSettings.GoalLimit) = 0 then
-          gGameSettings.GoalLimit := 0
-        else
+        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
-            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);
+            gPlayer1.FActualModelName := gPlayer1Settings.Model;
+            gPlayer1.SetModel(gPlayer1Settings.Model);
+            if g_Game_IsNet then MH_SEND_PlayerSettings(gPlayer1.UID);
           end;
-
-        if g_Game_IsNet then MH_SEND_GameSettings;
+        end;
       end;
-
-      g_Console_Add(Format(_lc[I_MSG_SCORE_LIMIT], [gGameSettings.GoalLimit]));
-    end
-    else if (cmd = 'g_timelimit') and not g_Game_IsClient then
-    begin
-      if (Length(P) > 1) and (StrToIntDef(P[1], -1) >= 0) then
-        gGameSettings.TimeLimit := StrToIntDef(P[1], -1);
-
-      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;
-    end
-    else if (cmd = 'g_maxlives') and not g_Game_IsClient then
-    begin
-      if Length(P) > 1 then
+    'p2_model':
       begin
-        if StrToIntDef(P[1], gGameSettings.MaxLives) = 0 then
-          gGameSettings.MaxLives := 0
-        else
+        if (Length(P) > 1) 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);
+          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;
-
-      g_Console_Add(Format(_lc[I_MSG_LIVES],
-                           [gGameSettings.MaxLives]));
-      if g_Game_IsNet then MH_SEND_GameSettings;
-    end;
   end;
 end;
 
@@ -5758,17 +4172,14 @@ var
   //pt: TDFPoint;
   mon: TMonster;
 begin
-// Êîìàíäû îòëàäî÷íîãî ðåæèìà:
+// Команды отладочного режима:
   if {gDebugMode}conIsCheatsEnabled then
   begin
     cmd := LowerCase(P[0]);
     if cmd = 'd_window' then
     begin
-      g_Console_Add(Format('gWinPosX = %d, gWinPosY %d', [gWinPosX, gWinPosY]));
-      g_Console_Add(Format('gWinRealPosX = %d, gWinRealPosY %d', [gWinRealPosX, gWinRealPosY]));
       g_Console_Add(Format('gScreenWidth = %d, gScreenHeight = %d', [gScreenWidth, gScreenHeight]));
-      g_Console_Add(Format('gWinSizeX = %d, gWinSizeY = %d', [gWinSizeX, gWinSizeY]));
-      g_Console_Add(Format('Frame X = %d, Y = %d, Caption Y = %d', [gWinFrameX, gWinFrameY, gWinCaption]));
+      g_Console_Add(Format('gScreenWidth = %d, gScreenHeight = %d', [gScreenWidth, gScreenHeight]));
     end
     else if cmd = 'd_sounds' then
     begin
@@ -6108,18 +4519,12 @@ var
   prt: Word;
   nm: Boolean;
   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);
@@ -6162,23 +4567,21 @@ begin
         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);
-        if NetUseMaster then
-          g_Net_Slist_Update;
+        g_Net_Slist_ServerPlayerLeaves();
       end else if gPlayers <> nil then
         for a := Low(gPlayers) to High(gPlayers) do
           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;
               gPlayers[a].Kill(K_SIMPLEKILL, 0, HIT_DISCON);
               g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [gPlayers[a].Name]), True);
               g_Player_Remove(gPlayers[a].UID);
-              if NetUseMaster then
-                g_Net_Slist_Update;
-              // Åñëè íå ïåðåìåøàòü, ïðè äîáàâëåíèè íîâûõ áîòîâ ïîÿâÿòñÿ ñòàðûå
+              g_Net_Slist_ServerPlayerLeaves();
+              // Если не перемешать, при добавлении новых ботов появятся старые
               g_Bot_MixNames();
             end;
     end else
@@ -6208,25 +4611,139 @@ begin
           enet_peer_disconnect(NetClients[a].Peer, NET_DISC_KICK);
           g_Console_Add(Format(_lc[I_PLAYER_KICK], [s]));
           MH_SEND_GameEvent(NET_EV_PLAYER_KICK, 0, s);
-          if NetUseMaster then
-            g_Net_Slist_Update;
+          g_Net_Slist_ServerPlayerLeaves();
+        end;
+      end;
+    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
+    begin
+      if Length(P) < 2 then
+      begin
+        g_Console_Add('ban <name>');
+        Exit;
+      end;
+      if P[1] = '' then
+      begin
+        g_Console_Add('ban <name>');
+        Exit;
+      end;
+
+      pl := g_Net_Client_ByName(P[1]);
+      if (pl <> 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 else
+        g_Console_Add(Format(_lc[I_NET_ERR_NAME404], [P[1]]));
+    end else
+      g_Console_Add(_lc[I_MSG_SERVERONLY]);
+  end
+  else if cmd = 'ban_id' then
+  begin
+    if g_Game_IsServer and g_Game_IsNet then
+    begin
+      if Length(P) < 2 then
+      begin
+        g_Console_Add('ban_id <client ID>');
+        Exit;
+      end;
+      if P[1] = '' then
+      begin
+        g_Console_Add('ban_id <client ID>');
+        Exit;
+      end;
+
+      a := StrToIntDef(P[1], 0);
+      if (NetClients <> nil) and (a <= High(NetClients)) then
+        if NetClients[a].Used and (NetClients[a].Peer <> nil) then
+        begin
+          s := g_Net_ClientName_ByID(NetClients[a].ID);
+          g_Net_BanHost(NetClients[a].Peer^.address.host, False);
+          enet_peer_disconnect(NetClients[a].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 = '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 = 'ban' then
+  else if cmd = 'permban' then
   begin
     if g_Game_IsServer and g_Game_IsNet then
     begin
       if Length(P) < 2 then
       begin
-        g_Console_Add('ban <name>');
+        g_Console_Add('permban <name>');
         Exit;
       end;
       if P[1] = '' then
       begin
-        g_Console_Add('ban <name>');
+        g_Console_Add('permban <name>');
         Exit;
       end;
 
@@ -6234,29 +4751,29 @@ begin
       if (pl <> 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_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);
-        if NetUseMaster then
-          g_Net_Slist_Update;
+        g_Net_Slist_ServerPlayerLeaves();
       end else
         g_Console_Add(Format(_lc[I_NET_ERR_NAME404], [P[1]]));
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
-  else if cmd = 'ban_id' then
+  else if cmd = 'permban_id' then
   begin
     if g_Game_IsServer and g_Game_IsNet then
     begin
       if Length(P) < 2 then
       begin
-        g_Console_Add('ban_id <client ID>');
+        g_Console_Add('permban_id <client ID>');
         Exit;
       end;
       if P[1] = '' then
       begin
-        g_Console_Add('ban_id <client ID>');
+        g_Console_Add('permban_id <client ID>');
         Exit;
       end;
 
@@ -6265,33 +4782,34 @@ begin
         if NetClients[a].Used and (NetClients[a].Peer <> nil) then
         begin
           s := g_Net_ClientName_ByID(NetClients[a].ID);
-          g_Net_BanHost(NetClients[a].Peer^.address.host, False);
-          enet_peer_disconnect(NetClients[a].Peer, NET_DISC_TEMPBAN);
+          g_Net_BanHost(NetClients[a].Peer^.address.host);
+          enet_peer_disconnect(NetClients[a].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);
-          if NetUseMaster then
-            g_Net_Slist_Update;
+          g_Net_Slist_ServerPlayerLeaves();
         end;
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
-  else if cmd = 'permban' then
+  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 <name>');
+        g_Console_Add('permban_pid <player ID>');
         Exit;
       end;
       if P[1] = '' then
       begin
-        g_Console_Add('permban <name>');
+        g_Console_Add('permban_pid <player ID>');
         Exit;
       end;
 
-      pl := g_Net_Client_ByName(P[1]);
-      if (pl <> nil) then
+      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);
@@ -6299,41 +4817,29 @@ begin
         g_Net_SaveBanList();
         g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
         MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
-        if NetUseMaster then
-          g_Net_Slist_Update;
-      end else
-        g_Console_Add(Format(_lc[I_NET_ERR_NAME404], [P[1]]));
+        g_Net_Slist_ServerPlayerLeaves();
+      end;
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
-  else if cmd = 'permban_id' then
+  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_id <client ID>');
+        g_Console_Add('permban_ip <IP address>');
         Exit;
       end;
       if P[1] = '' then
       begin
-        g_Console_Add('permban_id <client ID>');
+        g_Console_Add('permban_ip <IP address>');
         Exit;
       end;
 
-      a := StrToIntDef(P[1], 0);
-      if (NetClients <> nil) and (a <= High(NetClients)) then
-        if NetClients[a].Used and (NetClients[a].Peer <> nil) then
-        begin
-          s := g_Net_ClientName_ByID(NetClients[a].ID);
-          g_Net_BanHost(NetClients[a].Peer^.address.host);
-          enet_peer_disconnect(NetClients[a].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);
-          if NetUseMaster then
-            g_Net_Slist_Update;
-        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
@@ -6522,26 +5028,31 @@ begin
       g_Console_Add(cmd + ' <WAD> [MAP] [# players]');
       Exit;
     end;
-    // Èãðà åù¸ íå çàïóùåíà, ñíà÷àëà íàì íàäî çàãðóçèòü êàêîé-òî WAD
-    P[1] := addWadExtension(P[1]);
-    if FileExists(MapsDir + P[1]) then
+    // game not started yet, load fist map from some wad
+    found := false;
+    s := addWadExtension(P[1]);
+    found := e_FindResource(AllMapDirs, s);
+    P[1] := s;
+    if found then
     begin
-      // Åñëè êàðòà íå óêàçàíà, áåð¸ì ïåðâóþ êàðòó â ôàéëå
+      P[1] := ExpandFileName(P[1]);
+      // if map not choosed then set first map
       if Length(P) < 3 then
       begin
         SetLength(P, 3);
-        P[2] := g_Game_GetFirstMap(MapsDir + P[1]);
+        P[2] := g_Game_GetFirstMap(P[1]);
       end;
 
       s := P[1] + ':\' + UpperCase(P[2]);
 
-      if g_Map_Exist(MapsDir + s) then
+      if g_Map_Exist(s) then
       begin
-        // Çàïóñêàåì ñâîþ èãðó
+        // start game
         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;
@@ -6577,43 +5088,47 @@ begin
       Exit;
     prt := StrToIntDef(P[2], 25666);
 
-    P[3] := addWadExtension(P[3]);
-    if FileExists(MapsDir + P[3]) then
+    s := addWadExtension(P[3]);
+    found := e_FindResource(AllMapDirs, s);
+    P[3] := s;
+    if found then
     begin
-      // Åñëè êàðòà íå óêàçàíà, áåð¸ì ïåðâóþ êàðòó â ôàéëå
+      // get first map in wad, if not specified
       if Length(P) < 5 then
       begin
         SetLength(P, 5);
-        P[4] := g_Game_GetFirstMap(MapsDir + P[1]);
+        P[4] := g_Game_GetFirstMap(P[1]);
       end;
-
       s := P[3] + ':\' + UpperCase(P[4]);
-
-      if g_Map_Exist(MapsDir + s) then
+      if g_Map_Exist(s) then
       begin
-        // Çàïóñêàåì ñâîþ èãðó
+        // start game
         g_Game_Free();
         with gGameSettings do
         begin
-          GameMode := g_Game_TextToMode(gcGameMode);
-          if gSwitchGameMode <> GM_NONE then
-            GameMode := gSwitchGameMode;
+          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;
           b := 0;
           if Length(P) >= 6 then
             b := StrToIntDef(P[5], 0);
-          g_Game_StartServer(s, GameMode, TimeLimit,
-                             GoalLimit, MaxLives, Options, b, listen, prt);
-        end;
+          g_Game_StartServer(s, GameMode, TimeLimit, GoalLimit, MaxLives, Options, b, listen, prt)
+        end
       end
       else
+      begin
         if P[4] = '' then
           g_Console_Add(Format(_lc[I_MSG_NO_MAPS], [P[3]]))
         else
-          g_Console_Add(Format(_lc[I_MSG_NO_MAP_FALLBACK], [UpperCase(P[4]), P[3]]));
-    end else
-      g_Console_Add(Format(_lc[I_MSG_NO_WAD], [P[3]]));
+          g_Console_Add(Format(_lc[I_MSG_NO_MAP_FALLBACK], [UpperCase(P[4]), P[3]]))
+      end
+    end
+    else
+    begin
+      g_Console_Add(Format(_lc[I_MSG_NO_WAD], [P[3]]))
+    end
   end
   else if cmd = 'map' then
   begin
@@ -6622,92 +5137,126 @@ begin
       if g_Game_IsServer and (gGameSettings.GameType <> GT_SINGLE) then
       begin
         g_Console_Add(cmd + ' <MAP>');
-        g_Console_Add(cmd + ' <WAD> [MAP]');
-      end else
-        g_Console_Add(_lc[I_MSG_GM_UNAVAIL]);
-    end else
+        g_Console_Add(cmd + ' <WAD> [MAP]')
+      end
+      else
+      begin
+        g_Console_Add(_lc[I_MSG_GM_UNAVAIL])
+      end
+    end
+    else
+    begin
       if g_Game_IsServer and (gGameSettings.GameType <> GT_SINGLE) then
       begin
-        // Èä¸ò ñâîÿ èãðà èëè ñåðâåð
         if Length(P) < 3 then
         begin
-          // Ïåðâûé ïàðàìåòð - ëèáî êàðòà, ëèáî èìÿ WAD ôàéëà
+          // first param is map or wad
           s := UpperCase(P[1]);
-          if g_Map_Exist(MapsDir + gGameSettings.WAD + ':\' + s) then
-          begin // Êàðòà íàøëàñü
+          if g_Map_Exist(gGameSettings.WAD + ':\' + s) then
+          begin
             gExitByTrigger := False;
             if gGameOn then
-            begin // Èä¸ò èãðà - çàâåðøàåì óðîâåíü
+            begin
+              // already in game, finish current map
               gNextMap := s;
               gExit := EXIT_ENDLEVELCUSTOM;
             end
-            else // Èíòåðìèññèÿ - ñðàçó çàãðóæàåì êàðòó
-              g_Game_ChangeMap(s);
-          end else
+            else
+            begin
+              // intermission, so change map immediately
+              g_Game_ChangeMap(s)
+            end
+          end
+          else
           begin
-            // Òàêîé êàðòû íåò, èùåì WAD ôàéë
-            pw := findDiskWad(MapsDir + P[1]);
+            s := P[1];
+            found := e_FindResource(AllMapDirs, s);
+            P[1] := s;
             g_Console_Add(Format(_lc[I_MSG_NO_MAP_FALLBACK], [s, 'WAD ' + P[1]]));
-            if FileExists(pw) then
+            if found then
             begin
-              // Ïàðàìåòðà êàðòû íåò, ïîýòîìó ñòàâèì ïåðâóþ èç ôàéëà
+              // no such map, found wad
+              pw := P[1];
               SetLength(P, 3);
-              P[1] := ExtractRelativePath(MapsDir, pw);
-              P[2] := g_Game_GetFirstMap(MapsDir + P[1]);
-
+              P[1] := ExpandFileName(pw);
+              P[2] := g_Game_GetFirstMap(P[1]);
               s := P[1] + ':\' + P[2];
-
-              if g_Map_Exist(MapsDir + s) then
+              if g_Map_Exist(s) then
               begin
                 gExitByTrigger := False;
                 if gGameOn then
-                begin // Èä¸ò èãðà - çàâåðøàåì óðîâåíü
+                begin
+                  // already in game, finish current map
                   gNextMap := s;
-                  gExit := EXIT_ENDLEVELCUSTOM;
+                  gExit := EXIT_ENDLEVELCUSTOM
                 end
-                else // Èíòåðìèññèÿ - ñðàçó çàãðóæàåì êàðòó
-                  g_Game_ChangeMap(s);
-              end else
+                else
+                begin
+                  // intermission, so change map immediately
+                  g_Game_ChangeMap(s)
+                end
+              end
+              else
+              begin
                 if P[2] = '' then
                   g_Console_Add(Format(_lc[I_MSG_NO_MAPS], [P[1]]))
                 else
-                  g_Console_Add(Format(_lc[I_MSG_NO_MAP], [P[2]]));
-            end else
-              g_Console_Add(Format(_lc[I_MSG_NO_WAD], [P[1]]));
+                  g_Console_Add(Format(_lc[I_MSG_NO_MAP], [P[2]]))
+              end
+            end
+            else
+            begin
+              g_Console_Add(Format(_lc[I_MSG_NO_WAD], [P[1]]))
+            end
           end;
-        end else
+        end
+        else
         begin
-          // Óêàçàíî äâà ïàðàìåòðà, çíà÷èò ïåðâûé - WAD ôàéë, à âòîðîé - êàðòà
-          P[1] := addWadExtension(P[1]);
-          if FileExists(MapsDir + P[1]) then
+          s := addWadExtension(P[1]);
+          found := e_FindResource(AllMapDirs, s);
+          P[1] := s;
+          if found then
           begin
-            // Íàøëè WAD ôàéë
             P[2] := UpperCase(P[2]);
             s := P[1] + ':\' + P[2];
-
-            if g_Map_Exist(MapsDir + s) then
-            begin // Íàøëè êàðòó
+            if g_Map_Exist(s) then
+            begin
               gExitByTrigger := False;
               if gGameOn then
-              begin // Èä¸ò èãðà - çàâåðøàåì óðîâåíü
+              begin
                 gNextMap := s;
-                gExit := EXIT_ENDLEVELCUSTOM;
+                gExit := EXIT_ENDLEVELCUSTOM
               end
-              else // Èíòåðìèññèÿ - ñðàçó çàãðóæàåì êàðòó
-                g_Game_ChangeMap(s);
-            end else
-              g_Console_Add(Format(_lc[I_MSG_NO_MAP], [P[2]]));
-          end else
-            g_Console_Add(Format(_lc[I_MSG_NO_WAD], [P[1]]));
-        end;
-      end else
-        g_Console_Add(_lc[I_MSG_GM_UNAVAIL]);
+              else
+              begin
+                g_Game_ChangeMap(s)
+              end
+            end
+            else
+            begin
+              g_Console_Add(Format(_lc[I_MSG_NO_MAP], [P[2]]))
+            end
+          end
+          else
+          begin
+            g_Console_Add(Format(_lc[I_MSG_NO_WAD], [P[1]]))
+          end
+        end
+      end
+      else
+      begin
+        g_Console_Add(_lc[I_MSG_GM_UNAVAIL])
+      end
+    end
   end
   else if cmd = 'nextmap' then
   begin
     if not(gGameOn or (gState = STATE_INTERCUSTOM)) then
+    begin
       g_Console_Add(_lc[I_MSG_NOT_GAME])
-    else begin
+    end
+    else
+    begin
       nm := True;
       if Length(P) = 1 then
       begin
@@ -6715,113 +5264,148 @@ begin
         begin
           g_Console_Add(cmd + ' <MAP>');
           g_Console_Add(cmd + ' <WAD> [MAP]');
-        end else begin
+        end
+        else
+        begin
           nm := False;
           g_Console_Add(_lc[I_MSG_GM_UNAVAIL]);
         end;
-      end else
+      end
+      else
       begin
         nm := False;
         if g_Game_IsServer and (gGameSettings.GameType <> GT_SINGLE) then
         begin
           if Length(P) < 3 then
           begin
-            // Ïåðâûé ïàðàìåòð - ëèáî êàðòà, ëèáî èìÿ WAD ôàéëà
+            // first param is map or wad
             s := UpperCase(P[1]);
-            if g_Map_Exist(MapsDir + gGameSettings.WAD + ':\' + s) then
-            begin // Êàðòà íàøëàñü
+            if g_Map_Exist(gGameSettings.WAD + ':\' + s) then
+            begin
+              // map founded
               gExitByTrigger := False;
               gNextMap := s;
               nm := True;
-            end else
+            end
+            else
             begin
-              // Òàêîé êàðòû íåò, èùåì WAD ôàéë
-              P[1] := addWadExtension(P[1]);
+              // no such map, found wad
+              pw := addWadExtension(P[1]);
+              found := e_FindResource(MapDirs, pw);
+              if not found then
+                found := e_FindResource(WadDirs, pw);
+              P[1] := pw;
               g_Console_Add(Format(_lc[I_MSG_NO_MAP_FALLBACK], [s, P[1]]));
-              if FileExists(MapsDir + P[1]) then
+              if found then
               begin
-                // Ïàðàìåòðà êàðòû íåò, ïîýòîìó ñòàâèì ïåðâóþ èç ôàéëà
+                // map not specified, select first map
                 SetLength(P, 3);
-                P[2] := g_Game_GetFirstMap(MapsDir + P[1]);
-
+                P[2] := g_Game_GetFirstMap(P[1]);
                 s := P[1] + ':\' + P[2];
-
-                if g_Map_Exist(MapsDir + s) then
-                begin // Óñòàíàâëèâàåì êàðòó
+                if g_Map_Exist(s) then
+                begin
                   gExitByTrigger := False;
                   gNextMap := s;
-                  nm := True;
-                end else
+                  nm := True
+                end
+                else
+                begin
                   if P[2] = '' then
                     g_Console_Add(Format(_lc[I_MSG_NO_MAPS], [P[1]]))
                   else
-                    g_Console_Add(Format(_lc[I_MSG_NO_MAP], [P[2]]));
-              end else
-                g_Console_Add(Format(_lc[I_MSG_NO_WAD], [P[1]]));
-            end;
-          end else
+                    g_Console_Add(Format(_lc[I_MSG_NO_MAP], [P[2]]))
+                end
+              end
+              else
+              begin
+                g_Console_Add(Format(_lc[I_MSG_NO_WAD], [P[1]]))
+              end
+            end
+          end
+          else
           begin
-            // Óêàçàíî äâà ïàðàìåòðà, çíà÷èò ïåðâûé - WAD ôàéë, à âòîðîé - êàðòà
-            P[1] := addWadExtension(P[1]);
-            if FileExists(MapsDir + P[1]) then
+            // specified two params wad + map
+            pw := addWadExtension(P[1]);
+            found := e_FindResource(MapDirs, pw);
+            if not found then
+              found := e_FindResource(MapDirs, pw);
+            P[1] := pw;
+            if found then
             begin
-              // Íàøëè WAD ôàéë
               P[2] := UpperCase(P[2]);
               s := P[1] + ':\' + P[2];
-
-              if g_Map_Exist(MapsDir + s) then
-              begin // Íàøëè êàðòó
+              if g_Map_Exist(s) then
+              begin
                 gExitByTrigger := False;
                 gNextMap := s;
-                nm := True;
-              end else
-                g_Console_Add(Format(_lc[I_MSG_NO_MAP], [P[2]]));
-            end else
-              g_Console_Add(Format(_lc[I_MSG_NO_WAD], [P[1]]));
-          end;
-        end else
-          g_Console_Add(_lc[I_MSG_GM_UNAVAIL]);
+                nm := True
+              end
+              else
+              begin
+                g_Console_Add(Format(_lc[I_MSG_NO_MAP], [P[2]]))
+              end
+            end
+            else
+            begin
+              g_Console_Add(Format(_lc[I_MSG_NO_WAD], [P[1]]))
+            end
+          end
+        end
+        else
+        begin
+          g_Console_Add(_lc[I_MSG_GM_UNAVAIL])
+        end
       end;
       if nm then
+      begin
         if gNextMap = '' then
           g_Console_Add(_lc[I_MSG_NEXTMAP_UNSET])
         else
-          g_Console_Add(Format(_lc[I_MSG_NEXTMAP_SET], [gNextMap]));
-    end;
+          g_Console_Add(Format(_lc[I_MSG_NEXTMAP_SET], [gNextMap]))
+      end
+    end
   end
   else if (cmd = 'endmap') or (cmd = 'goodbye') then
   begin
     if not gGameOn then
+    begin
       g_Console_Add(_lc[I_MSG_NOT_GAME])
+    end
     else
+    begin
       if g_Game_IsServer and (gGameSettings.GameType <> GT_SINGLE) then
       begin
         gExitByTrigger := False;
-        // Ñëåäóþùàÿ êàðòà íå çàäàíà, ïðîáóåì íàéòè òðèããåð Âûõîä
+        // next map not specified, try to find trigger EXIT
         if (gNextMap = '') and (gTriggers <> nil) then
+        begin
           for a := 0 to High(gTriggers) do
+          begin
             if gTriggers[a].TriggerType = TRIGGER_EXIT then
             begin
               gExitByTrigger := True;
               //gNextMap := gTriggers[a].Data.MapName;
               gNextMap := gTriggers[a].tgcMap;
-              Break;
-            end;
-        // Èùåì ñëåäóþùóþ êàðòó â WAD ôàéëå
+              Break
+            end
+          end
+        end;
         if gNextMap = '' then
           gNextMap := g_Game_GetNextMap();
-        // Ïðîâåðÿåì, íå çàäàí ëè WAD ôàéë ðåñóðñíîé ñòðîêîé
         if not isWadPath(gNextMap) then
           s := gGameSettings.WAD + ':\' + gNextMap
         else
           s := gNextMap;
-        // Åñëè êàðòà íàéäåíà, âûõîäèì ñ óðîâíÿ
-        if g_Map_Exist(MapsDir + s) then
+        if g_Map_Exist(s) then
           gExit := EXIT_ENDLEVELCUSTOM
         else
-          g_Console_Add(Format(_lc[I_MSG_NO_MAP], [gNextMap]));
-      end else
-        g_Console_Add(_lc[I_MSG_GM_UNAVAIL]);
+          g_Console_Add(Format(_lc[I_MSG_NO_MAP], [gNextMap]))
+      end
+      else
+      begin
+        g_Console_Add(_lc[I_MSG_GM_UNAVAIL])
+      end
+    end
   end
   else if (cmd = 'event') then
   begin
@@ -6874,7 +5458,9 @@ begin
   end
   else if cmd = 'screenshot' then
   begin
-    g_TakeScreenShot()
+    {$IFNDEF HEADLESS}
+      g_TakeScreenShot()
+    {$ENDIF}
   end
   else if cmd = 'weapon' then
   begin
@@ -6895,7 +5481,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
@@ -6971,11 +5557,35 @@ begin
       end else
         g_Console_Add(_lc[I_MSG_SERVERONLY]);
     end
+    else if cmd = 'centerprint' then
+    begin
+      if (Length(P) > 2) and (P[1] <> '') then
+      begin
+        chstr := '';
+        for a := 2 to High(P) do
+          chstr := chstr + P[a] + ' ';
+
+        if Length(chstr) > 200 then SetLength(chstr, 200);
+
+        if Length(chstr) < 1 then
+        begin
+          g_Console_Add('centerprint <timeout> <text>');
+          Exit;
+        end;
+
+        a := StrToIntDef(P[1], 100);
+        chstr := b_Text_Format(chstr);
+        g_Game_Message(chstr, a);
+        if g_Game_IsNet and g_Game_IsServer then
+          MH_SEND_GameEvent(NET_EV_BIGTEXT, a, chstr);
+      end
+      else g_Console_Add('centerprint <timeout> <text>');
+    end
     else if (cmd = 'overtime') and not g_Game_IsClient then
     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],
@@ -7076,48 +5686,88 @@ begin
   end;
 end;
 
-procedure g_TakeScreenShot();
+procedure SystemCommands(P: SSArray);
 var
-  a: Word;
-  FileName: string;
-  ssdir, t: string;
-  st: TStream;
-  ok: Boolean;
+  cmd: string;
 begin
-  if e_NoGraphics then Exit;
-  ssdir := GameDir+'/screenshots';
-  if not findFileCI(ssdir, true) then
-  begin
-    // try to create dir
-    try
-      CreateDir(ssdir);
-    except
-    end;
-    if not findFileCI(ssdir, true) then exit; // alas
-  end;
-  try
-    for a := 1 to High(Word) do
-    begin
-      FileName := Format(ssdir+'screenshot%.3d.png', [a]);
-      t := FileName;
-      if findFileCI(t, true) then continue;
-      if not findFileCI(FileName) then
+  cmd := LowerCase(P[0]);
+  case cmd of
+    'exit', 'quit':
+      begin
+        g_Game_Free();
+        g_Game_Quit();
+      end;
+{$IFNDEF HEADLESS}
+    'r_reset':
+         r_Render_Apply;
+{$ENDIF}
+    'r_maxfps':
       begin
-        ok := false;
-        st := createDiskFile(FileName);
-        try
-          e_MakeScreenshot(st, gScreenWidth, gScreenHeight);
-          ok := true;
-        finally
-          st.Free();
+        if Length(p) = 2 then
+        begin
+          gMaxFPS := StrToIntDef(p[1], gMaxFPS);
+          if gMaxFPS > 0 then
+            gFrameTime := 1000 div gMaxFPS
+          else
+            gFrameTime := 0;
         end;
-        if not ok then try DeleteFile(FileName); except end else g_Console_Add(Format(_lc[I_CONSOLE_SCREENSHOT], [ExtractFileName(FileName)]));
-        break;
+        e_LogWritefln('r_maxfps %d', [gMaxFPS]);
       end;
-    end;
-  except
+    '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;
+
+{$IFNDEF HEADLESS}
+procedure g_TakeScreenShot(Filename: string = '');
+  var t: TDateTime; dir, date, name: String;
+begin
+  if e_NoGraphics then
+    Exit;
+
+  dir := e_GetWriteableDir(ScreenshotDirs);
+  if Filename = '' then
+  begin
+    t := Now;
+    DateTimeToString(date, 'yyyy-mm-dd-hh-nn-ss', t);
+    Filename := 'screenshot-' + date;
   end;
+
+  name := e_CatPath(dir, Filename + '.png');
+  if r_Render_WriteScreenShot(name) then
+    g_Console_Add(Format(_lc[I_CONSOLE_SCREENSHOT], [name]))
+  else
+    g_Console_Add(Format(_lc[I_CONSOLE_ERROR_WRITE], [name]));
 end;
+{$ENDIF}
 
 procedure g_Game_InGameMenu(Show: Boolean);
 begin
@@ -7137,14 +5787,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;
@@ -7181,7 +5831,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
@@ -7192,13 +5842,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;
@@ -7292,12 +5942,11 @@ begin
   end;
 end;
 
-procedure g_Game_Message(Msg: string; Time: Word);
-begin
-  MessageLineLength := (gScreenWidth - 204) div e_CharFont_GetMaxWidth(gMenuFont);
-  MessageText := b_Text_Wrap(b_Text_Format(Msg), MessageLineLength);
-  MessageTime := Time;
-end;
+  procedure g_Game_Message (Msg: string; Time: Word);
+  begin
+    MessageText := Msg;
+    MessageTime := Time;
+  end;
 
 procedure g_Game_ChatSound(Text: String; Taunt: Boolean = True);
 const
@@ -7443,8 +6092,7 @@ begin
   case gAnnouncer of
     ANNOUNCE_NONE:
       Exit;
-    ANNOUNCE_ME,
-    ANNOUNCE_MEPLUS:
+    ANNOUNCE_ME:
       if not g_Game_IsWatchedPlayer(SpawnerUID) then
         Exit;
   end;
@@ -7566,7 +6214,7 @@ end;
 procedure g_Game_SetDebugMode();
 begin
   gDebugMode := True;
-// ×èòû (äàæå â ñâîåé èãðå):
+// Читы (даже в своей игре):
   gCheats := True;
 end;
 
@@ -7580,7 +6228,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
@@ -7655,7 +6303,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
@@ -7665,7 +6313,7 @@ begin
         end
       else
         if (i < ParamCount) then
-        begin // Ïàðàìåòð ñî çíà÷åíèåì
+        begin // Параметр со значением
           Inc(i);
           SetLength(pars, Length(pars) + 1);
           with pars[High(pars)] do
@@ -7779,11 +6427,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');
@@ -7793,13 +6439,17 @@ begin
   // Override map to test:
     s := LowerCase(Find_Param_Value(pars, '-testmap'));
     if s <> '' then
-      gTestMap := MapsDir + s;
+    begin
+      if e_IsValidResourceName(s) then
+        e_FindResource(AllMapDirs, s);
+      gTestMap := ExpandFileName(s);
+    end;
 
   // Delete test map after play:
     s := Find_Param_Value(pars, '--testdelete');
     if (s <> '') then
     begin
-      gMapToDelete := MapsDir + map;
+      //gMapToDelete := MapsDir + map;
       e_WriteLog('"--testdelete" is deprecated, use --tempdelete.', TMsgType.Fatal);
       Halt(1);
     end;
@@ -7831,8 +6481,8 @@ begin
   s := Find_Param_Value(pars, '-exec');
   if s <> '' then
   begin
-    if not isWadPath(s) then
-      s := GameDir + '/' + s;
+//    if not isWadPath(s) then
+//      s := GameDir + '/' + s;
 
     {$I-}
     AssignFile(F, s);
@@ -7891,8 +6541,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('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');
 
@@ -7901,4 +6549,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.