DEADSOFTWARE

Fix model override option not changing bots' models
[d2df-sdl.git] / src / game / g_player.pas
index db39cb84b0961e1260f840fb8a885e710648b1a8..a7afb5c9d17116a2ed8a5624ec881c62124056c7 100644 (file)
@@ -31,11 +31,14 @@ const
   KEY_UP         = 3;
   KEY_DOWN       = 4;
   KEY_FIRE       = 5;
-  KEY_NEXTWEAPON = 6;
-  KEY_PREVWEAPON = 7;
-  KEY_OPEN       = 8;
-  KEY_JUMP       = 9;
-  KEY_CHAT       = 10;
+  KEY_OPEN       = 6;
+  KEY_JUMP       = 7;
+  KEY_CHAT       = 8;
+
+  WP_PREV        = 0;
+  WP_NEXT        = 1;
+  WP_FACT        = WP_PREV;
+  WP_LACT        = WP_NEXT;
 
   R_ITEM_BACKPACK   = 0;
   R_KEY_RED         = 1;
@@ -114,6 +117,7 @@ type
     Kills: Word;
     Color: TRGB;
     Spectator: Boolean;
+    UID: Word;
   end;
 
   TPlayerStatArray = Array of TPlayerStat;
@@ -188,6 +192,7 @@ type
     FFireAngle: SmallInt;
     FIncCamOld:      Integer;
     FIncCam:         Integer;
+    FSlopeOld:       Integer;
     FShellTimer:     Integer;
     FShellType:      Byte;
     FSawSound:       TPlayableSound;
@@ -235,10 +240,12 @@ type
     function getNextWeaponIndex (): Byte; // return 255 for "no switch"
     procedure resetWeaponQueue ();
     function hasAmmoForWeapon (weapon: Byte): Boolean;
+    function hasAmmoForShooting (weapon: Byte): Boolean;
+    function shouldSwitch (weapon: Byte; hadWeapon: Boolean) : Boolean;
 
     procedure doDamage (v: Integer);
 
-    function followCorpse(): Boolean;
+    function refreshCorpse(): Boolean;
 
   public
     FDamageBuffer:   Integer;
@@ -252,6 +259,10 @@ type
     FReloading: Array [WP_FIRST..WP_LAST] of Word;
     FTime:      Array [T_RESPAWN..T_FLAGCAP] of DWORD;
     FKeys:      Array [KEY_LEFT..KEY_CHAT] of TKeyState;
+    FWeapSwitchMode: Byte;
+    FWeapPreferences: Array [WP_FIRST .. WP_LAST+1] of Byte;
+    FSwitchToEmpty: Byte;
+    FSkipFist: Byte;
     FColor:     TRGB;
     FPreferredTeam: Byte;
     FSpectator: Boolean;
@@ -271,6 +282,7 @@ type
     FSpawnInvul: Integer;
     FHandicap:  Integer;
     FWaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
+    FCorpse:    Integer;
 
     // debug: viewport offset
     viewPortX, viewPortY, viewPortW, viewPortH: Integer;
@@ -290,6 +302,11 @@ type
     function    IsKeyPressed(K: Byte): Boolean;
     function    GetKeys(): Byte;
     function    PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean; virtual;
+    procedure   SetWeaponPrefs(Prefs: Array of Byte);
+    procedure   SetWeaponPref(Weapon, Pref: Byte);
+    function    GetWeaponPref(Weapon: Byte) : Byte;
+    function    GetMorePrefered() : Byte;
+    function    MaySwitch(Weapon: Byte) : Boolean;
     function    Collide(X, Y: Integer; Width, Height: Word): Boolean; overload;
     function    Collide(Panel: TPanel): Boolean; overload;
     function    Collide(X, Y: Integer): Boolean; overload;
@@ -304,7 +321,8 @@ type
     procedure   BFGHit();
     function    GetFlag(Flag: Byte): Boolean;
     procedure   SetFlag(Flag: Byte);
-    function    DropFlag(Silent: Boolean = True): Boolean;
+    function    DropFlag(Silent: Boolean = True; DoThrow: Boolean = False): Boolean;
+    function    TryDropFlag(): Boolean;
     procedure   AllRulez(Health: Boolean);
     procedure   RestoreHealthArmor();
     procedure   FragCombo();
@@ -336,6 +354,7 @@ type
     procedure   NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
     procedure   DoLerp(Level: Integer = 2);
     procedure   SetLerp(XTo, YTo: Integer);
+    procedure   ProcessWeaponAction(Action: Byte);
     procedure   QueueWeaponSwitch(Weapon: Byte);
     procedure   RealizeCurrentWeapon();
     procedure   FlamerOn;
@@ -350,6 +369,8 @@ type
     procedure getMapBox (out x, y, w, h: Integer); inline;
     procedure moveBy (dx, dy: Integer); inline;
 
+    function getCameraObj(): TObj;
+
   public
     property    Vel: TPoint2i read FObj.Vel;
     property    Obj: TObj read FObj;
@@ -365,6 +386,9 @@ type
     property    Death: Integer read FDeath write FDeath;
     property    Kills: Integer read FKills write FKills;
     property    CurrWeap: Byte read FCurrWeap write FCurrWeap;
+    property    WeapSwitchMode: Byte read FWeapSwitchMode write FWeapSwitchMode;
+    property    SwitchToEmpty: Byte read FSwitchToEmpty write FSwitchToEmpty;
+    property    SkipFist: Byte read FSkipFist write FSkipFist;
     property    MonsterKills: Integer read FMonsterKills write FMonsterKills;
     property    Secrets: Integer read FSecrets;
     property    GodMode: Boolean read FGodMode write FGodMode;
@@ -382,6 +406,7 @@ type
     property    GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
     property    IncCam: Integer read FIncCam write FIncCam;
     property    IncCamOld: Integer read FIncCamOld write FIncCamOld;
+    property    SlopeOld: Integer read FSlopeOld write FSlopeOld;
     property    UID: Word read FUID write FUID;
     property    JustTeleported: Boolean read FJustTeleported write FJustTeleported;
     property    NetTime: LongWord read FNetTime write FNetTime;
@@ -528,7 +553,7 @@ type
   public
     constructor Create(X, Y: Integer; ModelName: String; aMess: Boolean);
     destructor  Destroy(); override;
-    procedure   Damage(Value: Word; vx, vy: Integer);
+    procedure   Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer);
     procedure   Update();
     procedure   Draw();
     procedure   SaveState (st: TStream);
@@ -548,7 +573,7 @@ type
 
   TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
     record
-      Goals: SmallInt;
+      Score: SmallInt;
     end;
 
 var
@@ -575,6 +600,10 @@ procedure g_Gibs_SetMax(Count: Word);
 function  g_Gibs_GetMax(): Word;
 procedure g_Corpses_SetMax(Count: Word);
 function  g_Corpses_GetMax(): Word;
+procedure g_Force_Model_Set(Mode: Word);
+function g_Force_Model_Get(): Word;
+procedure g_Forced_Model_SetName(Model: String);
+function  g_Forced_Model_GetName(): String;
 procedure g_Shells_SetMax(Count: Word);
 function  g_Shells_GetMax(): Word;
 
@@ -595,7 +624,7 @@ function  g_Player_Get(UID: Word): TPlayer;
 function  g_Player_GetCount(): Byte;
 function  g_Player_GetStats(): TPlayerStatArray;
 function  g_Player_ValidName(Name: String): Boolean;
-procedure g_Player_CreateCorpse(Player: TPlayer);
+function  g_Player_CreateCorpse(Player: TPlayer): Integer;
 procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: String; fColor: TRGB);
 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
 procedure g_Player_UpdatePhysicalObjects();
@@ -609,6 +638,7 @@ procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
 procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1; Handicap: Integer = 100);
 procedure g_Bot_MixNames();
 procedure g_Bot_RemoveAll();
+function g_Bot_GetCount(): Integer;
 
 implementation
 
@@ -703,6 +733,8 @@ var
   MaxGibs: Word = 150;
   MaxCorpses: Word = 20;
   MaxShells: Word = 300;
+  ForceModel: Word = 0;
+  ForcedModelName: String = STD_PLAYER_MODEL;
   CurrentGib: Integer = 0;
   CurrentShell: Integer = 0;
   BotNames: Array of String;
@@ -770,6 +802,26 @@ begin
   Result := MaxCorpses;
 end;
 
+procedure g_Force_Model_Set(Mode: Word);
+begin
+  ForceModel := Mode;
+end;
+
+function g_Force_Model_Get(): Word;
+begin
+  Result := ForceModel;
+end;
+
+procedure g_Forced_Model_SetName(Model: String);
+begin
+  ForcedModelName := Model;
+end;
+
+function g_Forced_Model_GetName(): String;
+begin
+  Result := ForcedModelName;
+end;
+
 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
 var
   a: Integer;
@@ -805,6 +857,8 @@ begin
 
   gPlayers[a].FActualModelName := ModelName;
   gPlayers[a].SetModel(ModelName);
+  if Bot and (g_Force_Model_Get() <> 0) then
+    gPlayers[a].SetModel(g_Forced_Model_GetName());
 
 // Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî:
   if gPlayers[a].FModel = nil then
@@ -844,148 +898,44 @@ begin
 end;
 
 function g_Player_CreateFromState (st: TStream): Word;
-var
-  a, i: Integer;
-  ok, Bot: Boolean;
-  b: Byte;
+  var a: Integer; ok, Bot: Boolean; pos: Int64;
 begin
-  result := 0;
-  if (st = nil) then exit; //???
+  assert(st <> nil);
 
-  // Ñèãíàòóðà èãðîêà
+  // check signature and entity type
+  pos := st.Position;
   if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
   if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
-
-  // Áîò èëè ÷åëîâåê:
   Bot := utils.readBool(st);
+  st.Position := pos;
 
+  // find free player slot
   ok := false;
-  a := 0;
-
-  // Åñòü ëè ìåñòî â gPlayers:
-  for a := 0 to High(gPlayers) do if (gPlayers[a] = nil) then begin ok := true; break; end;
+  for a := 0 to High(gPlayers) do
+    if gPlayers[a] = nil then
+    begin
+      ok := true;
+      break;
+    end;
 
-  // Íåò ìåñòà - ðàñøèðÿåì gPlayers
+  // allocate player slot
   if not ok then
   begin
     SetLength(gPlayers, Length(gPlayers)+1);
     a := High(gPlayers);
   end;
 
-  // Ñîçäàåì îáúåêò èãðîêà
+  // create entity and load state
   if Bot then
-    gPlayers[a] := TBot.Create()
-  else
-    gPlayers[a] := TPlayer.Create();
-  gPlayers[a].FIamBot := Bot;
-  gPlayers[a].FPhysics := True;
-
-  // UID èãðîêà
-  gPlayers[a].FUID := utils.readWord(st);
-  // Èìÿ èãðîêà
-  gPlayers[a].FName := utils.readStr(st);
-  // Êîìàíäà
-  gPlayers[a].FTeam := utils.readByte(st);
-  gPlayers[a].FPreferredTeam := gPlayers[a].FTeam;
-  // Æèâ ëè
-  gPlayers[a].FAlive := utils.readBool(st);
-  // Èçðàñõîäîâàë ëè âñå æèçíè
-  gPlayers[a].FNoRespawn := utils.readBool(st);
-  // Íàïðàâëåíèå
-  b := utils.readByte(st);
-  if b = 1 then gPlayers[a].FDirection := TDirection.D_LEFT else gPlayers[a].FDirection := TDirection.D_RIGHT; // b = 2
-  // Çäîðîâüå
-  gPlayers[a].FHealth := utils.readLongInt(st);
-  // Ôîðà
-  gPlayers[a].FHandicap := utils.readLongInt(st);
-  // Æèçíè
-  gPlayers[a].FLives := utils.readByte(st);
-  // Áðîíÿ
-  gPlayers[a].FArmor := utils.readLongInt(st);
-  // Çàïàñ âîçäóõà
-  gPlayers[a].FAir := utils.readLongInt(st);
-  // Çàïàñ ãîðþ÷åãî
-  gPlayers[a].FJetFuel := utils.readLongInt(st);
-  // Áîëü
-  gPlayers[a].FPain := utils.readLongInt(st);
-  // Óáèë
-  gPlayers[a].FKills := utils.readLongInt(st);
-  // Óáèë ìîíñòðîâ
-  gPlayers[a].FMonsterKills := utils.readLongInt(st);
-  // Ôðàãîâ
-  gPlayers[a].FFrags := utils.readLongInt(st);
-  // Ôðàãîâ ïîäðÿä
-  gPlayers[a].FFragCombo := utils.readByte(st);
-  // Âðåìÿ ïîñëåäíåãî ôðàãà
-  gPlayers[a].FLastFrag := utils.readLongWord(st);
-  // Ñìåðòåé
-  gPlayers[a].FDeath := utils.readLongInt(st);
-  // Êàêîé ôëàã íåñåò
-  gPlayers[a].FFlag := utils.readByte(st);
-  // Íàøåë ñåêðåòîâ
-  gPlayers[a].FSecrets := utils.readLongInt(st);
-  // Òåêóùåå îðóæèå
-  gPlayers[a].FCurrWeap := utils.readByte(st);
-  // Ñëåäóþùåå æåëàåìîå îðóæèå
-  gPlayers[a].FNextWeap := utils.readWord(st);
-  // ...è ïàóçà
-  gPlayers[a].FNextWeapDelay := utils.readByte(st);
-  // Âðåìÿ çàðÿäêè BFG
-  gPlayers[a].FBFGFireCounter := utils.readSmallInt(st);
-  // Áóôåð óðîíà
-  gPlayers[a].FDamageBuffer := utils.readLongInt(st);
-  // Ïîñëåäíèé óäàðèâøèé
-  gPlayers[a].FLastSpawnerUID := utils.readWord(st);
-  // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
-  gPlayers[a].FLastHit := utils.readByte(st);
-  // Îáúåêò èãðîêà:
-  Obj_LoadState(@gPlayers[a].FObj, st);
-  // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
-  for i := A_BULLETS to A_HIGH do gPlayers[a].FAmmo[i] := utils.readWord(st);
-  // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
-  for i := A_BULLETS to A_HIGH do gPlayers[a].FMaxAmmo[i] := utils.readWord(st);
-  // Íàëè÷èå îðóæèÿ
-  for i := WP_FIRST to WP_LAST do gPlayers[a].FWeapon[i] := utils.readBool(st);
-  // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
-  for i := WP_FIRST to WP_LAST do gPlayers[a].FReloading[i] := utils.readWord(st);
-  // Íàëè÷èå ðþêçàêà
-  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_ITEM_BACKPACK);
-  // Íàëè÷èå êðàñíîãî êëþ÷à
-  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_RED);
-  // Íàëè÷èå çåëåíîãî êëþ÷à
-  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_GREEN);
-  // Íàëè÷èå ñèíåãî êëþ÷à
-  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_BLUE);
-  // Íàëè÷èå áåðñåðêà
-  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_BERSERK);
-  // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
-  for i := MR_SUIT to MR_MAX do gPlayers[a].FMegaRulez[i] := utils.readLongWord(st);
-  // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
-  for i := T_RESPAWN to T_FLAGCAP do gPlayers[a].FTime[i] := utils.readLongWord(st);
-
-  // Íàçâàíèå ìîäåëè:
-  gPlayers[a].FActualModelName := utils.readStr(st);
-  // Öâåò ìîäåëè
-  gPlayers[a].FColor.R := utils.readByte(st);
-  gPlayers[a].FColor.G := utils.readByte(st);
-  gPlayers[a].FColor.B := utils.readByte(st);
-  // Îáíîâëÿåì ìîäåëü èãðîêà
-  gPlayers[a].SetModel(gPlayers[a].FActualModelName);
-
-  // Íåò ìîäåëè - ñîçäàíèå íåâîçìîæíî
-  if (gPlayers[a].FModel = nil) then
   begin
-    gPlayers[a].Free();
-    gPlayers[a] := nil;
-    g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [gPlayers[a].FActualModelName]));
-    exit;
-  end;
-
-  // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû
-  if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
-    gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
+    gPlayers[a] := TBot.Create();
+    if (g_Force_Model_Get() <> 0) then
+      gPlayers[a].SetModel(g_Forced_Model_GetName());
+  end
   else
-    gPlayers[a].FModel.Color := gPlayers[a].FColor;
+    gPlayers[a] := TPlayer.Create();
+  gPlayers[a].FPhysics := True; // ???
+  gPlayers[a].LoadState(st);
 
   result := gPlayers[a].FUID;
 end;
@@ -1027,6 +977,9 @@ var
 begin
   if not g_Game_IsServer then Exit;
 
+// Íå äîáàâëÿåì áîòîâ åñëè ëèìèò óæå äîñòèãíóò
+  if (g_Bot_GetCount() >= gMaxBots) then Exit;
+
 // Ñïèñîê íàçâàíèé ìîäåëåé:
   m := g_PlayerModel_GetNames();
   if m = nil then
@@ -1124,6 +1077,9 @@ var
 begin
   if not g_Game_IsServer then Exit;
 
+// Íå äîáàâëÿåì áîòîâ åñëè ëèìèò óæå äîñòèãíóò
+  if (g_Bot_GetCount() >= gMaxBots) then Exit;
+
 // Ñïèñîê íàçâàíèé ìîäåëåé:
   m := g_PlayerModel_GetNames();
   if m = nil then
@@ -1435,6 +1391,8 @@ begin
   e_TextureFontPrint(0, fH * 3, 'Vel Y: ' + IntToStr(p.FObj.Vel.Y), gStdFont);
   e_TextureFontPrint(0, fH * 4, 'Acc X: ' + IntToStr(p.FObj.Accel.X), gStdFont);
   e_TextureFontPrint(0, fH * 5, 'Acc Y: ' + IntToStr(p.FObj.Accel.Y), gStdFont);
+  e_TextureFontPrint(0, fH * 6, 'Old X: ' + IntToStr(p.FObj.oldX), gStdFont);
+  e_TextureFontPrint(0, fH * 7, 'Old Y: ' + IntToStr(p.FObj.oldY), gStdFont);
 end;
 
 procedure g_Player_DrawHealth();
@@ -1489,6 +1447,20 @@ begin
       Result := Result + 1;
 end;
 
+function g_Bot_GetCount(): Integer;
+var
+  a: Integer;
+begin
+  Result := 0;
+
+  if gPlayers = nil then
+    Exit;
+
+  for a := 0 to High(gPlayers) do
+    if (gPlayers[a] <> nil) and (gPlayers[a] is TBot) then
+      Result := Result + 1;
+end;
+
 function g_Player_GetStats(): TPlayerStatArray;
 var
   a: Integer;
@@ -1514,6 +1486,7 @@ begin
         Color := gPlayers[a].FModel.Color;
         Lives := gPlayers[a].FLives;
         Spectator := gPlayers[a].FSpectator;
+        UID := gPlayers[a].FUID;
       end;
     end;
 end;
@@ -1547,8 +1520,8 @@ procedure g_Player_ResetAll(Force, Silent: Boolean);
 var
   i: Integer;
 begin
-  gTeamStat[TEAM_RED].Goals := 0;
-  gTeamStat[TEAM_BLUE].Goals := 0;
+  gTeamStat[TEAM_RED].Score := 0;
+  gTeamStat[TEAM_BLUE].Score := 0;
 
   if gPlayers <> nil then
     for i := 0 to High(gPlayers) do
@@ -1568,21 +1541,24 @@ begin
       end;
 end;
 
-procedure g_Player_CreateCorpse(Player: TPlayer);
+function  g_Player_CreateCorpse(Player: TPlayer): Integer;
 var
   i: Integer;
   find_id: DWORD;
   ok: Boolean;
 begin
+  Result := -1;
+
   if Player.alive then
     Exit;
 
 // Ðàçðûâàåì ñâÿçü ñ ïðåæíèì òðóïîì:
-  if gCorpses <> nil then
-    for i := 0 to High(gCorpses) do
-      if gCorpses[i] <> nil then
-        if gCorpses[i].FPlayerUID = Player.FUID then
-          gCorpses[i].FPlayerUID := 0;
+  i := Player.FCorpse;
+  if (i >= 0) and (i < Length(gCorpses)) then
+  begin
+    if (gCorpses[i] <> nil) and (gCorpses[i].FPlayerUID = Player.FUID) then
+      gCorpses[i].FPlayerUID := 0;
+  end;
 
   if Player.FObj.Y >= gMapInfo.Height+128 then
     Exit;
@@ -1610,6 +1586,8 @@ begin
         gCorpses[find_id].FObj.Vel := FObj.Vel;
         gCorpses[find_id].FObj.Accel := FObj.Accel;
         gCorpses[find_id].FPlayerUID := FUID;
+
+        Result := find_id;
       end
     else
       g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX,
@@ -2065,11 +2043,71 @@ begin
     if FModel <> nil then FModel.Color := Color;
 end;
 
+
+
 function TPlayer.GetColor(): TRGB;
 begin
   result := FModel.Color;
 end;
 
+procedure TPlayer.SetWeaponPrefs(Prefs: Array of Byte);
+var
+  i: Integer;
+begin
+  for i := WP_FIRST to WP_LAST + 1 do
+    begin
+      if (Prefs[i] > WP_LAST + 1) then
+        FWeapPreferences[i] := 0
+      else
+        FWeapPreferences[i] := Prefs[i];
+    end;
+end;
+
+procedure TPlayer.SetWeaponPref(Weapon, Pref: Byte);
+begin
+  if (Weapon > WP_LAST + 1) then
+    exit
+  else if (Pref <= WP_LAST + 1) and (Weapon <= WP_LAST + 1) then
+    FWeapPreferences[Weapon] := Pref
+  else if (Weapon <= WP_LAST + 1) and (Pref > WP_LAST + 1) then
+    FWeapPreferences[Weapon] := 0;
+end;
+
+function TPlayer.GetWeaponPref(Weapon: Byte) : Byte;
+begin
+  if (Weapon > WP_LAST + 1) then
+    result := 0
+  else if (FWeapPreferences[Weapon] > WP_LAST + 1) then
+    result := 0
+  else
+    result := FWeapPreferences[Weapon];
+end;
+
+function TPlayer.GetMorePrefered() : Byte;
+var
+  testedWeap, i: Byte;
+begin
+  testedWeap := FCurrWeap;
+  for i := WP_FIRST to WP_LAST do
+    if FWeapon[i] and maySwitch(i) and (FWeapPreferences[i] > FWeapPreferences[testedWeap]) then
+      testedWeap := i;
+  if (R_BERSERK in FRulez) and (FWeapPreferences[WP_LAST + 1] > FWeapPreferences[testedWeap]) then
+    testedWeap := WEAPON_KASTET;
+  result := testedWeap;
+end;
+
+function TPlayer.maySwitch(Weapon: Byte) : Boolean;
+begin
+  result := true;
+  if (Weapon = WEAPON_KASTET) and (FSkipFist <> 0) then
+  begin
+    if (FSkipFist = 1) and (not (R_BERSERK in FRulez)) then
+      result := false;
+  end
+  else if (FSwitchToEmpty = 0) and (not hasAmmoForShooting(Weapon)) then
+    result := false;
+end;
+
 procedure TPlayer.SwitchTeam;
 begin
   if g_Game_IsClient then
@@ -2197,6 +2235,7 @@ begin
   FFirePainTime := 0;
   FFireAttacker := 0;
   FHandicap := 100;
+  FCorpse := -1;
 
   FActualModelName := 'doomer';
 
@@ -2292,8 +2331,13 @@ begin
         end;
 
       if t = HIT_WATER then
+      begin
         g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
                       FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
+        if Random(2) = 0
+          then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
+          else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
+      end;
     end;
 
   // Áóôåð óðîíà:
@@ -2362,7 +2406,7 @@ end;
 
 procedure TPlayer.DrawIndicator(Color: TRGB);
 var
-  indX, indY, fX, fY: Integer;
+  indX, indY, fX, fY, fSlope: Integer;
   indW, indH: Word;
   indA: Single;
   a: TDFPoint;
@@ -2373,6 +2417,8 @@ begin
   if FAlive then
   begin
     FObj.lerp(gLerpFactor, fX, fY);
+    fSlope := nlerp(FSlopeOld, FObj.slopeUpLeft, gLerpFactor);
+
     case gPlayerIndicatorStyle of
       0:
         begin
@@ -2410,6 +2456,7 @@ begin
               indY := fY - indH;
             end;
 
+            indY := indY + fSlope;
             indX := EnsureRange(indX, 0, Max(gMapInfo.Width, gPlayerScreenSize.X) - indW);
             indY := EnsureRange(indY, 0, Max(gMapInfo.Height, gPlayerScreenSize.Y) - indH);
 
@@ -2424,7 +2471,7 @@ begin
         begin
           e_TextureFontGetSize(gStdFont, nW, nH);
           indX := fX + FObj.Rect.X + (FObj.Rect.Width - Length(FName) * nW) div 2;
-          indY := fY - nH;
+          indY := fY - nH + fSlope;
           e_TextureFontPrintEx(indX, indY, FName, gStdFont, Color.R, Color.G, Color.B, 1.0, True);
         end;
     end;
@@ -2438,8 +2485,11 @@ var
   Rb, Gb, Bb,
   Rw, Gw, Bw: SmallInt;
   Dot: Byte;
+  CObj: TObj;
 begin
-  FObj.lerp(gLerpFactor, fX, fY);
+  CObj := getCameraObj();
+  CObj.lerp(gLerpFactor, fX, fY);
+  // NB: _F_Obj.Rect is used to keep the bubble higher; this is not a mistake
   bubX := fX+FObj.Rect.X + IfThen(FDirection = TDirection.D_LEFT, -4, 18);
   bubY := fY+FObj.Rect.Y - 18;
   Rb := 64;
@@ -2519,9 +2569,10 @@ var
   w, h: Word;
   dr: Boolean;
   Mirror: TMirrorType;
-  fX, fY: Integer;
+  fX, fY, fSlope: Integer;
 begin
   FObj.lerp(gLerpFactor, fX, fY);
+  fSlope := nlerp(FSlopeOld, FObj.slopeUpLeft, gLerpFactor);
 
   if FAlive then
   begin
@@ -2533,7 +2584,7 @@ begin
     if FPunchAnim <> nil then
     begin
       FPunchAnim.Draw(fX+IfThen(Direction = TDirection.D_LEFT, 15-FObj.Rect.X, FObj.Rect.X-15),
-                      fY+FObj.Rect.Y-11, Mirror);
+                      fY+fSlope+FObj.Rect.Y-11, Mirror);
       if FPunchAnim.played then
       begin
         FPunchAnim.Free;
@@ -2547,10 +2598,10 @@ begin
         e_GetTextureSize(ID, @w, @h);
         if FDirection = TDirection.D_LEFT then
           e_Draw(ID, fX+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)+4,
-                     fY+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+FObj.slopeUpLeft, 0, True, False)
+                     fY+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+fSlope, 0, True, False)
         else
           e_Draw(ID, fX+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)-2,
-                     fY+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+FObj.slopeUpLeft, 0, True, False);
+                     fY+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+fSlope, 0, True, False);
       end;
 
     if FMegaRulez[MR_INVIS] > gTime then
@@ -2563,15 +2614,15 @@ begin
         else
           dr := True;
         if dr then
-          FModel.Draw(fX, fY+FObj.slopeUpLeft, 200)
+          FModel.Draw(fX, fY+fSlope, 200)
         else
-          FModel.Draw(fX, fY+FObj.slopeUpLeft);
+          FModel.Draw(fX, fY+fSlope);
       end
       else
-        FModel.Draw(fX, fY+FObj.slopeUpLeft, 254);
+        FModel.Draw(fX, fY+fSlope, 254);
     end
     else
-      FModel.Draw(fX, fY+FObj.slopeUpLeft);
+      FModel.Draw(fX, fY+fSlope);
   end;
 
   if g_debug_Frames then
@@ -2724,7 +2775,7 @@ begin
   SY := gPlayerScreenSize.Y;
   Y := 0;
 
-  if gShowGoals and (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
+  if gShowScore and (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
   begin
     if gGameSettings.GameMode = GM_CTF then
       a := 32 + 8
@@ -2741,7 +2792,7 @@ begin
         e_Draw(ID, X-16-32, 240-72-4, 0, True, False);
     end;
 
-    s := IntToStr(gTeamStat[TEAM_RED].Goals);
+    s := IntToStr(gTeamStat[TEAM_RED].Score);
     e_CharFont_GetSize(gMenuFont, s, tw, th);
     e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-72-4, s, TEAMCOLOR[TEAM_RED]);
 
@@ -2756,7 +2807,7 @@ begin
         e_Draw(ID,  X-16-32, 240-32-4, 0, True, False);
     end;
 
-    s := IntToStr(gTeamStat[TEAM_BLUE].Goals);
+    s := IntToStr(gTeamStat[TEAM_BLUE].Score);
     e_CharFont_GetSize(gMenuFont, s, tw, th);
     e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-32-4, s, TEAMCOLOR[TEAM_BLUE]);
   end;
@@ -3462,10 +3513,15 @@ begin
 
   if SpawnerUID = FUID then
     begin // Ñàìîóáèëñÿ
-      if Srv and (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
+      if Srv then
       begin
-        Dec(FFrags);
-        FLastFrag := 0;
+        if gGameSettings.GameMode = GM_TDM then
+          Dec(gTeamStat[FTeam].Score);
+        if DoFrags or (gGameSettings.GameMode = GM_TDM) then
+        begin
+          Dec(FFrags);
+          FLastFrag := 0;
+        end;
       end;
       g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
     end
@@ -3487,7 +3543,7 @@ begin
             end;
 
           if (gGameSettings.GameMode = GM_TDM) and DoFrags then
-            Inc(gTeamStat[KP.Team].Goals,
+            Inc(gTeamStat[KP.Team].Score,
               IfThen(SameTeam(FUID, SpawnerUID), -1, 1));
 
           if netsrv then MH_SEND_PlayerStats(SpawnerUID);
@@ -3597,7 +3653,7 @@ begin
     DropFlag(KillType = K_FALLKILL);
   end;
 
-  g_Player_CreateCorpse(Self);
+  FCorpse := g_Player_CreateCorpse(Self);
 
   if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and
      (gLMSRespawn = LMS_RESPAWN_NONE) then
@@ -3648,7 +3704,7 @@ begin
         g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_RED])]), 144);
         if Netsrv then
           MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_RED);
-        Inc(gTeamStat[TEAM_RED].Goals);
+        Inc(gTeamStat[TEAM_RED].Score);
         gLMSRespawn := LMS_RESPAWN_FINAL;
         gLMSRespawnTime := gTime + 5000;
       end
@@ -3658,7 +3714,7 @@ begin
         g_Game_Message(Format(_lc[I_MESSAGE_TLMS_WIN], [AnsiUpperCase(_lc[I_GAME_TEAM_BLUE])]), 144);
         if Netsrv then
           MH_SEND_GameEvent(NET_EV_TLMS_WIN, TEAM_BLUE);
-        Inc(gTeamStat[TEAM_BLUE].Goals);
+        Inc(gTeamStat[TEAM_BLUE].Score);
         gLMSRespawn := LMS_RESPAWN_FINAL;
         gLMSRespawnTime := gTime + 5000;
       end
@@ -3750,6 +3806,15 @@ begin
               FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
 end;
 
+procedure TPlayer.ProcessWeaponAction(Action: Byte);
+begin
+  if g_Game_IsClient then Exit;
+  case Action of
+    WP_PREV: PrevWeapon();
+    WP_NEXT: NextWeapon();
+  end;
+end;
+
 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
 begin
   if g_Game_IsClient then Exit;
@@ -3777,6 +3842,36 @@ begin
   end;
 end;
 
+function TPlayer.hasAmmoForShooting (weapon: Byte): Boolean;
+begin
+  result := false;
+  case weapon of
+    WEAPON_KASTET, WEAPON_SAW: result := true;
+    WEAPON_SHOTGUN1, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
+    WEAPON_SHOTGUN2: result := (FAmmo[A_SHELLS] > 1);
+    WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
+    WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
+    WEAPON_PLASMA: result := (FAmmo[A_CELLS] > 0);
+    WEAPON_BFG: result := (FAmmo[A_CELLS] >= 40);
+    WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
+    else result := (weapon < length(FWeapon));
+  end;
+end;
+
+function TPlayer.shouldSwitch (weapon: Byte; hadWeapon: Boolean): Boolean;
+begin
+  result := false;
+  if (weapon > WP_LAST + 1) then
+    begin
+      result := false;
+      exit;
+    end;
+  if (FWeapSwitchMode = 1) and not hadWeapon then
+    result := true
+  else if (FWeapSwitchMode = 2) then
+    result := (FWeapPreferences[weapon] > FWeapPreferences[FCurrWeap]);
+end;
+
 // return 255 for "no switch"
 function TPlayer.getNextWeaponIndex (): Byte;
 var
@@ -3786,6 +3881,7 @@ var
   dir, cwi: Integer;
 begin
   result := 255; // default result: "no switch"
+  //e_LogWriteFln('FSWITCHTOEMPTY: %s', [FSwitchToEmpty], TMsgType.Notify);
   // had weapon cycling on previous frame? remove that flag
   if (FNextWeap and $2000) <> 0 then
   begin
@@ -3806,9 +3902,9 @@ begin
     for i := 0 to High(FWeapon) do
     begin
       cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
-      if FWeapon[cwi] then
+      if FWeapon[cwi] and maySwitch(cwi)  then
       begin
-        //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING);
+        //e_LogWriteFln(' SWITCH: cur=%d; new=%d %s %s', [FCurrWeap, cwi, FSwitchToEmpty, hasAmmoForWeapon(cwi)], TMsgType.Notify);
         result := Byte(cwi);
         FNextWeapDelay := WEAPON_DELAY;
         exit;
@@ -3826,6 +3922,7 @@ begin
       wantThisWeapon[i] := true;
       Inc(wwc);
     end;
+
   // exclude currently selected weapon from the set
   wantThisWeapon[FCurrWeap] := false;
   // slow down alterations a little
@@ -3856,6 +3953,7 @@ begin
       result := Byte(i);
       resetWeaponQueue();
       FNextWeapDelay := WEAPON_DELAY * 2; // anyway, 'cause why not
+      //e_LogWriteFln('FOUND %s %s %s', [result, FSwitchToEmpty, hasAmmoForWeapon(i)], TMsgType.Notify);
       exit;
     end;
   end;
@@ -3894,6 +3992,7 @@ begin
   end;
 
   nw := getNextWeaponIndex();
+  //
   if nw = 255 then exit; // don't reset anything here
   if nw > High(FWeapon) then
   begin
@@ -3937,18 +4036,10 @@ begin
 end;
 
 function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean;
-
-  function allowBerserkSwitching (): Boolean;
-  begin
-    if (FBFGFireCounter <> -1) then begin result := false; exit; end;
-    result := true;
-    if gBerserkAutoswitch then exit;
-    if not conIsCheatsEnabled then exit;
-    result := false;
-  end;
-
 var
   a: Boolean;
+  switchWeapon: Byte = 255;
+  hadWeapon: Boolean = False;
 begin
   Result := False;
   if g_Game_IsClient then Exit;
@@ -3956,7 +4047,6 @@ begin
   // a = true - ìåñòî ñïàâíà ïðåäìåòà:
   a := LongBool(gGameSettings.Options and GAME_OPTION_WEAPONSTAY) and arespawn;
   remove := not a;
-
   case ItemType of
     ITEM_MEDKIT_SMALL:
       if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
@@ -4022,6 +4112,8 @@ begin
     ITEM_WEAPON_SAW:
       if (not FWeapon[WEAPON_SAW]) or ((not arespawn) and (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) then
       begin
+        hadWeapon := FWeapon[WEAPON_SAW];
+        switchWeapon := WEAPON_SAW;
         FWeapon[WEAPON_SAW] := True;
         Result := True;
         if gFlash = 2 then Inc(FPickup, 5);
@@ -4033,7 +4125,8 @@ begin
       begin
         // Íóæíî, ÷òîáû íå âçÿòü âñå ïóëè ñðàçó:
         if a and FWeapon[WEAPON_SHOTGUN1] then Exit;
-
+        hadWeapon := FWeapon[WEAPON_SHOTGUN1];
+        switchWeapon := WEAPON_SHOTGUN1;
         IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
         FWeapon[WEAPON_SHOTGUN1] := True;
         Result := True;
@@ -4045,7 +4138,8 @@ begin
       if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN2] then
       begin
         if a and FWeapon[WEAPON_SHOTGUN2] then Exit;
-
+        hadWeapon := FWeapon[WEAPON_SHOTGUN2];
+        switchWeapon := WEAPON_SHOTGUN2;
         IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
         FWeapon[WEAPON_SHOTGUN2] := True;
         Result := True;
@@ -4057,7 +4151,8 @@ begin
       if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or not FWeapon[WEAPON_CHAINGUN] then
       begin
         if a and FWeapon[WEAPON_CHAINGUN] then Exit;
-
+        hadWeapon := FWeapon[WEAPON_CHAINGUN];
+        switchWeapon := WEAPON_CHAINGUN;
         IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
         FWeapon[WEAPON_CHAINGUN] := True;
         Result := True;
@@ -4069,7 +4164,8 @@ begin
       if (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or not FWeapon[WEAPON_ROCKETLAUNCHER] then
       begin
         if a and FWeapon[WEAPON_ROCKETLAUNCHER] then Exit;
-
+        switchWeapon := WEAPON_ROCKETLAUNCHER;
+        hadWeapon := FWeapon[WEAPON_ROCKETLAUNCHER];
         IncMax(FAmmo[A_ROCKETS], 2, FMaxAmmo[A_ROCKETS]);
         FWeapon[WEAPON_ROCKETLAUNCHER] := True;
         Result := True;
@@ -4081,7 +4177,8 @@ begin
       if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_PLASMA] then
       begin
         if a and FWeapon[WEAPON_PLASMA] then Exit;
-
+        switchWeapon := WEAPON_PLASMA;
+        hadWeapon := FWeapon[WEAPON_PLASMA];
         IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
         FWeapon[WEAPON_PLASMA] := True;
         Result := True;
@@ -4093,7 +4190,8 @@ begin
       if (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or not FWeapon[WEAPON_BFG] then
       begin
         if a and FWeapon[WEAPON_BFG] then Exit;
-
+        switchWeapon := WEAPON_BFG;
+        hadWeapon := FWeapon[WEAPON_BFG];
         IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
         FWeapon[WEAPON_BFG] := True;
         Result := True;
@@ -4105,7 +4203,8 @@ begin
       if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SUPERPULEMET] then
       begin
         if a and FWeapon[WEAPON_SUPERPULEMET] then Exit;
-
+        switchWeapon := WEAPON_SUPERPULEMET;
+        hadWeapon := FWeapon[WEAPON_SUPERPULEMET];
         IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
         FWeapon[WEAPON_SUPERPULEMET] := True;
         Result := True;
@@ -4117,7 +4216,8 @@ begin
       if (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) or not FWeapon[WEAPON_FLAMETHROWER] then
       begin
         if a and FWeapon[WEAPON_FLAMETHROWER] then Exit;
-
+        switchWeapon := WEAPON_FLAMETHROWER;
+        hadWeapon := FWeapon[WEAPON_FLAMETHROWER];
         IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
         FWeapon[WEAPON_FLAMETHROWER] := True;
         Result := True;
@@ -4291,7 +4391,7 @@ begin
         if not (R_BERSERK in FRulez) then
         begin
           Include(FRulez, R_BERSERK);
-          if allowBerserkSwitching then
+          if (FBFGFireCounter = -1) then
           begin
             FCurrWeap := WEAPON_KASTET;
             resetWeaponQueue();
@@ -4364,6 +4464,9 @@ begin
         if gFlash = 2 then Inc(FPickup, 5);
       end;
   end;
+
+  if (shouldSwitch(switchWeapon, hadWeapon)) then
+    QueueWeaponSwitch(switchWeapon);
 end;
 
 procedure TPlayer.Touch();
@@ -4408,6 +4511,7 @@ begin
   FDeath := 0;
   FSecrets := 0;
   FSpawnInvul := 0;
+  FCorpse := -1;
   FReady := False;
   if FNoRespawn then
   begin
@@ -4427,6 +4531,7 @@ begin
   ReleaseKeys();
 
   FDamageBuffer := 0;
+  FSlopeOld := 0;
   FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
@@ -4450,143 +4555,58 @@ begin
   // Îäèíî÷íàÿ èãðà/êîîïåðàòèâ
   if gGameSettings.GameMode in [GM_COOP, GM_SINGLE] then
   begin
-    if (Self = gPlayer1) or (Self = gPlayer2) then
+    if Self = gPlayer1 then
     begin
-      // Òî÷êà ïîÿâëåíèÿ ñâîåãî èãðîêà
-      if Self = gPlayer1 then
-        c := RESPAWNPOINT_PLAYER1
-      else
-        c := RESPAWNPOINT_PLAYER2;
-      if g_Map_GetPointCount(c) > 0 then
-      begin
-        Result := c;
-        Exit;
-      end;
-
-      // Òî÷êà ïîÿâëåíèÿ äðóãîãî èãðîêà
-      if Self = gPlayer1 then
-        c := RESPAWNPOINT_PLAYER2
-      else
-        c := RESPAWNPOINT_PLAYER1;
-      if g_Map_GetPointCount(c) > 0 then
-      begin
-        Result := c;
-        Exit;
-      end;
-    end else
+      // player 1 should try to spawn on the player 1 point
+      if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then
+        Exit(RESPAWNPOINT_PLAYER1)
+      else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then
+        Exit(RESPAWNPOINT_PLAYER2);
+    end
+    else if Self = gPlayer2 then
     begin
-      // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà (áîòà)
-      if Random(2) = 0 then
-        c := RESPAWNPOINT_PLAYER1
-      else
-        c := RESPAWNPOINT_PLAYER2;
-      if g_Map_GetPointCount(c) > 0 then
-      begin
-        Result := c;
-        Exit;
-      end;
-    end;
-
-    // Òî÷êà ëþáîé èç êîìàíä
-    if Random(2) = 0 then
-      c := RESPAWNPOINT_RED
+      // player 2 should try to spawn on the player 2 point
+      if g_Map_GetPointCount(RESPAWNPOINT_PLAYER2) > 0 then
+        Exit(RESPAWNPOINT_PLAYER2)
+      else if g_Map_GetPointCount(RESPAWNPOINT_PLAYER1) > 0 then
+        Exit(RESPAWNPOINT_PLAYER1);
+    end
     else
-      c := RESPAWNPOINT_BLUE;
-    if g_Map_GetPointCount(c) > 0 then
-    begin
-      Result := c;
-      Exit;
-    end;
-
-    // Òî÷êà DM
-    c := RESPAWNPOINT_DM;
-    if g_Map_GetPointCount(c) > 0 then
     begin
-      Result := c;
-      Exit;
+      // other players randomly pick either the first or the second point
+      c := IfThen((Random(2) = 0), RESPAWNPOINT_PLAYER1, RESPAWNPOINT_PLAYER2);
+      if g_Map_GetPointCount(c) > 0 then
+        Exit(c);
+        // try the other one
+      c := IfThen((c = RESPAWNPOINT_PLAYER1), RESPAWNPOINT_PLAYER2, RESPAWNPOINT_PLAYER1);
+      if g_Map_GetPointCount(c) > 0 then
+        Exit(c);
     end;
   end;
 
   // Ìÿñîïîâàë
   if gGameSettings.GameMode = GM_DM then
   begin
-    // Òî÷êà DM
-    c := RESPAWNPOINT_DM;
-    if g_Map_GetPointCount(c) > 0 then
-    begin
-      Result := c;
-      Exit;
-    end;
-
-    // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà
-    if Random(2) = 0 then
-      c := RESPAWNPOINT_PLAYER1
-    else
-      c := RESPAWNPOINT_PLAYER2;
-    if g_Map_GetPointCount(c) > 0 then
-    begin
-      Result := c;
-      Exit;
-    end;
-
-    // Òî÷êà ëþáîé èç êîìàíä
-    if Random(2) = 0 then
-      c := RESPAWNPOINT_RED
-    else
-      c := RESPAWNPOINT_BLUE;
-    if g_Map_GetPointCount(c) > 0 then
-    begin
-      Result := c;
-      Exit;
-    end;
+    // try DM points first
+    if g_Map_GetPointCount(RESPAWNPOINT_DM) > 0 then
+      Exit(RESPAWNPOINT_DM);
   end;
 
   // Êîìàíäíûå
   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
   begin
-    // Òî÷êà ñâîåé êîìàíäû
-    c := RESPAWNPOINT_DM;
-    if FTeam = TEAM_RED then
-      c := RESPAWNPOINT_RED;
-    if FTeam = TEAM_BLUE then
-      c := RESPAWNPOINT_BLUE;
-    if g_Map_GetPointCount(c) > 0 then
-    begin
-      Result := c;
-      Exit;
-    end;
-
-    // Òî÷êà DM
-    c := RESPAWNPOINT_DM;
-    if g_Map_GetPointCount(c) > 0 then
-    begin
-      Result := c;
-      Exit;
-    end;
-
-    // Òî÷êà ïîÿâëåíèÿ ëþáîãî èãðîêà
-    if Random(2) = 0 then
-      c := RESPAWNPOINT_PLAYER1
-    else
-      c := RESPAWNPOINT_PLAYER2;
-    if g_Map_GetPointCount(c) > 0 then
-    begin
-      Result := c;
-      Exit;
-    end;
-
-    // Òî÷êà äðóãîé êîìàíäû
+    // try team points first
     c := RESPAWNPOINT_DM;
     if FTeam = TEAM_RED then
+      c := RESPAWNPOINT_RED
+    else if FTeam = TEAM_BLUE then
       c := RESPAWNPOINT_BLUE;
-    if FTeam = TEAM_BLUE then
-      c := RESPAWNPOINT_RED;
     if g_Map_GetPointCount(c) > 0 then
-    begin
-      Result := c;
-      Exit;
-    end;
+      Exit(c);
   end;
+
+  // still haven't found a spawnpoint, try random shit
+  Result := g_Map_GetRandomPointType();
 end;
 
 procedure TPlayer.Respawn(Silent: Boolean; Force: Boolean = False);
@@ -4596,6 +4616,7 @@ var
   Anim: TAnimation;
   ID: DWORD;
 begin
+  FSlopeOld := 0;
   FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
@@ -4603,6 +4624,7 @@ begin
   FPain := 0;
   FLastHit := 0;
   FSpawnInvul := 0;
+  FCorpse := -1;
 
   if not g_Game_IsServer then
     Exit;
@@ -4785,6 +4807,7 @@ begin
   FPhysics := False;
   FWantsInGame := False;
   FSpawned := False;
+  FCorpse := -1;
 
   if FNoRespawn then
   begin
@@ -5017,11 +5040,12 @@ begin
     Result := 1;
 end;
 
-function TPlayer.followCorpse(): Boolean;
+function TPlayer.refreshCorpse(): Boolean;
 var
   i: Integer;
 begin
   Result := False;
+  FCorpse := -1;
   if FAlive or FSpectator then
     Exit;
   if (gCorpses = nil) or (Length(gCorpses) = 0) then
@@ -5031,18 +5055,29 @@ begin
       if gCorpses[i].FPlayerUID = FUID then
       begin
         Result := True;
-        FObj.X := gCorpses[i].FObj.X;
-        FObj.Y := gCorpses[i].FObj.Y;
-        FObj.Vel.X := gCorpses[i].FObj.Vel.X;
-        FObj.Vel.Y := gCorpses[i].FObj.Vel.Y;
-        FObj.Accel.X := gCorpses[i].FObj.Accel.X;
-        FObj.Accel.Y := gCorpses[i].FObj.Accel.Y;
+        FCorpse := i;
         break;
       end;
 end;
 
+function TPlayer.getCameraObj(): TObj;
+begin
+  if (not FAlive) and (not FSpectator) and
+     (FCorpse >= 0) and (FCorpse < Length(gCorpses)) and
+     (gCorpses[FCorpse] <> nil) and (gCorpses[FCorpse].FPlayerUID = FUID) then
+  begin
+    gCorpses[FCorpse].FObj.slopeUpLeft := FObj.slopeUpLeft;
+    Result := gCorpses[FCorpse].FObj;
+  end
+  else
+  begin
+    Result := FObj;
+  end;
+end;
+
 procedure TPlayer.PreUpdate();
 begin
+  FSlopeOld := FObj.slopeUpLeft;
   FIncCamOld := FIncCam;
   FObj.oldX := FObj.X;
   FObj.oldY := FObj.Y;
@@ -5060,11 +5095,14 @@ begin
   NetServer := g_Game_IsNet and g_Game_IsServer;
   AnyServer := g_Game_IsServer;
 
-  if FGhost then
-    DoLerp(4);
+  if g_Game_IsClient and (NetInterpLevel > 0) then
+    DoLerp(NetInterpLevel + 1)
+  else
+    if FGhost then
+      DoLerp(4);
 
   if NetServer then
-    if FClientID >= 0 then
+    if (FClientID >= 0) and (NetClients[FClientID].Peer <> nil) then
     begin
       FPing := NetClients[FClientID].Peer^.lastRoundTripTime;
       if NetClients[FClientID].Peer^.packetsSent > 0 then
@@ -5105,10 +5143,6 @@ begin
     FIncCam := FIncCam*i;
   end;
 
-  // no need to do that each second frame, weapon queue will take care of it
-  if FAlive and FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
-  if FAlive and FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
-
   if gTime mod (GAME_TICK*2) <> 0 then
   begin
     if (FObj.Vel.X = 0) and FAlive then
@@ -5121,8 +5155,7 @@ begin
 
     if FPhysics then
     begin
-      if not followCorpse() then
-        g_Obj_Move(@FObj, True, True, True);
+      g_Obj_Move(@FObj, True, True, True);
       positionChanged(); // this updates spatial accelerators
     end;
 
@@ -5136,8 +5169,6 @@ begin
     // Let alive player do some actions
     if FKeys[KEY_LEFT].Pressed then Run(TDirection.D_LEFT);
     if FKeys[KEY_RIGHT].Pressed then Run(TDirection.D_RIGHT);
-    //if FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
-    //if FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
     if FKeys[KEY_FIRE].Pressed and AnyServer then Fire()
     else
     begin
@@ -5256,8 +5287,7 @@ begin
 
   if FPhysics then
   begin
-    if not followCorpse() then
-      g_Obj_Move(@FObj, True, True, True);
+    g_Obj_Move(@FObj, True, True, True);
     positionChanged(); // this updates spatial accelerators
   end
   else
@@ -5380,10 +5410,9 @@ begin
       else if (FAir mod 31 = 0) and not blockmon then
       begin
         g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2), FObj.Y+PLAYER_RECT.Y-4, 5+Random(6), 8, 4);
-        if Random(2) = 0 then
-          g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
-        else
-          g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
+        if Random(2) = 0
+          then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
+          else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
       end;
     end else if FAir < AIR_DEF then
       FAir := AIR_DEF;
@@ -5591,10 +5620,9 @@ end;
 procedure TPlayer.NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
 var
   locObj: TObj;
-  F: Boolean;
+  visible: Boolean = True;
   WX, WY, XD, YD: Integer;
 begin
-  F := False;
   WX := X;
   WY := Y;
   XD := AX;
@@ -5603,6 +5631,7 @@ begin
   case FCurrWeap of
     WEAPON_KASTET:
     begin
+      visible := False;
       DoPunch();
       if R_BERSERK in FRulez then
       begin
@@ -5644,14 +5673,12 @@ begin
         FSawSoundSelect.Stop();
         FSawSound.PlayAt(FObj.X, FObj.Y);
       end;
-      f := True;
     end;
 
     WEAPON_PISTOL:
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX, Gamey);
       FFireAngle := FAngle;
-      f := True;
       g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
                              GameVelX, GameVelY-2, SHELL_BULLET);
     end;
@@ -5660,7 +5687,6 @@ begin
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
       FFireAngle := FAngle;
-      f := True;
       FShellTimer := 10;
       FShellType := SHELL_SHELL;
     end;
@@ -5669,7 +5695,6 @@ begin
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex, Gamey);
       FFireAngle := FAngle;
-      f := True;
       FShellTimer := 13;
       FShellType := SHELL_DBLSHELL;
     end;
@@ -5678,7 +5703,6 @@ begin
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex, Gamey);
       FFireAngle := FAngle;
-      f := True;
       g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
                              GameVelX, GameVelY-2, SHELL_BULLET);
     end;
@@ -5687,28 +5711,24 @@ begin
     begin
       g_Weapon_Rocket(wx, wy, xd, yd, FUID, WID);
       FFireAngle := FAngle;
-      f := True;
     end;
 
     WEAPON_PLASMA:
     begin
       g_Weapon_Plasma(wx, wy, xd, yd, FUID, WID);
       FFireAngle := FAngle;
-      f := True;
     end;
 
     WEAPON_BFG:
     begin
       g_Weapon_BFGShot(wx, wy, xd, yd, FUID, WID);
       FFireAngle := FAngle;
-      f := True;
     end;
 
     WEAPON_SUPERPULEMET:
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
       FFireAngle := FAngle;
-      f := True;
       g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
                              GameVelX, GameVelY-2, SHELL_SHELL);
     end;
@@ -5718,11 +5738,10 @@ begin
       g_Weapon_flame(wx, wy, xd, yd, FUID, WID);
       FlamerOn;
       FFireAngle := FAngle;
-      f := True;
     end;
   end;
 
-  if not f then Exit;
+  if not visible then Exit;
 
   if (FAngle = 0) or (FAngle = 180) then SetAction(A_ATTACK)
     else if (FAngle = ANGLE_LEFTDOWN) or (FAngle = ANGLE_RIGHTDOWN) then SetAction(A_ATTACKDOWN)
@@ -5739,16 +5758,20 @@ procedure TPlayer.SetLerp(XTo, YTo: Integer);
 var
   AX, AY: Integer;
 begin
-  if NetInterpLevel < 1 then
+  FXTo := XTo;
+  FYTo := YTo;
+  if FJustTeleported or (NetInterpLevel < 1) then
   begin
     FObj.X := XTo;
     FObj.Y := YTo;
+    if FJustTeleported then
+    begin
+      FObj.oldX := FObj.X;
+      FObj.oldY := FObj.Y;
+    end;
   end
   else
   begin
-    FXTo := XTo;
-    FYTo := YTo;
-
     AX := Abs(FXTo - FObj.X);
     AY := Abs(FYTo - FObj.Y);
     if (AX > 32) or (AX <= NetInterpLevel) then
@@ -5811,7 +5834,7 @@ begin
     if not sound_cap_flag[a].IsPlaying() then
       sound_cap_flag[a].Play();
 
-    gTeamStat[FTeam].Goals := gTeamStat[FTeam].Goals + 1;
+    gTeamStat[FTeam].Score += 1;
 
     Result := True;
     if g_Game_IsNet then
@@ -5905,10 +5928,19 @@ begin
     FModel.SetFlag(FFlag);
 end;
 
-function TPlayer.DropFlag(Silent: Boolean = True): Boolean;
+function TPlayer.TryDropFlag(): Boolean;
+begin
+  if LongBool(gGameSettings.Options and GAME_OPTION_ALLOWDROPFLAG) then
+    Result := DropFlag(False, LongBool(gGameSettings.Options and GAME_OPTION_THROWFLAG))
+  else
+    Result := False;
+end;
+
+function TPlayer.DropFlag(Silent: Boolean = True; DoThrow: Boolean = False): Boolean;
 var
   s: String;
   a: Byte;
+  xv, yv: Integer;
 begin
   Result := False;
   if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then
@@ -5921,8 +5953,18 @@ begin
     Direction := FDirection;
     State := FLAG_STATE_DROPPED;
     Count := FLAG_TIME;
-    g_Obj_Push(@Obj, (FObj.Vel.X div 2)-2+Random(5),
-                     (FObj.Vel.Y div 2)-2+Random(5));
+    if DoThrow then
+    begin
+      xv := FObj.Vel.X + IfThen(Direction = TDirection.D_RIGHT, 10, -10);
+      yv := FObj.Vel.Y - 2;
+    end
+    else
+    begin
+      xv := (FObj.Vel.X div 2);
+      yv := (FObj.Vel.Y div 2) - 2;
+    end;
+    g_Obj_Push(@Obj, xv, yv);
+
     positionChanged(); // this updates spatial accelerators
 
     if FFlag = FLAG_RED then
@@ -6471,10 +6513,9 @@ begin
   begin
     g_GFX_Bubbles(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)+Random(3)-1,
                   Obj.Y+Obj.Rect.Height+8, 1, 8, 4);
-    if Random(2) = 0 then
-      g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
-    else
-      g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
+    if Random(2) = 0
+      then g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
+      else g_Sound_PlayExAt('SOUND_GAME_BUBBLE2', FObj.X, FObj.Y);
     Exit;
   end;
 
@@ -6580,7 +6621,7 @@ begin
 end;
 
 
-procedure TCorpse.Damage(Value: Word; vx, vy: Integer);
+procedure TCorpse.Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer);
 var
   pm: TPlayerModel;
   Blood: TModelBlood;
@@ -6611,7 +6652,7 @@ begin
         if (gBodyKillEvent <> -1)
         and gDelayedEvents[gBodyKillEvent].Pending then
           gDelayedEvents[gBodyKillEvent].Pending := False;
-        gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, 0);
+        gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, SpawnerUID);
       end;
     end
   else
@@ -8039,7 +8080,6 @@ end;
 
 
 begin
-  conRegVar('cheat_berserk_autoswitch', @gBerserkAutoswitch, 'autoswitch to fist when berserk pack taken', '',  true, true);
   conRegVar('player_indicator', @gPlayerIndicator, 'Draw indicator only for current player, also for teammates, or not at all', 'Draw indicator only for current player, also for teammates, or not at all');
   conRegVar('player_indicator_style', @gPlayerIndicatorStyle, 'Visual appearance of indicator', 'Visual appearance of indicator');
 end.