DEADSOFTWARE

Game: Use proper syntax of sets for game options instead of raw bitwise operations
[d2df-sdl.git] / src / game / g_game.pas
index 3dc360c810087c893adfb3956bc69372b43ddd74..824595b850be4297c8199295f4f1d7b8c9f73cc0 100644 (file)
@@ -25,15 +25,41 @@ uses
   g_touch, g_weapons;
 
 type
+  TGameOption = (
+    //RESERVED = 0,  // FIXME: reuse for something
+    TEAM_DAMAGE = 1,
+    ALLOW_EXIT,
+    WEAPONS_STAY,
+    MONSTERS,
+    BOTS_VS_PLAYERS,
+    BOTS_VS_MONSTERS,
+    DM_KEYS,
+    TEAM_HIT_TRACE,
+    TEAM_HIT_PROJECTILE,
+    TEAM_ABSORB_DAMAGE,
+    ALLOW_DROP_FLAG,
+    THROW_FLAG,
+    POWERUP_RANDOM,
+    ITEM_ALL_RANDOM,
+    ITEM_LIFE_RANDOM,
+    ITEM_AMMO_RANDOM,
+    ITEM_WEAPON_RANDOM
+  );
+  TGameOptions = set of TGameOption;
+
   TGameSettings = record
     GameType: Byte;
     GameMode: Byte;
     TimeLimit: Word;
-    GoalLimit: Word;
+    ScoreLimit: Word;
     WarmupTime: Word;
     SpawnInvul: Word;
+    ItemRespawnTime: Word;
+    ItemRespawnRandom: Word;
+    PowerupRespawnTime: Word;
+    PowerupRespawnRandom: Word;
     MaxLives: Byte;
-    Options: LongWord;
+    Options: TGameOptions;
     WAD: String;
   end;
 
@@ -61,6 +87,11 @@ type
     Model: String;
     Color: TRGB;
     Team: Byte;
+    // ones below are sent only to the server
+    WeaponSwitch: Byte;
+    WeaponPreferences: Array[WP_FIRST..WP_LAST+1] of Byte;
+    SwitchToEmpty: Byte;
+    SkipIronFist: Byte;
   end;
 
   TMegaWADInfo = record
@@ -83,6 +114,7 @@ procedure g_Game_Free (freeTextures: Boolean=true);
 procedure g_Game_LoadData();
 procedure g_Game_FreeData();
 procedure g_Game_Update();
+procedure g_Game_PreUpdate();
 procedure g_Game_Draw();
 procedure g_Game_Quit();
 procedure g_Game_SetupScreenSize();
@@ -96,8 +128,8 @@ procedure g_Game_RemovePlayer();
 procedure g_Game_Spectate();
 procedure g_Game_SpectateCenterView();
 procedure g_Game_StartSingle(Map: String; TwoPlayers: Boolean; nPlayers: Byte);
-procedure g_Game_StartCustom(Map: String; GameMode: Byte; TimeLimit, GoalLimit: Word; MaxLives: Byte; Options: LongWord; nPlayers: Byte);
-procedure g_Game_StartServer(Map: String; GameMode: Byte; TimeLimit, GoalLimit: Word; MaxLives: Byte; Options: LongWord; nPlayers: Byte; IPAddr: LongWord; Port: Word);
+procedure g_Game_StartCustom(Map: String; GameMode: Byte; TimeLimit, ScoreLimit: Word; MaxLives: Byte; Options: TGameOptions; nPlayers: Byte);
+procedure g_Game_StartServer(Map: String; GameMode: Byte; TimeLimit, ScoreLimit: Word; MaxLives: Byte; Options: TGameOptions; nPlayers: Byte; IPAddr: LongWord; Port: Word);
 procedure g_Game_StartClient(Addr: String; Port: Word; PW: String);
 procedure g_Game_Restart();
 procedure g_Game_RestartLevel();
@@ -124,6 +156,7 @@ procedure g_Game_ChatSound(Text: String; Taunt: Boolean = True);
 procedure g_Game_Announce_GoodShot(SpawnerUID: Word);
 procedure g_Game_Announce_KillCombo(Param: Integer);
 procedure g_Game_Announce_BodyKill(SpawnerUID: Word);
+procedure g_Game_Effect_Bubbles(fX, fY: Integer; count: Word; devX, devY: Byte; Silent: Boolean = False);
 procedure g_Game_StartVote(Command, Initiator: string);
 procedure g_Game_CheckVote;
 procedure g_TakeScreenShot(Filename: string = '');
@@ -132,6 +165,8 @@ procedure g_SimpleError(Text: String);
 function  g_Game_IsTestMap(): Boolean;
 procedure g_Game_DeleteTestMap();
 procedure GameCVars(P: SSArray);
+procedure PlayerSettingsCVars(P: SSArray);
+procedure SystemCommands(P: SSArray);
 procedure GameCommands(P: SSArray);
 procedure GameCheats(P: SSArray);
 procedure DebugCommands(P: SSArray);
@@ -172,16 +207,6 @@ const
   EXIT_ENDLEVELSINGLE  = 4;
   EXIT_ENDLEVELCUSTOM  = 5;
 
-  GAME_OPTION_RESERVED     = 1;
-  GAME_OPTION_TEAMDAMAGE   = 2;
-  GAME_OPTION_ALLOWEXIT    = 4;
-  GAME_OPTION_WEAPONSTAY   = 8;
-  GAME_OPTION_MONSTERS     = 16;
-  GAME_OPTION_BOTVSPLAYER  = 32;
-  GAME_OPTION_BOTVSMONSTER = 64;
-  GAME_OPTION_DMKEYS       = 128;
-  GAME_OPTION_RESPAWNITEMS = 256;
-
   STATE_NONE        = 0;
   STATE_MENU        = 1;
   STATE_FOLD        = 2;
@@ -238,6 +263,7 @@ var
   gPlayer2: TPlayer = nil;
   gPlayerDrawn: TPlayer = nil;
   gTime: LongWord;
+  gLerpFactor: Single = 1.0;
   gSwitchGameMode: Byte = GM_DM;
   gHearPoint1, gHearPoint2: THearPoint;
   gSoundEffectsDF: Boolean = False;
@@ -261,8 +287,9 @@ var
   gPauseHolmes: Boolean = false;
   gShowTime: Boolean = False;
   gShowFPS: Boolean = False;
-  gShowGoals: Boolean = True;
+  gShowScore: Boolean = True;
   gShowStat: Boolean = True;
+  gShowPIDs: Boolean = False;
   gShowKillMsg: Boolean = True;
   gShowLives: Boolean = True;
   gShowPing: Boolean = False;
@@ -291,7 +318,8 @@ var
   gMapToDelete: String;
   gTempDelete: Boolean = False;
   gLastMap: Boolean = False;
-  gWinSizeX, gWinSizeY: Integer;
+  gScreenWidth: Word;
+  gScreenHeight: Word;
   gResolutionChange: Boolean = False;
   gRC_Width, gRC_Height: Integer;
   gRC_FullScreen, gRC_Maximized: Boolean;
@@ -331,8 +359,10 @@ var
   gDelayedEvents: Array of TDelayedEvent;
   gUseChatSounds: Boolean = True;
   gChatSounds: Array of TChatSound;
+  gWeaponAction: Array [0..1, WP_FACT..WP_LACT] of Boolean; // [player, weapon_action]
   gSelectWeapon: Array [0..1, WP_FIRST..WP_LAST] of Boolean; // [player, weapon]
   gInterReadyCount: Integer = 0;
+  gMaxBots: Integer = 127;
 
   g_dbg_ignore_bounds: Boolean = false;
   r_smallmap_h: Integer = 0; // 0: left; 1: center; 2: right
@@ -382,7 +412,7 @@ uses
   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_main,
+  g_language, g_net, g_main, g_phys,
   ENet, e_msg, g_netmsg, g_netmaster,
   sfs, wadreader, g_system;
 
@@ -649,9 +679,10 @@ end;
 
 // saves a shitty CSV containing the game stats passed to it
 procedure SaveGameStat(Stat: TEndCustomGameStat; Path: string);
-var 
+var
   s: TextFile;
-  dir, fname, map, mode, etime: String;
+  dir, fname, map, mode, etime, flags, strf: String;
+  flag: TGameOption;
   I: Integer;
 begin
   try
@@ -662,6 +693,7 @@ begin
     fname := e_CatPath(fname, StatFilename + '.csv');
     AssignFile(s, fname);
     try
+      SetTextCodePage(s, CP_UTF8);
       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 := '';
@@ -672,16 +704,25 @@ begin
         (Stat.GameTime div 1000 div 60) mod 60,
         Stat.GameTime div 1000 mod 60
       ]);
+      flags := '';
+      strf := '';
+      for flag in gGameSettings.Options do
+      begin
+        flags += strf;
+        System.WriteStr(strf, flag);  // FIXME: rename our utils.WriteStr()
+        flags += strf;
+        strf := ', ';
+      end;
       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', [
+      WriteLn(s, Format('%d,%s,%s,%s,%s,%u,%u,"%s",%s,%d', [
         STATFILE_VERSION,
         StatDate,
         dquoteStr(fname),
         dquoteStr(map),
         mode,
         gGameSettings.TimeLimit,
-        gGameSettings.GoalLimit,
-        gGameSettings.Options,
+        gGameSettings.ScoreLimit,
+        flags,
         etime,
         Length(Stat.PlayerStat)
       ]));
@@ -690,8 +731,8 @@ begin
       //   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]))
+        WriteLn(s,
+          Format('red_score,blue_score' + LineEnding + '%d,%d', [Stat.TeamStat[TEAM_RED].Score, Stat.TeamStat[TEAM_BLUE].Score]))
       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]));
@@ -709,6 +750,17 @@ begin
   CloseFile(s);
 end;
 
+procedure ClearDebugCvars();
+begin
+  g_debug_Sounds := False;
+  g_debug_Frames := False;
+  g_debug_WinMsgs := False;
+  g_debug_MonsterOff := False;
+  g_debug_BotAIOff := 0;
+  g_debug_HealthBar := False;
+  g_Debug_Player := False;
+end;
+
 function g_Game_ModeToText(Mode: Byte): string;
 begin
   Result := '';
@@ -1065,7 +1117,7 @@ begin
 
           SortGameStat(CustomStat.PlayerStat);
 
-          if (gSaveStats or gScreenshotStats) and (Length(gPlayers) > 1) then
+          if (gSaveStats or gScreenshotStats) and (Length(CustomStat.PlayerStat) > 1) then
           begin
             t := Now;
             if g_Game_IsNet then StatFilename := NetServerName else StatFilename := 'local';
@@ -1157,6 +1209,7 @@ var
   stat: TPlayerStatArray;
   wad, map: string;
   mapstr: string;
+  namestr: string;
 begin
   s1 := '';
   s2 := '';
@@ -1188,7 +1241,7 @@ begin
         s1 := _lc[I_GAME_DM]
       else
         s1 := _lc[I_GAME_LMS];
-      s2 := Format(_lc[I_GAME_FRAG_LIMIT], [gGameSettings.GoalLimit]);
+      s2 := Format(_lc[I_GAME_FRAG_LIMIT], [gGameSettings.ScoreLimit]);
       s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
     end;
 
@@ -1198,14 +1251,14 @@ begin
         s1 := _lc[I_GAME_TDM]
       else
         s1 := _lc[I_GAME_TLMS];
-      s2 := Format(_lc[I_GAME_FRAG_LIMIT], [gGameSettings.GoalLimit]);
+      s2 := Format(_lc[I_GAME_FRAG_LIMIT], [gGameSettings.ScoreLimit]);
       s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
     end;
 
     GM_CTF:
     begin
       s1 := _lc[I_GAME_CTF];
-      s2 := Format(_lc[I_GAME_SCORE_LIMIT], [gGameSettings.GoalLimit]);
+      s2 := Format(_lc[I_GAME_SCORE_LIMIT], [gGameSettings.ScoreLimit]);
       s3 := Format(_lc[I_GAME_TIME_LIMIT], [gGameSettings.TimeLimit div 3600, (gGameSettings.TimeLimit div 60) mod 60, gGameSettings.TimeLimit mod 60]);
     end;
 
@@ -1275,7 +1328,7 @@ begin
       end;
 
       e_TextureFontPrintEx(x+16, _y, s1, gStdFont, r, g, b, 1);
-      e_TextureFontPrintEx(x+w1+16, _y, IntToStr(gTeamStat[a].Goals),
+      e_TextureFontPrintEx(x+w1+16, _y, IntToStr(gTeamStat[a].Score),
                            gStdFont, r, g, b, 1);
 
       _y := _y+ch+(ch div 4);
@@ -1298,8 +1351,12 @@ begin
               gg := g;
               bb := b;
             end;
+            if gShowPIDs then
+              namestr := Format('[%5d] %s', [UID, Name])
+            else
+              namestr := Name;
             // Èìÿ
-            e_TextureFontPrintEx(x+16, _y, Name, gStdFont, rr, gg, bb, 1);
+            e_TextureFontPrintEx(x+16, _y, namestr, gStdFont, rr, gg, bb, 1);
             // Ïèíã/ïîòåðè
             e_TextureFontPrintEx(x+w1+16, _y, Format(_lc[I_GAME_PING_MS], [Ping, Loss]), gStdFont, rr, gg, bb, 1);
             // Ôðàãè
@@ -1334,11 +1391,15 @@ begin
           r := 255;
           g := 127;
         end;
+        if gShowPIDs then
+          namestr := Format('[%5d] %s', [UID, Name])
+        else
+          namestr := Name;
         // Öâåò èãðîêà
         e_DrawFillQuad(x+16, _y+4, x+32-1, _y+16+4-1, Color.R, Color.G, Color.B, 0);
         e_DrawQuad(x+16, _y+4, x+32-1, _y+16+4-1, 192, 192, 192);
         // Èìÿ
-        e_TextureFontPrintEx(x+16+16+8, _y+4, Name, gStdFont, r, g, 0, 1);
+        e_TextureFontPrintEx(x+16+16+8, _y+4, namestr, gStdFont, r, g, 0, 1);
         // Ïèíã/ïîòåðè
         e_TextureFontPrintEx(x+w1+16, _y+4, Format(_lc[I_GAME_PING_MS], [Ping, Loss]), gStdFont, r, g, 0, 1);
         // Ôðàãè
@@ -1672,12 +1733,16 @@ begin
   if gPlayerAction[p, ACTION_LOOKUP] then plr.PressKey(KEY_UP, time);
   if gPlayerAction[p, ACTION_LOOKDOWN] then plr.PressKey(KEY_DOWN, time);
   if gPlayerAction[p, ACTION_ATTACK] then plr.PressKey(KEY_FIRE);
-  if gPlayerAction[p, ACTION_WEAPNEXT] then plr.PressKey(KEY_NEXTWEAPON);
-  if gPlayerAction[p, ACTION_WEAPPREV] then plr.PressKey(KEY_PREVWEAPON);
   if gPlayerAction[p, ACTION_ACTIVATE] then plr.PressKey(KEY_OPEN);
 
-  gPlayerAction[p, ACTION_WEAPNEXT] := False; // HACK, remove after readyweaon&pendinweapon implementation
-  gPlayerAction[p, ACTION_WEAPPREV] := False; // HACK, remove after readyweaon&pendinweapon implementation
+  for i := WP_FACT to WP_LACT do
+  begin
+    if gWeaponAction[p, i] then
+    begin
+      plr.ProcessWeaponAction(i);
+      gWeaponAction[p, i] := False
+    end
+  end;
 
   for i := WP_FIRST to WP_LAST do
   begin
@@ -1712,6 +1777,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;
@@ -1745,6 +1821,16 @@ var
     if mon.gncNeedSend then MH_SEND_MonsterPos(mon.UID);
   end;
 
+  function sendItemPos (it: PItem): Boolean;
+  begin
+    result := false; // don't stop
+    if it.needSend then
+    begin
+      MH_SEND_ItemPos(it.myId);
+      it.needSend := False;
+    end;
+  end;
+
 var
   reliableUpdate: Boolean;
 begin
@@ -1872,6 +1958,7 @@ begin
           // Çàêîí÷èëñÿ óðîâåíü â Ñâîåé èãðå:
             if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
               begin
+                gState := STATE_INTERCUSTOM;
                 InterReadyTime := -1;
                 if gLastMap and (gGameSettings.GameMode = GM_COOP) then
                 begin
@@ -1881,9 +1968,7 @@ begin
                 end
                 else
                   gMusic.SetByName('MUSIC_ROUNDMUS');
-
                 gMusic.Play();
-                gState := STATE_INTERCUSTOM;
                 e_UnpressAllKeys();
               end
             else // Çàêîí÷èëàñü ïîñëåäíÿÿ êàðòà â Îäèíî÷íîé èãðå
@@ -1959,7 +2044,7 @@ begin
         gFlags[FLAG_BLUE].CaptureTime := gFlags[FLAG_BLUE].CaptureTime + GAME_TICK;
 
     // Áûë çàäàí ëèìèò ïîáåä:
-      if (gGameSettings.GoalLimit > 0) then
+      if (gGameSettings.ScoreLimit > 0) then
       begin
         b := 0;
 
@@ -1973,11 +2058,11 @@ begin
         else
           if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
           begin // Â CTF/TDM âûáèðàåì êîìàíäó ñ íàèáîëüøèì ñ÷åòîì
-            b := Max(gTeamStat[TEAM_RED].Goals, gTeamStat[TEAM_BLUE].Goals);
+            b := Max(gTeamStat[TEAM_RED].Score, gTeamStat[TEAM_BLUE].Score);
           end;
 
       // Ëèìèò ïîáåä íàáðàí => êîíåö óðîâíÿ:
-        if b >= gGameSettings.GoalLimit then
+        if b >= gGameSettings.ScoreLimit then
         begin
           g_Game_NextLevel();
           Exit;
@@ -2027,17 +2112,17 @@ begin
             gSpectY := Max(gSpectY - gSpectStep, 0);
           if gPlayerAction[0, ACTION_LOOKDOWN] then
             gSpectY := Min(gSpectY + gSpectStep, gMapInfo.Height - gScreenHeight);
-          if gPlayerAction[0, ACTION_WEAPPREV] then
+          if gWeaponAction[0, WP_PREV] then
           begin
             // decrease step
             if gSpectStep > 4 then gSpectStep := gSpectStep shr 1;
-            gSpectKeyPress := True;
+            gWeaponAction[0, WP_PREV] := False;
           end;
-          if gPlayerAction[0, ACTION_WEAPNEXT] then
+          if gWeaponAction[0, WP_NEXT] then
           begin
             // increase step
             if gSpectStep < 64 then gSpectStep := gSpectStep shl 1;
-            gSpectKeyPress := True;
+            gWeaponAction[0, WP_NEXT] := False;
           end;
         end;
         if (gSpectMode = SPECT_PLAYERS)
@@ -2067,17 +2152,17 @@ begin
             gSpectPID1 := GetActivePlayerID_Next(gSpectPID1);
             gSpectKeyPress := True;
           end;
-          if gPlayerAction[0, ACTION_WEAPPREV] then
+          if gWeaponAction[0, WP_PREV] then
           begin
             // prev player (view 2)
             gSpectPID2 := GetActivePlayerID_Prev(gSpectPID2);
-            gSpectKeyPress := True;
+            gWeaponAction[0, WP_PREV] := False;
           end;
-          if gPlayerAction[0, ACTION_WEAPNEXT] then
+          if gWeaponAction[0, WP_NEXT] then
           begin
             // next player (view 2)
             gSpectPID2 := GetActivePlayerID_Next(gSpectPID2);
-            gSpectKeyPress := True;
+            gWeaponAction[0, WP_NEXT] := False;
           end;
         end;
         if gPlayerAction[0, ACTION_ATTACK] then
@@ -2104,9 +2189,7 @@ begin
            (not gPlayerAction[0, ACTION_MOVELEFT]) and
            (not gPlayerAction[0, ACTION_MOVERIGHT]) and
            (not gPlayerAction[0, ACTION_LOOKUP]) and
-           (not gPlayerAction[0, ACTION_LOOKDOWN]) and
-           (not gPlayerAction[0, ACTION_WEAPPREV]) and
-           (not gPlayerAction[0, ACTION_WEAPNEXT]) then
+           (not gPlayerAction[0, ACTION_LOOKDOWN]) then
           gSpectKeyPress := False;
 
       if gSpectAuto then
@@ -2206,6 +2289,18 @@ begin
 
         g_Mons_ForEach(sendMonsPos);
 
+        // update flags that aren't stationary
+        if gGameSettings.GameMode = GM_CTF then
+          for I := FLAG_RED to FLAG_BLUE do
+            if gFlags[I].NeedSend then
+            begin
+              gFlags[I].NeedSend := False;
+              MH_SEND_FlagPos(I);
+            end;
+
+        // update items that aren't stationary
+        g_Items_ForEachAlive(sendItemPos);
+
         if reliableUpdate then
         begin
           NetTimeToReliable := 0;
@@ -2786,8 +2881,8 @@ 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]
+      if TeamStat[TEAM_RED].Score > TeamStat[TEAM_BLUE].Score then s1 := _lc[I_GAME_WIN_RED]
+        else if TeamStat[TEAM_BLUE].Score > TeamStat[TEAM_RED].Score then s1 := _lc[I_GAME_WIN_BLUE]
           else s1 := _lc[I_GAME_WIN_DRAW];
 
     e_TextureFontPrintEx(x+8+(w div 2)-(Length(s1)*ww2 div 2), _y, s1, gStdFont, 255, 255, 255, 1);
@@ -2799,7 +2894,7 @@ begin
       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),
+        e_TextureFontPrintEx(x+w1+8, _y, IntToStr(CustomStat.TeamStat[TEAM_RED].Score),
                              gStdFont, 255, 0, 0, 1);
         r := 255;
         g := 0;
@@ -2809,7 +2904,7 @@ begin
       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),
+        e_TextureFontPrintEx(x+w1+8, _y, IntToStr(CustomStat.TeamStat[TEAM_BLUE].Score),
                              gStdFont, 0, 0, 255, 1);
         r := 0;
         g := 0;
@@ -2876,7 +2971,7 @@ begin
   end;
 
   // HACK: take stats screenshot immediately after the first frame of the stats showing
-  if gScreenshotStats and not StatShotDone then
+  if gScreenshotStats and (not StatShotDone) and (Length(CustomStat.PlayerStat) > 1) then
   begin
     g_TakeScreenShot('stats/' + StatFilename);
     StatShotDone := True;
@@ -3418,7 +3513,7 @@ begin
     end
     else
     begin
-      glScissor(0, 0, gWinSizeX, gWinSizeY);
+      glScissor(0, 0, gScreenWidth, gScreenHeight);
     end;
     // no need to clear stencil buffer, light blitting will do it for us... but only for normal scale
     if (g_dbg_scale <> 1.0) then glClear(GL_STENCIL_BUFFER_BIT);
@@ -3618,7 +3713,8 @@ end;
 
 procedure DrawPlayer(p: TPlayer);
 var
-  px, py, a, b, c, d, i: Integer;
+  px, py, a, b, c, d, i, fX, fY: Integer;
+  camObj: TObj;
   //R: TRect;
 begin
   if (p = nil) or (p.FDummy) then
@@ -3636,8 +3732,10 @@ begin
 
   glPushMatrix();
 
-  px := p.GameX + PLAYER_RECT_CX;
-  py := p.GameY + PLAYER_RECT_CY+p.Obj.slopeUpLeft;
+  camObj := p.getCameraObj();
+  camObj.lerp(gLerpFactor, fX, fY);
+  px := fX + PLAYER_RECT_CX;
+  py := fY + PLAYER_RECT_CY+nlerp(p.SlopeOld, camObj.slopeUpLeft, gLerpFactor);
 
   if (g_dbg_scale = 1.0) and (not g_dbg_ignore_bounds) then
   begin
@@ -3694,7 +3792,7 @@ begin
       p.IncCam := nclamp(p.IncCam, min(0, -120 - i), 0);
   end;
 
-  sY := sY - p.IncCam;
+  sY := sY - nlerp(p.IncCamOld, p.IncCam, gLerpFactor);
 
   if (not g_dbg_ignore_bounds) then
   begin
@@ -3766,10 +3864,6 @@ begin
               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
@@ -3792,7 +3886,7 @@ begin
 
   p.DrawPain();
   p.DrawPickup();
-  p.DrawRulez();
+  p.DrawOverlay();
   if gShowMap then DrawMinimap(p, _TRect(0, 0, 128, 128));
   if g_Debug_Player then
     g_Player_DrawDebug(p);
@@ -3804,9 +3898,20 @@ 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;
+  if g_profile_frame_draw and (profileFrameDraw <> nil) then
+    px -= drawProfiles(px, py, profileFrameDraw);
+
+  if g_profile_collision and (profMapCollision <> nil) then
+  begin
+    px -= drawProfiles(px, py, profMapCollision);
+    py -= calcProfilesHeight(profMonsLOS);
+  end;
+
+  if g_profile_los and (profMonsLOS <> nil) then
+  begin
+    px -= drawProfiles(px, py, profMonsLOS);
+    py -= calcProfilesHeight(profMonsLOS);
+  end;
 end;
 
 procedure g_Game_Draw();
@@ -3830,6 +3935,9 @@ begin
     FPSTime := Time;
   end;
 
+  e_SetRendertarget(True);
+  e_SetViewPort(0, 0, gScreenWidth, gScreenHeight);
+
   if gGameOn or (gState = STATE_FOLD) then
   begin
     if (gPlayer1 <> nil) and (gPlayer2 <> nil) then
@@ -3970,7 +4078,8 @@ begin
                   Round(gScreenHeight / 2.75)-(h div 2), MessageText);
     end;
 
-    if IsDrawStat or (gSpectMode = 1) then DrawStat();
+    if IsDrawStat or (gSpectMode = SPECT_STATS) then
+      DrawStat();
 
     if gSpectHUD and (not gChatShow) and (gSpectMode <> SPECT_NONE) and (not gSpectAuto) then
     begin
@@ -4160,10 +4269,20 @@ begin
 
   if gGameOn then drawProfilers();
 
+  // TODO: draw this after the FBO and remap mouse click coordinates
+
 {$IFDEF ENABLE_HOLMES}
   g_Holmes_DrawUI();
 {$ENDIF}
 
+  // blit framebuffer to screen
+
+  e_SetRendertarget(False);
+  e_SetViewPort(0, 0, gWinSizeX, gWinSizeY);
+  e_BlitFramebuffer(gWinSizeX, gWinSizeY);
+
+  // draw the overlay stuff on top of it
+
   g_Touch_Draw;
 end;
 
@@ -4195,6 +4314,7 @@ begin
   e_WriteLog(Format(_lc[I_FATAL_ERROR], [Text]), TMsgType.Warning);
 
   gExit := EXIT_SIMPLE;
+  if gGameOn then EndGame;
 end;
 
 procedure g_SimpleError(Text: String);
@@ -4252,15 +4372,16 @@ begin
   if ((not gGameOn) and (gState <> STATE_INTERCUSTOM))
   or (not (gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT])) then
     Exit;
+
+  if (gGameSettings.MaxLives > 0) and (gLMSRespawn = LMS_RESPAWN_NONE) 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;
 
@@ -4276,6 +4397,10 @@ begin
     else
     begin
       gPlayer1.Name := gPlayer1Settings.Name;
+      gPlayer1.WeapSwitchMode := gPlayer1Settings.WeaponSwitch;
+      gPlayer1.setWeaponPrefs(gPlayer1Settings.WeaponPreferences);
+      gPlayer1.SwitchToEmpty := gPlayer1Settings.SwitchToEmpty;
+      gPlayer1.SkipIronFist := gPlayer1Settings.SkipIronFist;
       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);
@@ -4306,6 +4431,10 @@ begin
     else
     begin
       gPlayer2.Name := gPlayer2Settings.Name;
+      gPlayer2.WeapSwitchMode := gPlayer2Settings.WeaponSwitch;
+      gPlayer2.setWeaponPrefs(gPlayer2Settings.WeaponPreferences);
+      gPlayer2.SwitchToEmpty := gPlayer2Settings.SwitchToEmpty;
+      gPlayer2.SkipIronFist := gPlayer2Settings.SkipIronFist;
       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);
@@ -4334,8 +4463,12 @@ begin
       g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [Pl.Name]), True);
       g_Player_Remove(Pl.UID);
       g_Net_Slist_ServerPlayerLeaves();
-    end else
+    end
+    else
+    begin
+      gSpectLatchPID2 := Pl.UID;
       gPlayer2 := nil;
+    end;
     Exit;
   end;
   Pl := gPlayer1;
@@ -4350,6 +4483,7 @@ begin
       g_Net_Slist_ServerPlayerLeaves();
     end else
     begin
+      gSpectLatchPID1 := Pl.UID;
       gPlayer1 := nil;
       MC_SEND_CheatRequest(NET_CHEAT_SPECTATE);
     end;
@@ -4383,16 +4517,22 @@ begin
   g_Game_ClearLoading();
 
 // Íàñòðîéêè èãðû:
-  FillByte(gGameSettings, SizeOf(TGameSettings), 0);
+  gGameSettings := Default(TGameSettings);
   gAimLine := False;
   gShowMap := False;
   gGameSettings.GameType := GT_SINGLE;
   gGameSettings.MaxLives := 0;
-  gGameSettings.Options := gGameSettings.Options + GAME_OPTION_ALLOWEXIT;
-  gGameSettings.Options := gGameSettings.Options + GAME_OPTION_MONSTERS;
-  gGameSettings.Options := gGameSettings.Options + GAME_OPTION_BOTVSMONSTER;
+  gGameSettings.Options := [TGameOption.ALLOW_EXIT, TGameOption.MONSTERS,
+    TGameOption.BOTS_VS_MONSTERS, TGameOption.TEAM_HIT_PROJECTILE, TGameOption.TEAM_HIT_TRACE,
+    TGameOption.POWERUP_RANDOM, TGameOption.ITEM_ALL_RANDOM, TGameOption.ITEM_LIFE_RANDOM,
+    TGameOption.ITEM_AMMO_RANDOM, TGameOption.ITEM_WEAPON_RANDOM];
   gSwitchGameMode := GM_SINGLE;
 
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
+
   g_Game_ExecuteEvent('ongamestart');
 
 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
@@ -4409,6 +4549,10 @@ begin
   end;
 
   gPlayer1.Name := gPlayer1Settings.Name;
+  gPlayer1.WeapSwitchMode := gPlayer1Settings.WeaponSwitch;
+  gPlayer1.setWeaponPrefs(gPlayer1Settings.WeaponPreferences);
+  gPlayer1.SwitchToEmpty := gPlayer1Settings.SwitchToEmpty;
+  gPlayer1.SkipIronFist := gPlayer1Settings.SkipIronFist;
   nPl := 1;
 
 // Ñîçäàíèå âòîðîãî èãðîêà, åñëè åñòü:
@@ -4424,6 +4568,10 @@ begin
     end;
 
     gPlayer2.Name := gPlayer2Settings.Name;
+    gPlayer2.WeapSwitchMode := gPlayer2Settings.WeaponSwitch;
+    gPlayer2.setWeaponPrefs(gPlayer2Settings.WeaponPreferences);
+    gPlayer2.SwitchToEmpty := gPlayer2Settings.SwitchToEmpty;
+    gPlayer2.SkipIronFist := gPlayer2Settings.SkipIronFist;
     Inc(nPl);
   end;
 
@@ -4444,9 +4592,9 @@ begin
 end;
 
 procedure g_Game_StartCustom(Map: String; GameMode: Byte;
-                             TimeLimit, GoalLimit: Word;
+                             TimeLimit, ScoreLimit: Word;
                              MaxLives: Byte;
-                             Options: LongWord; nPlayers: Byte);
+                             Options: TGameOptions; nPlayers: Byte);
 var
   i, nPl: Integer;
 begin
@@ -4461,7 +4609,7 @@ begin
   gGameSettings.GameMode := GameMode;
   gSwitchGameMode := GameMode;
   gGameSettings.TimeLimit := TimeLimit;
-  gGameSettings.GoalLimit := GoalLimit;
+  gGameSettings.ScoreLimit := ScoreLimit;
   gGameSettings.MaxLives := IfThen(GameMode = GM_CTF, 0, MaxLives);
   gGameSettings.Options := Options;
 
@@ -4472,6 +4620,11 @@ begin
   gAimLine := False;
   gShowMap := False;
 
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
+
   g_Game_ExecuteEvent('ongamestart');
 
 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ:
@@ -4498,6 +4651,10 @@ begin
     end;
 
     gPlayer1.Name := gPlayer1Settings.Name;
+    gPlayer1.WeapSwitchMode := gPlayer1Settings.WeaponSwitch;
+    gPlayer1.setWeaponPrefs(gPlayer1Settings.WeaponPreferences);
+    gPlayer1.SwitchToEmpty := gPlayer1Settings.SwitchToEmpty;
+    gPlayer1.SkipIronFist := gPlayer1Settings.SkipIronFist;
     Inc(nPl);
   end;
 
@@ -4514,6 +4671,10 @@ begin
     end;
 
     gPlayer2.Name := gPlayer2Settings.Name;
+    gPlayer2.WeapSwitchMode := gPlayer2Settings.WeaponSwitch;
+    gPlayer2.setWeaponPrefs(gPlayer2Settings.WeaponPreferences);
+    gPlayer2.SwitchToEmpty := gPlayer2Settings.SwitchToEmpty;
+    gPlayer2.SkipIronFist := gPlayer2Settings.SkipIronFist;
     Inc(nPl);
   end;
 
@@ -4544,8 +4705,8 @@ begin
 end;
 
 procedure g_Game_StartServer(Map: String; GameMode: Byte;
-                             TimeLimit, GoalLimit: Word; MaxLives: Byte;
-                             Options: LongWord; nPlayers: Byte;
+                             TimeLimit, ScoreLimit: Word; MaxLives: Byte;
+                             Options: TGameOptions; nPlayers: Byte;
                              IPAddr: LongWord; Port: Word);
 begin
   g_Game_Free();
@@ -4555,12 +4716,14 @@ begin
 
   g_Game_ClearLoading();
 
+  ClearDebugCvars();
+
 // Íàñòðîéêè èãðû:
   gGameSettings.GameType := GT_SERVER;
   gGameSettings.GameMode := GameMode;
   gSwitchGameMode := GameMode;
   gGameSettings.TimeLimit := TimeLimit;
-  gGameSettings.GoalLimit := GoalLimit;
+  gGameSettings.ScoreLimit := ScoreLimit;
   gGameSettings.MaxLives := IfThen(GameMode = GM_CTF, 0, MaxLives);
   gGameSettings.Options := Options;
 
@@ -4571,6 +4734,11 @@ begin
   gAimLine := False;
   gShowMap := False;
 
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
+
   g_Game_ExecuteEvent('ongamestart');
 
 // Óñòàíîâêà ðàçìåðîâ îêíà èãðîêà
@@ -4596,6 +4764,10 @@ begin
     end;
 
     gPlayer1.Name := gPlayer1Settings.Name;
+    gPlayer1.WeapSwitchMode := gPlayer1Settings.WeaponSwitch;
+    gPlayer1.setWeaponPrefs(gPlayer1Settings.WeaponPreferences);
+    gPlayer1.SwitchToEmpty := gPlayer1Settings.SwitchToEmpty;
+    gPlayer1.SkipIronFist := gPlayer1Settings.SkipIronFist;
   end;
 
   if nPlayers >= 2 then
@@ -4611,6 +4783,10 @@ begin
     end;
 
     gPlayer2.Name := gPlayer2Settings.Name;
+    gPlayer2.WeapSwitchMode := gPlayer2Settings.WeaponSwitch;
+    gPlayer2.setWeaponPrefs(gPlayer2Settings.WeaponPreferences);
+    gPlayer2.SwitchToEmpty := gPlayer2Settings.SwitchToEmpty;
+    gPlayer2.SkipIronFist := gPlayer2Settings.SkipIronFist;
   end;
 
   g_Game_SetLoadingText(_lc[I_LOAD_HOST], 0, False);
@@ -4624,7 +4800,7 @@ begin
     Exit;
   end;
 
-  g_Net_Slist_Set(NetSlistIP, NetSlistPort, NetSlistList);
+  g_Net_Slist_Set(NetMasterList);
 
   g_Net_Slist_ServerStarted();
 
@@ -4675,6 +4851,8 @@ begin
 
   g_Game_ClearLoading();
 
+  ClearDebugCvars();
+
 // Íàñòðîéêè èãðû:
   gGameSettings.GameType := GT_CLIENT;
 
@@ -4697,6 +4875,11 @@ begin
   // create (or update) map/resource databases
   g_Res_CreateDatabases(true);
 
+  gLMSRespawn := LMS_RESPAWN_NONE;
+  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
+
 // Ñòàðòóåì êëèåíò
   if not g_Net_Connect(Addr, Port) then
   begin
@@ -4719,12 +4902,6 @@ begin
     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
@@ -4747,10 +4924,10 @@ begin
 
           gGameSettings.GameMode := InMsg.ReadByte();
           gSwitchGameMode := gGameSettings.GameMode;
-          gGameSettings.GoalLimit := InMsg.ReadWord();
+          gGameSettings.ScoreLimit := InMsg.ReadWord();
           gGameSettings.TimeLimit := InMsg.ReadWord();
           gGameSettings.MaxLives := InMsg.ReadByte();
-          gGameSettings.Options := InMsg.ReadLongWord();
+          gGameSettings.Options := TGameOptions(InMsg.ReadLongWord());
           T := InMsg.ReadLongWord();
 
           //newResPath := g_Res_SearchSameWAD(MapsDir, WadName, gWADHash);
@@ -4784,6 +4961,10 @@ begin
           end;
 
           gPlayer1.Name := gPlayer1Settings.Name;
+          gPlayer1.WeapSwitchMode := gPlayer1Settings.WeaponSwitch;
+          gPlayer1.setWeaponPrefs(gPlayer1Settings.WeaponPreferences);
+          gPlayer1.SwitchToEmpty := gPlayer1Settings.SwitchToEmpty;
+          gPlayer1.SkipIronFist := gPlayer1Settings.SkipIronFist;
           gPlayer1.UID := NetPlrUID1;
           gPlayer1.Reset(True);
 
@@ -4820,8 +5001,7 @@ begin
       end;
     end;
 
-    ProcessLoading(true);
-
+    ProcessLoading(True);
     if g_Net_UserRequestExit() then
     begin
       State := 0;
@@ -4836,9 +5016,6 @@ begin
     Exit;
   end;
 
-  gLMSRespawn := LMS_RESPAWN_NONE;
-  gLMSRespawnTime := 0;
-
   g_Player_Init();
   NetState := NET_STATE_GAME;
   MC_SEND_FullStateRequest;
@@ -4989,8 +5166,8 @@ begin
   NetTimeToUpdate := 1;
   NetTimeToReliable := 0;
   NetTimeToMaster := NetMasterRate;
-  gLMSRespawn := LMS_RESPAWN_NONE;
-  gLMSRespawnTime := 0;
+  gSpectLatchPID1 := 0;
+  gSpectLatchPID2 := 0;
   gMissionFailed := False;
   gNextMap := '';
 
@@ -5008,19 +5185,30 @@ 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
+  // reset full state flags
+    if NetClients <> nil then
+      for I := 0 to High(NetClients) do
+        NetClients[I].FullUpdateSent := False;
+
     MH_SEND_GameEvent(NET_EV_MAPSTART, gGameSettings.GameMode, Map);
 
   // Ìàñòåðñåðâåð
@@ -5038,7 +5226,7 @@ begin
           end;
         end;
 
-    g_Net_UnbanNonPermHosts();
+    g_Net_UnbanNonPerm();
   end;
 
   if gLastMap then
@@ -5165,13 +5353,6 @@ end;
 procedure g_Game_RestartRound(NoMapRestart: Boolean = False);
 var
   i, n, nb, nr: Integer;
-
-  function monRespawn (mon: TMonster): Boolean;
-  begin
-    result := false; // don't stop
-    if not mon.FNoRespawn then mon.Respawn();
-  end;
-
 begin
   if not g_Game_IsServer then Exit;
   if gLMSRespawn = LMS_RESPAWN_NONE then Exit;
@@ -5197,12 +5378,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;
 
@@ -5230,19 +5413,16 @@ begin
     if gGameSettings.GameMode = GM_COOP then
     begin
       gPlayers[i].Frags := 0;
-      gPlayers[i].RecallState;
+      gPlayers[i].RestoreState;
     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;
 
@@ -5381,567 +5561,697 @@ 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;
+  it: PItem;
+
+  procedure ParseGameFlag(Flag: TGameOption; OffMsg, OnMsg: TStrings_Locale; OnMapChange: Boolean = False);
+  var
+    x: Boolean;
   begin
-    with gGameSettings do
+    if Length(P) <= 1 then
+      x := Flag in gsGameFlags
+    else
     begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+      x := P[1] = '1';
+
+      if x
+        then gsGameFlags += [Flag]
+        else gsGameFlags -= [Flag];
+
+      if g_Game_IsServer then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_TEAMDAMAGE
-        else
-          Options := Options and (not GAME_OPTION_TEAMDAMAGE);
+        if x
+          then gGameSettings.Options += [Flag]
+          else gGameSettings.Options -= [Flag];
+        if g_Game_IsNet then MH_SEND_GameSettings;
       end;
+    end;
 
-      if (LongBool(Options and GAME_OPTION_TEAMDAMAGE)) then
-        g_Console_Add(_lc[I_MSG_FRIENDLY_FIRE_ON])
-      else
-        g_Console_Add(_lc[I_MSG_FRIENDLY_FIRE_OFF]);
+    if x
+      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_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 OnMapChange and g_Game_IsServer then
+      g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
+  end;
+
+begin
+  stat := nil;
+  cmd := LowerCase(P[0]);
+
+  case cmd of
+    'g_gamemode': begin
+      if (Length(P) > 1) then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_WEAPONSTAY
-        else
-          Options := Options and (not GAME_OPTION_WEAPONSTAY);
+        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 (LongBool(Options and GAME_OPTION_WEAPONSTAY)) then
-        g_Console_Add(_lc[I_MSG_WEAPONSTAY_ON])
+      if gSwitchGameMode = gGameSettings.GameMode then
+        g_Console_Add(Format(_lc[I_MSG_GAMEMODE_CURRENT],
+                            [g_Game_ModeToText(gGameSettings.GameMode)]))
       else
-        g_Console_Add(_lc[I_MSG_WEAPONSTAY_OFF]);
+        g_Console_Add(Format(_lc[I_MSG_GAMEMODE_CHANGE],
+                            [g_Game_ModeToText(gGameSettings.GameMode),
+                             g_Game_ModeToText(gSwitchGameMode)]));
+    end;
+
+    'g_friendlyfire':
+      ParseGameFlag(TGameOption.TEAM_DAMAGE, I_MSG_FRIENDLY_FIRE_OFF, I_MSG_FRIENDLY_FIRE_ON);
+    'g_friendly_absorb_damage':
+      ParseGameFlag(TGameOption.TEAM_ABSORB_DAMAGE, I_MSG_FRIENDLY_ABSORB_DAMAGE_OFF, I_MSG_FRIENDLY_ABSORB_DAMAGE_ON);
+    'g_friendly_hit_trace':
+      ParseGameFlag(TGameOption.TEAM_HIT_TRACE, I_MSG_FRIENDLY_HIT_TRACE_OFF, I_MSG_FRIENDLY_HIT_TRACE_ON);
+    'g_friendly_hit_projectile':
+      ParseGameFlag(TGameOption.TEAM_HIT_PROJECTILE, I_MSG_FRIENDLY_PROJECT_TRACE_OFF, I_MSG_FRIENDLY_PROJECT_TRACE_ON);
+    'g_items_all_respawn_random':
+      ParseGameFlag(TGameOption.ITEM_ALL_RANDOM, I_MSG_ITEM_ALL_RANDOM_OFF, I_MSG_ITEM_ALL_RANDOM_ON, False);
+    'g_items_help_respawn_random':
+      ParseGameFlag(TGameOption.ITEM_LIFE_RANDOM, I_MSG_ITEM_LIFE_RANDOM_OFF, I_MSG_ITEM_LIFE_RANDOM_ON, False);
+    'g_items_ammo_respawn_random':
+      ParseGameFlag(TGameOption.ITEM_AMMO_RANDOM, I_MSG_ITEM_AMMO_RANDOM_OFF, I_MSG_ITEM_AMMO_RANDOM_ON, False);
+    'g_items_weapon_respawn_random':
+      ParseGameFlag(TGameOption.ITEM_WEAPON_RANDOM, I_MSG_ITEM_WEAPON_RANDOM_OFF, I_MSG_ITEM_WEAPON_RANDOM_ON);
+    'g_powerup_randomize_respawn':
+      ParseGameFlag(TGameOption.POWERUP_RANDOM, I_MSG_POWERUP_RANDOM_OFF, I_MSG_POWERUP_RANDOM_ON, False);
+    'g_weaponstay':
+      ParseGameFlag(TGameOption.WEAPONS_STAY, I_MSG_WEAPONSTAY_OFF, I_MSG_WEAPONSTAY_ON);
+    'g_allow_exit':
+      ParseGameFlag(TGameOption.ALLOW_EXIT, I_MSG_ALLOWEXIT_OFF, I_MSG_ALLOWEXIT_ON, True);
+    'g_allow_monsters':
+      ParseGameFlag(TGameOption.MONSTERS, I_MSG_ALLOWMON_OFF, I_MSG_ALLOWMON_ON, True);
+    'g_allow_dropflag':
+      ParseGameFlag(TGameOption.ALLOW_DROP_FLAG, I_MSG_ALLOWDROPFLAG_OFF, I_MSG_ALLOWDROPFLAG_ON);
+    'g_throw_flag':
+      ParseGameFlag(TGameOption.THROW_FLAG, I_MSG_THROWFLAG_OFF, I_MSG_THROWFLAG_ON);
+    'g_bot_vsplayers':
+      ParseGameFlag(TGameOption.BOTS_VS_PLAYERS, I_MSG_BOTSVSPLAYERS_OFF, I_MSG_BOTSVSPLAYERS_ON);
+    'g_bot_vsmonsters':
+      ParseGameFlag(TGameOption.BOTS_VS_MONSTERS, I_MSG_BOTSVSMONSTERS_OFF, I_MSG_BOTSVSMONSTERS_ON);
+    'g_dm_keys':
+      ParseGameFlag(TGameOption.DM_KEYS, I_MSG_DMKEYS_OFF, I_MSG_DMKEYS_ON, True);
+
+    'g_gameflags': begin
+      if Length(P) > 1 then
+      begin
+        gsGameFlags := TGameOptions(StrToDWordDef(P[1], LongWord(gsGameFlags)));
+        if g_Game_IsServer then
+        begin
+          gGameSettings.Options := gsGameFlags;
+          if g_Game_IsNet then MH_SEND_GameSettings;
+        end;
+      end;
 
-      if g_Game_IsNet then MH_SEND_GameSettings;
-    end;
-  end
-  else 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
-    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;
+      g_Console_Add(Format('%s %u', [cmd, LongWord(gsGameFlags)]));
     end;
-    if gSwitchGameMode = gGameSettings.GameMode then
-      g_Console_Add(Format(_lc[I_MSG_GAMEMODE_CURRENT],
-                          [g_Game_ModeToText(gGameSettings.GameMode)]))
-    else
-      g_Console_Add(Format(_lc[I_MSG_GAMEMODE_CHANGE],
-                          [g_Game_ModeToText(gGameSettings.GameMode),
-                           g_Game_ModeToText(gSwitchGameMode)]));
-  end
-  else if (cmd = 'g_allow_exit') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+
+    'g_warmup_time': begin
+      if Length(P) > 1 then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_ALLOWEXIT
-        else
-          Options := Options and (not GAME_OPTION_ALLOWEXIT);
+        gsWarmupTime := nclamp(StrToIntDef(P[1], gsWarmupTime), 0, $FFFF);
+        if g_Game_IsServer then
+        begin
+          gGameSettings.WarmupTime := gsWarmupTime;
+          // extend warmup if it's already going
+          if gLMSRespawn = LMS_RESPAWN_WARMUP then
+          begin
+            gLMSRespawnTime := gTime + gsWarmupTime * 1000;
+            if g_Game_IsNet then MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime);
+          end;
+          if g_Game_IsNet then MH_SEND_GameSettings;
+        end;
       end;
 
-      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;
+      g_Console_Add(Format(_lc[I_MSG_WARMUP], [Integer(gsWarmupTime)]));
+      if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
     end;
-  end
-  else if (cmd = 'g_allow_monsters') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+
+    'g_spawn_invul': begin
+      if Length(P) > 1 then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_MONSTERS
-        else
-          Options := Options and (not GAME_OPTION_MONSTERS);
+        gsSpawnInvul := nclamp(StrToIntDef(P[1], gsSpawnInvul), 0, $FFFF);
+        if g_Game_IsServer then
+        begin
+          gGameSettings.SpawnInvul := gsSpawnInvul;
+          if g_Game_IsNet then MH_SEND_GameSettings;
+        end;
       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;
+      g_Console_Add(Format('%s %d', [cmd, Integer(gsSpawnInvul)]));
     end;
-  end
-  else if (cmd = 'g_bot_vsplayers') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+
+    'g_item_respawn_time': begin
+      if Length(P) > 1 then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_BOTVSPLAYER
-        else
-          Options := Options and (not GAME_OPTION_BOTVSPLAYER);
+        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;
 
-      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;
+      g_Console_Add(Format('%s %d', [cmd, Integer(gsItemRespawnTime)]));
+      if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
     end;
-  end
-  else if (cmd = 'g_bot_vsmonsters') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+
+    'g_item_time_random': begin
+      if Length(P) > 1 then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_BOTVSMONSTER
-        else
-          Options := Options and (not GAME_OPTION_BOTVSMONSTER);
+        gsItemRespawnRandom := nclamp(StrToIntDef(P[1], gsItemRespawnRandom), 0, $FFFF);
+        if g_Game_IsServer then
+        begin
+          gGameSettings.ItemRespawnRandom := gsItemRespawnRandom;
+          if g_Game_IsNet then MH_SEND_GameSettings;
+        end;
       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;
+      g_Console_Add(Format('%s %d', [cmd, Integer(gsItemRespawnRandom)]));
+      if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
     end;
-  end
-  else if (cmd = 'g_dm_keys') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+
+    'g_powerup_respawn_time': begin
+      if Length(P) > 1 then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_DMKEYS
-        else
-          Options := Options and (not GAME_OPTION_DMKEYS);
+        gsPowerupRespawnTime := nclamp(StrToIntDef(P[1], gsPowerupRespawnTime), 0, $FFFF);
+        if g_Game_IsServer then
+        begin
+          gGameSettings.PowerupRespawnTime := gsPowerupRespawnTime;
+          if g_Game_IsNet then MH_SEND_GameSettings;
+        end;
       end;
 
-      if (LongBool(Options and GAME_OPTION_DMKEYS)) then
-        g_Console_Add(_lc[I_MSG_DMKEYS_ON])
-      else
-        g_Console_Add(_lc[I_MSG_DMKEYS_OFF]);
-
-      if g_Game_IsNet then MH_SEND_GameSettings;
+      g_Console_Add(Format('%s %d', [cmd, Integer(gsPowerupRespawnTime)]));
+      if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
     end;
-  end
-  else if (cmd = 'g_respawn_items') and not g_Game_IsClient then
-  begin
-    with gGameSettings do
-    begin
-      if (Length(P) > 1) and
-         ((P[1] = '1') or (P[1] = '0')) then
+
+    'g_powerup_time_random': begin
+      if Length(P) > 1 then
       begin
-        if (P[1][1] = '1') then
-          Options := Options or GAME_OPTION_RESPAWNITEMS
-        else
-          Options := Options and (not GAME_OPTION_RESPAWNITEMS);
+        gsPowerupRespawnRandom := nclamp(StrToIntDef(P[1], gsPowerupRespawnRandom), 0, $FFFF);
+        if g_Game_IsServer then
+        begin
+          gGameSettings.PowerupRespawnRandom := gsPowerupRespawnRandom;
+          if g_Game_IsNet then MH_SEND_GameSettings;
+        end;
       end;
 
-      if (LongBool(Options and GAME_OPTION_RESPAWNITEMS)) then
-        g_Console_Add(_lc[I_MSG_RESPAWNITEMS_ON])
-      else
-        g_Console_Add(_lc[I_MSG_RESPAWNITEMS_OFF]);
+      g_Console_Add(Format('%s %d', [cmd, Integer(gsPowerupRespawnRandom)]));
+      if g_Game_IsServer then g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
+    end;
 
-      if g_Game_IsNet then MH_SEND_GameSettings;
+    'sv_intertime': begin
+      if (Length(P) > 1) then
+        gDefInterTime := Min(Max(StrToIntDef(P[1], gDefInterTime), -1), 120);
+
+      g_Console_Add(cmd + ' = ' + IntToStr(gDefInterTime));
     end;
-  end
-  else if (cmd = 'g_warmuptime') and not g_Game_IsClient then
-  begin
-    if Length(P) > 1 then
-    begin
-      if StrToIntDef(P[1], gGameSettings.WarmupTime) = 0 then
-        gGameSettings.WarmupTime := 30
+
+    'g_max_particles': begin
+      if Length(p) = 2 then
+      begin
+        a := Max(0, StrToIntDef(p[1], 0));
+        g_GFX_SetMax(a)
+      end
+      else if Length(p) = 1 then
+      begin
+        e_LogWritefln('%s', [g_GFX_GetMax()])
+      end
       else
-        gGameSettings.WarmupTime := StrToIntDef(P[1], gGameSettings.WarmupTime);
+      begin
+        e_LogWritefln('usage: %s <n>', [cmd])
+      end
     end;
 
-    g_Console_Add(Format(_lc[I_MSG_WARMUP],
-                 [gGameSettings.WarmupTime]));
-    g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
-  end
-  else if (cmd = 'g_spawn_invul') and not g_Game_IsClient then
-  begin
-    if Length(P) > 1 then
-    begin
-      if StrToIntDef(P[1], gGameSettings.SpawnInvul) = 0 then
-        gGameSettings.SpawnInvul := 0
+    'g_max_shells': begin
+      if Length(p) = 2 then
+      begin
+        a := Max(0, StrToIntDef(p[1], 0));
+        g_Shells_SetMax(a)
+      end
+      else if Length(p) = 1 then
+      begin
+        e_LogWritefln('%s', [g_Shells_GetMax()])
+      end
       else
-        gGameSettings.SpawnInvul := StrToIntDef(P[1], gGameSettings.SpawnInvul);
+      begin
+        e_LogWritefln('usage: %s <n>', [cmd])
+      end
     end;
 
-    g_Console_Add(Format(_lc[I_MSG_SPAWNINVUL],
-                 [gGameSettings.SpawnInvul]));
-    g_Console_Add(_lc[I_MSG_ONMAPCHANGE]);
-  end
-  else if cmd = 'net_interp' then
-  begin
-    if (Length(P) > 1) then
-      NetInterpLevel := StrToIntDef(P[1], NetInterpLevel);
-    g_Console_Add('net_interp = ' + IntToStr(NetInterpLevel));
-    s := e_GetWriteableDir(ConfigDirs);
-    if s <> '' then
-    begin
-      config := TConfig.CreateFile(s + '/' + CONFIG_FILENAME);
-      config.WriteInt('Client', 'InterpolationSteps', NetInterpLevel);
-      config.SaveFile(s + '/' + CONFIG_FILENAME);
-      config.Free
-    end
-  end
-  else if cmd = 'net_forceplayerupdate' then
-  begin
-    if (Length(P) > 1) and ((P[1] = '1') or (P[1] = '0')) then
-      NetForcePlayerUpdate := (P[1][1] = '1');
-
-    if NetForcePlayerUpdate then
-      g_Console_Add('net_forceplayerupdate = 1')
-    else
-      g_Console_Add('net_forceplayerupdate = 0');
-
-    s := e_GetWriteableDir(ConfigDirs);
-    if s <> '' then
-    begin
-      config := TConfig.CreateFile(s + '/' + CONFIG_FILENAME);
-      config.WriteBool('Client', 'ForcePlayerUpdate', NetForcePlayerUpdate);
-      config.SaveFile(s + '/' + CONFIG_FILENAME);
-      config.Free
-    end
-  end
-  else if cmd = 'net_predictself' then
-  begin
-    if (Length(P) > 1) and
-       ((P[1] = '1') or (P[1] = '0')) then
-      NetPredictSelf := (P[1][1] = '1');
-
-    if NetPredictSelf then
-      g_Console_Add('net_predictself = 1')
-    else
-      g_Console_Add('net_predictself = 0');
+    'g_max_gibs': begin
+      if Length(p) = 2 then
+      begin
+        a := Max(0, StrToIntDef(p[1], 0));
+        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;
 
-    s := e_GetWriteableDir(ConfigDirs);
-    if s <> '' then
-    begin
-      config := TConfig.CreateFile(s + '/' + CONFIG_FILENAME);
-      config.WriteBool('Client', 'PredictSelf', NetPredictSelf);
-      config.SaveFile(s + '/' + CONFIG_FILENAME);
-      config.Free
-    end
-  end
-  else if cmd = 'sv_name' then
-  begin
-    if (Length(P) > 1) and (Length(P[1]) > 0) then
-    begin
-      NetServerName := P[1];
-      if Length(NetServerName) > 64 then
-        SetLength(NetServerName, 64);
-      g_Net_Slist_ServerRenamed();
+    'g_max_corpses': begin
+      if Length(p) = 2 then
+      begin
+        a := Max(0, StrToIntDef(p[1], 0));
+        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;
 
-    g_Console_Add(cmd + ' = "' + NetServerName + '"');
-  end
-  else if cmd = 'sv_passwd' then
-  begin
-    if (Length(P) > 1) and (Length(P[1]) > 0) then
-    begin
-      NetPassword := P[1];
-      if Length(NetPassword) > 24 then
-        SetLength(NetPassword, 24);
-      g_Net_Slist_ServerRenamed();
+    'g_force_model': begin
+      if Length(p) = 2 then
+      begin
+        a := StrToIntDef(p[1], 0);
+        g_Force_Model_Set(a);
+        if (g_Force_Model_Get() <> 0) and (gPlayers <> nil) then
+        begin
+          for a := Low(gPlayers) to High(gPlayers) do
+          begin
+            if (gPlayers[a] <> nil) then
+            begin
+              if (gPlayers[a].UID = gPlayer1.UID) then
+                continue
+              else if (gPlayer2 <> nil) and (gPlayers[a].UID = gPlayer2.UID) then
+                continue;
+              gPlayers[a].setModel(g_Forced_Model_GetName());
+            end;
+          end
+        end
+        else if (g_Force_Model_Get() = 0) and (gPlayers <> nil) then
+        begin
+          for a := Low(gPlayers) to High(gPlayers) do
+          begin
+            if (gPlayers[a] <> nil) then
+            begin
+              if (gPlayers[a].UID = gPlayer1.UID) then
+                continue
+              else if (gPlayer2 <> nil) and (gPlayers[a].UID = gPlayer2.UID) then
+                continue;
+              gPlayers[a].setModel(gPlayers[a].FActualModelName);
+            end;
+          end
+        end
+      end
     end;
 
-    g_Console_Add(cmd + ' = "' + AnsiLowerCase(NetPassword) + '"');
-  end
-  else if cmd = 'sv_maxplrs' then
-  begin
-    if (Length(P) > 1) then
-    begin
-      NetMaxClients := Min(Max(StrToIntDef(P[1], NetMaxClients), 1), NET_MAXCLIENTS);
-      if g_Game_IsServer and g_Game_IsNet then
+    'g_force_model_name': begin
+      if (Length(P) > 1) then
       begin
-        b := 0;
-        for a := 0 to High(NetClients) do
+        cmd := b_Text_Unformat(P[1]);
+        g_Forced_Model_SetName(cmd);
+        if (g_Force_Model_Get() <> 0) and (gPlayers <> nil) then
         begin
-          if NetClients[a].Used then
+          for a := Low(gPlayers) to High(gPlayers) do
           begin
-            Inc(b);
-            if b > NetMaxClients then
+            if (gPlayers[a] <> nil) 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);
+              if (gPlayers[a].UID = gPlayer1.UID) then
+                continue
+              else if (gPlayer2 <> nil) and (gPlayers[a].UID = gPlayer2.UID) then
+                continue;
+              gPlayers[a].setModel(g_Forced_Model_GetName());
             end;
-          end;
+          end
+        end
+      end
+    end;
+
+    'g_scorelimit': begin
+      if Length(P) > 1 then
+      begin
+        gsScoreLimit := nclamp(StrToIntDef(P[1], gsScoreLimit), 0, $FFFF);
+
+        if g_Game_IsServer then
+        begin
+          b := 0;
+          if gGameSettings.GameMode = GM_DM then
+          begin // DM
+            stat := g_Player_GetStats();
+            if stat <> nil then
+              for a := 0 to High(stat) do
+                if stat[a].Frags > b then
+                  b := stat[a].Frags;
+          end
+          else // TDM/CTF
+            b := Max(gTeamStat[TEAM_RED].Score, gTeamStat[TEAM_BLUE].Score);
+
+          // if someone has a higher score, set it to that instead
+          gsScoreLimit := max(gsScoreLimit, b);
+          gGameSettings.ScoreLimit := gsScoreLimit;
+
+          if g_Game_IsNet then MH_SEND_GameSettings;
         end;
-        g_Net_Slist_ServerRenamed();
       end;
+
+      g_Console_Add(Format(_lc[I_MSG_SCORE_LIMIT], [Integer(gsScoreLimit)]));
     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 NetUseMaster then g_Net_Slist_Public() else g_Net_Slist_Private();
+    'g_timelimit': begin
+      if Length(P) > 1 then
+      begin
+        gsTimeLimit := nclamp(StrToIntDef(P[1], gsTimeLimit), 0, $FFFF);
+        if g_Game_IsServer then
+        begin
+          gGameSettings.TimeLimit := gsTimeLimit;
+          if g_Game_IsNet then MH_SEND_GameSettings;
+        end;
+      end;
+      g_Console_Add(Format(_lc[I_MSG_TIME_LIMIT],
+                           [gsTimeLimit div 3600,
+                           (gsTimeLimit div 60) mod 60,
+                            gsTimeLimit mod 60]));
     end;
 
-    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);
+    'g_max_bots': begin
+      if Length(P) > 1 then
+        gMaxBots := nclamp(StrToIntDef(P[1], gMaxBots), 0, 127);
+      g_Console_Add('g_max_bots = ' + IntToStr(gMaxBots));
+    end;
 
-    g_Console_Add(cmd + ' = ' + IntToStr(gDefInterTime));
-  end
-  else if cmd = 'p1_name' then
-  begin
-    if (Length(P) > 1) and gGameOn then
-    begin
-      if g_Game_IsClient then
+    'g_maxlives': begin
+      if Length(P) > 1 then
       begin
-        gPlayer1Settings.Name := b_Text_Unformat(P[1]);
-        MC_SEND_PlayerSettings;
-      end
-      else
-        if gPlayer1 <> nil then
+        gsMaxLives := nclamp(StrToIntDef(P[1], gsMaxLives), 0, $FFFF);
+        if g_Game_IsServer 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.MaxLives := gsMaxLives;
+          if g_Game_IsNet then MH_SEND_GameSettings;
+        end;
+      end;
+
+      g_Console_Add(Format(_lc[I_MSG_LIVES], [Integer(gsMaxLives)]));
     end;
-  end
-  else if cmd = 'p2_name' then
+  end;
+end;
+
+procedure PlayerSettingsCVars(P: SSArray);
+var
+  cmd: string;
+  team: Byte;
+
+  function ParseTeam(s: string): Byte;
   begin
-    if (Length(P) > 1) and gGameOn then
-    begin
-      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
-        gPlayer2Settings.Name := b_Text_Unformat(P[1]);
-        MC_SEND_PlayerSettings;
-      end
-      else
-        if gPlayer2 <> 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
-          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]);
-    end;
-  end
-  else if cmd = 'p1_color' then
-  begin
-    if Length(P) > 3 then
-      if g_Game_IsClient then
+          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
-        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) > 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;
-
-      g_Console_Add(Format(_lc[I_MSG_LIVES],
-                           [gGameSettings.MaxLives]));
-      if g_Game_IsNet then MH_SEND_GameSettings;
-    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;
+    'p1_autoswitch':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponSwitch := EnsureRange(StrTointDef(P[1], 0), 0, 2);
+        end;
+    'p2_autoswitch':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponSwitch := EnsureRange(StrTointDef(P[1], 0), 0, 2);
+        end;
+    'p1_switch_empty':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.SwitchToEmpty := EnsureRange(StrTointDef(P[1], 0), 0, 1);
+        end;
+    'p2_switch_empty':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.SwitchToEmpty := EnsureRange(StrTointDef(P[1], 0), 0, 1);
+        end;
+    'p1_skip_ironfist':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.SkipIronFist := EnsureRange(StrTointDef(P[1], 0), 0, 1);
+        end;
+    'p2_skip_ironfist':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.SkipIronFist := EnsureRange(StrTointDef(P[1], 0), 0, 1);
+        end;
+    'p1_priority_ironfist':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_IRONFIST] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+        end;
+    'p2_priority_ironfist':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_IRONFIST] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_saw':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_SAW] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+        end;
+    'p2_priority_saw':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_SAW] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_pistol':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_PISTOL] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+        end;
+    'p2_priority_pistol':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_PISTOL] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_shotgun1':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_SHOTGUN1] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p2_priority_shotgun1':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_SHOTGUN1] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_shotgun2':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_SHOTGUN2] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p2_priority_shotgun2':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_SHOTGUN2] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_chaingun':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_CHAINGUN] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p2_priority_chaingun':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_CHAINGUN] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_rocketlauncher':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_ROCKETLAUNCHER] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p2_priority_rocketlauncher':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_ROCKETLAUNCHER] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_plasma':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_PLASMA] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p2_priority_plasma':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_PLASMA] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_bfg':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_BFG] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p2_priority_bfg':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_BFG] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_superchaingun':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_SUPERCHAINGUN] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p2_priority_superchaingun':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_SUPERCHAINGUN] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_flamethrower':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WEAPON_FLAMETHROWER] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p2_priority_flamethrower':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WEAPON_FLAMETHROWER] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
+    'p1_priority_berserk':
+      begin
+        if (Length(P) = 2) then
+          gPlayer1Settings.WeaponPreferences[WP_LAST+1] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+        end;
+    'p2_priority_berserk':
+      begin
+        if (Length(P) = 2) then
+          gPlayer2Settings.WeaponPreferences[WP_LAST+1] := EnsureRange(StrToIntDef(P[1], WP_FIRST), WP_FIRST, WP_LAST+1);
+      end;
   end;
 end;
 
@@ -6120,7 +6430,7 @@ begin
     begin
       cmd := LowerCase(P[f]);
       if cmd = 'health' then begin plr.RestoreHealthArmor(); g_Console_Add('player feels himself better'); continue; end;
-      if (cmd = 'all') {or (cmd = 'weapons')} then begin plr.AllRulez(False); g_Console_Add('player got the gifts'); continue; end;
+      if (cmd = 'all') {or (cmd = 'weapons')} then begin plr.TankRamboCheats(False); g_Console_Add('player got the gifts'); continue; end;
       if cmd = 'exit' then
       begin
         if gTriggers <> nil then
@@ -6155,8 +6465,8 @@ begin
       if cmd = 'greenarmor' then begin plr.GiveItem(ITEM_ARMOR_GREEN); g_Console_Add('player got a security armor'); continue; end;
       if cmd = 'bluearmor' then begin plr.GiveItem(ITEM_ARMOR_BLUE); g_Console_Add('player got a combat armor'); continue; end;
 
-      if (cmd = 'megasphere') or (cmd = 'mega') then begin plr.GiveItem(ITEM_SPHERE_BLUE); g_Console_Add('player got a megasphere'); continue; end;
-      if (cmd = 'soulsphere') or (cmd = 'soul')then begin plr.GiveItem(ITEM_SPHERE_WHITE); g_Console_Add('player got a soul sphere'); continue; end;
+      if (cmd = 'soulsphere') or (cmd = 'soul') then begin plr.GiveItem(ITEM_SPHERE_BLUE); g_Console_Add('player got a soul sphere'); continue; end;
+      if (cmd = 'megasphere') or (cmd = 'mega') then begin plr.GiveItem(ITEM_SPHERE_WHITE); g_Console_Add('player got a megasphere'); continue; end;
 
       if (cmd = 'invul') or (cmd = 'invulnerability') then begin plr.GiveItem(ITEM_INVUL); g_Console_Add('player got invulnerability'); continue; end;
       if (cmd = 'invis') or (cmd = 'invisibility') then begin plr.GiveItem(ITEM_INVIS); g_Console_Add('player got invisibility'); continue; end;
@@ -6179,8 +6489,8 @@ begin
       if cmd = 'plasmagunzz' then begin plr.GiveItem(ITEM_WEAPON_PLASMA); plr.GiveItem(ITEM_AMMO_CELL_BIG); g_Console_Add('player got a plasma gun'); continue; end;
       if cmd = 'bfgzz' then begin plr.GiveItem(ITEM_WEAPON_BFG); plr.GiveItem(ITEM_AMMO_CELL_BIG); g_Console_Add('player got a BFG-9000'); continue; end;
 
-      if cmd = 'superchaingun' then begin plr.GiveItem(ITEM_WEAPON_SUPERPULEMET); g_Console_Add('player got a superchaingun'); continue; end;
-      if cmd = 'superchaingunzz' then begin plr.GiveItem(ITEM_WEAPON_SUPERPULEMET); plr.GiveItem(ITEM_AMMO_BULLETS_BOX); g_Console_Add('player got a superchaingun'); continue; end;
+      if cmd = 'superchaingun' then begin plr.GiveItem(ITEM_WEAPON_SUPERCHAINGUN); g_Console_Add('player got a superchaingun'); continue; end;
+      if cmd = 'superchaingunzz' then begin plr.GiveItem(ITEM_WEAPON_SUPERCHAINGUN); plr.GiveItem(ITEM_AMMO_BULLETS_BOX); g_Console_Add('player got a superchaingun'); continue; end;
 
       if (cmd = 'flamer') or (cmd = 'flamethrower') or (cmd = 'ft') then begin plr.GiveItem(ITEM_WEAPON_FLAMETHROWER); g_Console_Add('player got a flame thrower'); continue; end;
       if (cmd = 'flamerzz') or (cmd = 'flamethrowerzz') or (cmd = 'ftzz') then begin plr.GiveItem(ITEM_WEAPON_FLAMETHROWER); plr.GiveItem(ITEM_AMMO_FUELCAN); g_Console_Add('player got a flame thrower'); continue; end;
@@ -6314,18 +6624,12 @@ var
   nm: Boolean;
   listen: LongWord;
   found: Boolean;
+  t: Byte;
 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);
@@ -6365,7 +6669,7 @@ begin
       if (pl <> nil) then
       begin
         s := g_Net_ClientName_ByID(pl^.ID);
-        enet_peer_disconnect(pl^.Peer, NET_DISC_KICK);
+        g_Net_Host_Kick(pl^.ID, 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();
@@ -6409,7 +6713,7 @@ begin
         if NetClients[a].Used and (NetClients[a].Peer <> nil) then
         begin
           s := g_Net_ClientName_ByID(NetClients[a].ID);
-          enet_peer_disconnect(NetClients[a].Peer, NET_DISC_KICK);
+          g_Net_Host_Kick(NetClients[a].ID, 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();
@@ -6418,6 +6722,34 @@ begin
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
+  else if cmd = 'kick_pid' then
+  begin
+    if g_Game_IsServer and g_Game_IsNet then
+    begin
+      if Length(P) < 2 then
+      begin
+        g_Console_Add('kick_pid <player ID>');
+        Exit;
+      end;
+      if P[1] = '' then
+      begin
+        g_Console_Add('kick_pid <player ID>');
+        Exit;
+      end;
+
+      a := StrToIntDef(P[1], 0);
+      pl := g_Net_Client_ByPlayer(a);
+      if (pl <> nil) and pl^.Used and (pl^.Peer <> nil) then
+      begin
+        s := g_Net_ClientName_ByID(pl^.ID);
+        g_Net_Host_Kick(pl^.ID, 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
@@ -6435,14 +6767,8 @@ begin
 
       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_Net_Host_Ban(pl, False)
+      else
         g_Console_Add(Format(_lc[I_NET_ERR_NAME404], [P[1]]));
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
@@ -6465,14 +6791,29 @@ begin
       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;
+          g_Net_Host_Ban(pl, False);
+    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
+        g_Net_Host_Ban(pl, False);
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
   end
@@ -6493,15 +6834,8 @@ begin
 
       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);
-        enet_peer_disconnect(pl^.Peer, NET_DISC_BAN);
-        g_Net_SaveBanList();
-        g_Console_Add(Format(_lc[I_PLAYER_BAN], [s]));
-        MH_SEND_GameEvent(NET_EV_PLAYER_BAN, 0, s);
-        g_Net_Slist_ServerPlayerLeaves();
-      end else
+        g_Net_Host_Ban(pl, True)
+      else
         g_Console_Add(Format(_lc[I_NET_ERR_NAME404], [P[1]]));
     end else
       g_Console_Add(_lc[I_MSG_SERVERONLY]);
@@ -6524,15 +6858,50 @@ begin
       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);
-          g_Net_Slist_ServerPlayerLeaves();
-        end;
+          g_Net_Host_Ban(@NetClients[a], True);
+    end else
+      g_Console_Add(_lc[I_MSG_SERVERONLY]);
+  end
+  else if cmd = 'permban_pid' then
+  begin
+    if g_Game_IsServer and g_Game_IsNet then
+    begin
+      if Length(P) < 2 then
+      begin
+        g_Console_Add('permban_pid <player ID>');
+        Exit;
+      end;
+      if P[1] = '' then
+      begin
+        g_Console_Add('permban_pid <player ID>');
+        Exit;
+      end;
+
+      a := StrToIntDef(P[1], 0);
+      pl := g_Net_Client_ByPlayer(a);
+      if (pl <> nil) and pl^.Used and (pl^.Peer <> nil) then
+        g_Net_Host_Ban(pl, True);
+    end else
+      g_Console_Add(_lc[I_MSG_SERVERONLY]);
+  end
+  else if cmd = 'permban_ip' then
+  begin
+    if g_Game_IsServer and g_Game_IsNet then
+    begin
+      if Length(P) < 2 then
+      begin
+        g_Console_Add('permban_ip <IP address>');
+        Exit;
+      end;
+      if P[1] = '' then
+      begin
+        g_Console_Add('permban_ip <IP address>');
+        Exit;
+      end;
+
+      g_Net_BanAddress(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
@@ -6551,7 +6920,7 @@ begin
         Exit;
       end;
 
-      if g_Net_UnbanHost(P[1]) then
+      if g_Net_UnbanAddress(P[1]) then
       begin
         g_Console_Add(Format(_lc[I_MSG_UNBAN_OK], [P[1]]));
         g_Net_SaveBanList();
@@ -6631,23 +7000,29 @@ begin
   else if (cmd = 'addbot') or
      (cmd = 'bot_add') then
   begin
-    if Length(P) > 2 then
-      g_Bot_Add(TEAM_NONE, StrToIntDef(P[1], 2), StrToIntDef(P[2], 100))
-    else if Length(P) > 1 then
-      g_Bot_Add(TEAM_NONE, StrToIntDef(P[1], 2))
+    case Length(P) of
+      1: g_Bot_Add(TEAM_NONE, 2);
+      2: g_Bot_Add(TEAM_NONE, StrToIntDef(P[1], 2));
     else
-      g_Bot_Add(TEAM_NONE, 2);
+      g_Bot_Add(TEAM_NONE, StrToIntDef(P[1], 2), StrToIntDef(P[2], 100));
+    end;
   end
   else if cmd = 'bot_addlist' then
   begin
-    if Length(P) > 1 then
-    begin
-      if Length(P) = 2 then
-        g_Bot_AddList(TEAM_NONE, P[1], StrToIntDef(P[1], -1))
-      else if Length(P) = 3 then
-        g_Bot_AddList(TEAM_NONE, P[1], StrToIntDef(P[1], -1), StrToIntDef(P[2], 100))
+    case Length(P) of
+      1: g_Bot_AddList(TEAM_NONE, '');
+      2: g_Bot_AddList(TEAM_NONE, P[1], StrToIntDef(P[1], -1));
+    else
+      if P[2] = 'red' then
+        t := TEAM_RED
+      else if P[2] = 'blue' then
+        t := TEAM_BLUE
       else
-        g_Bot_AddList(IfThen(P[2] = 'red', TEAM_RED, TEAM_BLUE), P[1], StrToIntDef(P[1], -1));
+        t := TEAM_NONE;
+
+      if Length(P) = 3
+        then g_Bot_AddList(t, P[1], StrToIntDef(P[1], -1))
+        else g_Bot_AddList(t, P[1], StrToIntDef(P[1], -1), StrToIntDef(P[3], 100));
     end;
   end
   else if cmd = 'bot_removeall' then
@@ -6709,6 +7084,34 @@ begin
     end else
       g_Console_Add(_lc[I_MSG_GM_UNAVAIL]);
   end
+  else if (cmd = 'an') or (cmd = 'announce') then
+  begin
+    if g_Game_IsNet then
+    begin
+      if Length(P) > 1 then
+      begin
+        for a := 1 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('announce <text>');
+          Exit;
+        end;
+
+        chstr := 'centerprint 100 ' + b_Text_Format(chstr);
+        if g_Game_IsClient then
+          MC_SEND_RCONCommand(chstr)
+        else
+          g_Console_Process(chstr, True);
+      end
+      else
+        g_Console_Add('announce <text>');
+    end else
+      g_Console_Add(_lc[I_MSG_GM_UNAVAIL]);
+  end
   else if cmd = 'game' then
   begin
     if gGameSettings.GameType <> GT_NONE then
@@ -6721,7 +7124,7 @@ begin
       g_Console_Add(cmd + ' <WAD> [MAP] [# players]');
       Exit;
     end;
-    // game not started yet, load fist map from some wad
+    // game not started yet, load first map from some wad
     found := false;
     s := addWadExtension(P[1]);
     found := e_FindResource(AllMapDirs, s);
@@ -6744,7 +7147,8 @@ begin
         g_Game_Free();
         with gGameSettings do
         begin
-          GameMode := g_Game_TextToMode(gcGameMode);
+          Options := gsGameFlags;
+          GameMode := g_Game_TextToMode(gsGameMode);
           if gSwitchGameMode <> GM_NONE then
             GameMode := gSwitchGameMode;
           if GameMode = GM_NONE then GameMode := GM_DM;
@@ -6753,7 +7157,7 @@ begin
           if Length(P) >= 4 then
             b := StrToIntDef(P[3], 1);
           g_Game_StartCustom(s, GameMode, TimeLimit,
-                             GoalLimit, MaxLives, Options, b);
+                             ScoreLimit, MaxLives, Options, b);
         end;
       end
       else
@@ -6798,14 +7202,15 @@ begin
         g_Game_Free();
         with gGameSettings do
         begin
-          GameMode := g_Game_TextToMode(gcGameMode);
+          Options := gsGameFlags;
+          GameMode := g_Game_TextToMode(gsGameMode);
           if gSwitchGameMode <> GM_NONE then GameMode := gSwitchGameMode;
           if GameMode = GM_NONE then GameMode := GM_DM;
           if GameMode = GM_SINGLE then GameMode := GM_COOP;
           b := 0;
           if Length(P) >= 6 then
             b := StrToIntDef(P[5], 0);
-          g_Game_StartServer(s, GameMode, TimeLimit, GoalLimit, MaxLives, Options, b, listen, prt)
+          g_Game_StartServer(s, GameMode, TimeLimit, ScoreLimit, MaxLives, Options, b, listen, prt)
         end
       end
       else
@@ -6835,6 +7240,10 @@ begin
         g_Console_Add(_lc[I_MSG_GM_UNAVAIL])
       end
     end
+    else if not e_IsValidResourceName(P[1]) then
+    begin
+      g_Console_Add('wad name must not be absolute or relative');
+    end
     else
     begin
       if g_Game_IsServer and (gGameSettings.GameType <> GT_SINGLE) then
@@ -6962,6 +7371,10 @@ begin
           g_Console_Add(_lc[I_MSG_GM_UNAVAIL]);
         end;
       end
+      else if not e_IsValidResourceName(P[1]) then
+      begin
+        g_Console_Add('wad name must not be absolute or relative');
+      end
       else
       begin
         nm := False;
@@ -7151,25 +7564,72 @@ begin
   begin
     g_TakeScreenShot()
   end
+  else if (cmd = 'weapnext') or (cmd = 'weapprev') then
+  begin
+    a := 1 - (ord(cmd[5]) - ord('n'));
+    if a = -1 then
+      gWeaponAction[0, WP_PREV] := True;
+    if a = 1 then
+      gWeaponAction[0, WP_NEXT] := True;
+  end
   else if cmd = 'weapon' then
   begin
     if Length(p) = 2 then
     begin
-      a := WP_FIRST + StrToInt(p[1]) - 1;
+      a := WP_FIRST + StrToIntDef(p[1], 0) - 1;
       if (a >= WP_FIRST) and (a <= WP_LAST) then
         gSelectWeapon[0, a] := True
     end
   end
+  else if (cmd = 'p1_weapnext') or (cmd = 'p1_weapprev')
+       or (cmd = 'p2_weapnext') or (cmd = 'p2_weapprev') then
+  begin
+    a := 1 - (ord(cmd[8]) - ord('n'));
+    b := ord(cmd[2]) - ord('1');
+    if a = -1 then
+      gWeaponAction[b, WP_PREV] := True;
+    if a = 1 then
+      gWeaponAction[b, WP_NEXT] := True;
+  end
   else if (cmd = 'p1_weapon') or (cmd = 'p2_weapon') then
   begin
     if Length(p) = 2 then
     begin
-      a := WP_FIRST + StrToInt(p[1]) - 1;
+      a := WP_FIRST + StrToIntDef(p[1], 0) - 1;
       b := ord(cmd[2]) - ord('1');
       if (a >= WP_FIRST) and (a <= WP_LAST) then
         gSelectWeapon[b, a] := True
     end
   end
+  else if (cmd = 'p1_weapbest') or (cmd = 'p2_weapbest') then
+  begin
+    b := ord(cmd[2]) - ord('1');
+    if b = 0 then
+      gSelectWeapon[b, gPlayer1.GetMorePrefered()] := True
+    else
+      gSelectWeapon[b, gPlayer2.GetMorePrefered()] := True;
+  end
+  else if (cmd = 'dropflag') then
+  begin
+    if g_Game_IsServer then
+    begin
+      if gPlayer2 <> nil then gPlayer2.TryDropFlag();
+      if gPlayer1 <> nil then gPlayer1.TryDropFlag();
+    end
+    else
+      MC_SEND_CheatRequest(NET_CHEAT_DROPFLAG);
+  end
+  else if (cmd = 'p1_dropflag') or (cmd = 'p2_dropflag') then
+  begin
+    b := ord(cmd[2]) - ord('1');
+    if g_Game_IsServer then
+    begin
+      if (b = 1) and (gPlayer2 <> nil) then gPlayer2.TryDropFlag()
+      else if (b = 0) and (gPlayer1 <> nil) then gPlayer1.TryDropFlag();
+    end
+    else
+      MC_SEND_CheatRequest(NET_CHEAT_DROPFLAG);
+  end
 // Êîìàíäû Ñâîåé èãðû:
   else if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
   begin
@@ -7375,6 +7835,73 @@ begin
   end;
 end;
 
+procedure SystemCommands(P: SSArray);
+var
+  cmd: string;
+begin
+  cmd := LowerCase(P[0]);
+  case cmd of
+    'exit', 'quit':
+      begin
+        g_Game_Free();
+        g_Game_Quit();
+      end;
+    'r_reset':
+      begin
+        gRC_Width := Max(1, gRC_Width);
+        gRC_Height := Max(1, gRC_Height);
+        gBPP := Max(1, gBPP);
+        if sys_SetDisplayMode(gRC_Width, gRC_Height, gBPP, gRC_FullScreen, gRC_Maximized) = True then
+          e_LogWriteln('resolution changed')
+        else
+          e_LogWriteln('resolution not changed');
+        sys_EnableVSync(gVSync);
+      end;
+    'r_maxfps':
+      begin
+        if Length(p) = 2 then
+        begin
+          gMaxFPS := StrToIntDef(p[1], gMaxFPS);
+          if gMaxFPS > 0 then
+            gFrameTime := 1000 div gMaxFPS
+          else
+            gFrameTime := 0;
+        end;
+        e_LogWritefln('r_maxfps %d', [gMaxFPS]);
+      end;
+    'g_language':
+      begin
+        if Length(p) = 2 then
+        begin
+          gAskLanguage := true;
+          gLanguage := LANGUAGE_ENGLISH;
+          case LowerCase(p[1]) of
+            'english':
+               begin
+                 gAskLanguage := false;
+                 gLanguage := LANGUAGE_ENGLISH;
+               end;
+            'russian':
+               begin
+                 gAskLanguage := false;
+                 gLanguage := LANGUAGE_RUSSIAN;
+               end;
+            'ask':
+               begin
+                 gAskLanguage := true;
+                 gLanguage := LANGUAGE_ENGLISH;
+               end;
+          end;
+          g_Language_Set(gLanguage);
+        end
+        else
+        begin
+          e_LogWritefln('usage: %s <English|Russian|Ask>', [cmd]);
+        end
+      end;
+  end;
+end;
+
 procedure g_TakeScreenShot(Filename: string = '');
   var s: TStream; t: TDateTime; dir, date, name: String;
 begin
@@ -7388,11 +7915,11 @@ begin
       DateTimeToString(date, 'yyyy-mm-dd-hh-nn-ss', t);
       Filename := 'screenshot-' + date;
     end;
-    
+
     name := e_CatPath(dir, Filename + '.png');
     s := createDiskFile(name);
     try
-      e_MakeScreenshot(s, gScreenWidth, gScreenHeight);
+      e_MakeScreenshot(s, gWinSizeX, gWinSizeY);
       s.Free;
       g_Console_Add(Format(_lc[I_CONSOLE_SCREENSHOT], [name]))
     except
@@ -7729,8 +8256,7 @@ begin
   case gAnnouncer of
     ANNOUNCE_NONE:
       Exit;
-    ANNOUNCE_ME,
-    ANNOUNCE_MEPLUS:
+    ANNOUNCE_ME:
       if not g_Game_IsWatchedPlayer(SpawnerUID) then
         Exit;
   end;
@@ -7741,6 +8267,14 @@ begin
   hahasnd[Random(3)].Play();
 end;
 
+procedure g_Game_Effect_Bubbles (fX, fY: Integer; count: Word; devX, devY: Byte; Silent: Boolean);
+begin
+  g_GFX_Bubbles(fX, fY, count, devX, devY);
+  if not Silent then if Random(2) = 0
+    then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', fX, fY)
+    else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', fX, fY);
+end;
+
 procedure g_Game_StartVote(Command, Initiator: string);
 var
   Need: Integer;
@@ -7886,8 +8420,7 @@ begin
   end;
 
   g_ActiveWindow := nil;
-
-  ProcessLoading(true);
+  ProcessLoading(True);
 end;
 
 procedure g_Game_StepLoading(Value: Integer = -1);
@@ -7901,10 +8434,11 @@ begin
     end
     else
       CurValue := Value;
+
     if (ShowCount > LOADING_SHOW_STEP) or (Value > -1) then
     begin
       ShowCount := 0;
-      ProcessLoading();
+      ProcessLoading(False);
     end;
   end;
 end;
@@ -7938,9 +8472,9 @@ begin
   while i <= ParamCount do
   begin
     s := ParamStr(i);
-    if (s[1] = '-') and (Length(s) > 1) then
+    if (Length(s) > 1) and (s[1] = '-') then
     begin
-      if (s[2] = '-') and (Length(s) > 2) then
+      if (Length(s) > 2) and (s[2] = '-') then
         begin // Îäèíî÷íûé ïàðàìåòð
           SetLength(pars, Length(pars) + 1);
           with pars[High(pars)] do
@@ -7985,7 +8519,7 @@ var
   map: String;
   GMode, n: Byte;
   LimT, LimS: Integer;
-  Opt: LongWord;
+  Opt: TGameOptions;
   Lives: Integer;
   s: String;
   Port: Integer;
@@ -8048,7 +8582,7 @@ begin
     if LimT < 0 then
       LimT := 0;
 
-  // Goal limit:
+  // Score limit:
     s := Find_Param_Value(pars, '-lims');
     if (s = '') or (not TryStrToInt(s, LimS)) then
       LimS := 0;
@@ -8064,12 +8598,9 @@ begin
 
   // Options:
     s := Find_Param_Value(pars, '-opt');
-    if (s = '') then
-      Opt := GAME_OPTION_ALLOWEXIT or GAME_OPTION_BOTVSPLAYER or GAME_OPTION_BOTVSMONSTER
-    else
-      Opt := StrToIntDef(s, 0);
-    if Opt = 0 then
-      Opt := GAME_OPTION_ALLOWEXIT or GAME_OPTION_BOTVSPLAYER or GAME_OPTION_BOTVSMONSTER;
+    if (s = '')
+      then Opt := gsGameFlags
+      else Opt := TGameOptions(StrToIntDef(s, 0));
 
   // Close after map:
     s := Find_Param_Value(pars, '--close');
@@ -8111,10 +8642,9 @@ begin
 
   // Start:
     s := Find_Param_Value(pars, '-port');
-    if (s = '') or not TryStrToInt(s, Port) then
-      g_Game_StartCustom(map, GMode, LimT, LimS, Lives, Opt, n)
-    else
-      g_Game_StartServer(map, GMode, LimT, LimS, Lives, Opt, n, 0, Port);
+    if (s = '') or not TryStrToInt(s, Port)
+      then g_Game_StartCustom(map, GMode, LimT, LimS, Lives, Opt, n)
+      else g_Game_StartServer(map, GMode, LimT, LimS, Lives, Opt, n, 0, Port);
   end;
 
 // Execute script when game loads:
@@ -8182,6 +8712,7 @@ begin
   conRegVar('dbg_ignore_level_bounds', @g_dbg_ignore_bounds, 'ignore level bounds', '',  false);
 
   conRegVar('r_scale', @g_dbg_scale, 0.01, 100.0, 'render scale', '',  false);
+  conRegVar('r_resolution_scale', @r_pixel_scale, 0.01, 100.0, 'upscale factor', '', false);
 
   conRegVar('light_enabled', @gwin_k8_enable_light_experiments, 'enable/disable dynamic lighting', 'lighting');
   conRegVar('light_player_halo', @g_playerLight, 'enable/disable player halo', 'player light halo');
@@ -8191,4 +8722,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', @gShowScore, '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.