DEADSOFTWARE

Allow not to specify a name in the bot preset to choose it from the general list
[d2df-sdl.git] / src / game / g_player.pas
index 068c2b4124e41fe4d225a6dd4685666797c1300c..0990c0e5ce43a17ff51a0ff1ff2c6cfe96bdb366 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;
@@ -236,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;
@@ -253,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;
@@ -272,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;
@@ -291,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;
@@ -305,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();
@@ -337,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;
@@ -351,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;
@@ -366,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;
@@ -530,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);
@@ -550,7 +573,7 @@ type
 
   TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
     record
-      Goals: SmallInt;
+      Score: SmallInt;
     end;
 
 var
@@ -577,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;
 
@@ -596,8 +623,8 @@ procedure g_Player_ResetAll(Force, Silent: Boolean);
 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_ExistingName(Name: String): Boolean;
+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();
@@ -611,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
 
@@ -705,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;
@@ -772,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;
@@ -807,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
@@ -846,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;
@@ -1029,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
@@ -1073,7 +1024,7 @@ begin
   _name := '';
   if BotNames <> nil then
     for a := 0 to High(BotNames) do
-      if g_Player_ValidName(BotNames[a]) then
+      if g_Player_ExistingName(BotNames[a]) then
       begin
         _name := BotNames[a];
         Break;
@@ -1126,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
@@ -1142,26 +1096,33 @@ begin
         Team := BotList[num].team; // CTF / TDM
 
 // Âûáèðàåì íàñòðîéêè áîòà èç ñïèñêà ïî íîìåðó èëè èìåíè:
-  lName := AnsiLowerCase(lName);
-  if (num < 0) or (num > Length(BotList)-1) then
-    num := -1;
-  if (num = -1) and (lName <> '') and (BotList <> nil) then
-    for a := 0 to High(BotList) do
-      if AnsiLowerCase(BotList[a].name) = lName then
-      begin
-        num := a;
-        Break;
-      end;
-  if num = -1 then
-    Exit;
+  if lName = '' then 
+    num := Random(Length(BotList))
+  else
+  begin
+    if (num < 0) or (num > Length(BotList)-1) then
+      num := -1;
+    if (num = -1) and (BotList <> nil) then
+      lName := AnsiLowerCase(lName);
+      for a := 0 to High(BotList) do
+        if AnsiLowerCase(BotList[a].name) = lName then
+        begin
+          num := a;
+          Break;
+        end;
+    if num = -1 then
+      Exit;
+  end;
 
 // Èìÿ áîòà:
   _name := BotList[num].name;
-// Çàíÿòî - âûáèðàåì ñëó÷àéíîå:
-  if not g_Player_ValidName(_name) then
-  repeat
-    _name := Format('DFBOT%.2d', [Random(100)]);
-  until g_Player_ValidName(_name);
+  if (_name = '') and (BotNames <> nil) then
+    for a := 0 to High(BotNames) do
+      if g_Player_ExistingName(BotNames[a]) then
+      begin
+        _name := BotNames[a];
+        Break;
+      end;
 
 // Ìîäåëü:
   _model := BotList[num].model;
@@ -1172,7 +1133,10 @@ begin
 // Ñîçäàåì áîòà:
   with g_Player_Get(g_Player_Create(_model, BotList[num].color, Team, True)) as TBot do
   begin
-    Name := _name;
+  // Åñëè èìåíè íåò, äåëàåì åãî èç UID áîòà
+    if _name = ''
+      then Name := Format('DFBOT%.5d', [UID])
+      else Name := _name;
 
     FDifficult.DiagFire := BotList[num].diag_fire;
     FDifficult.InvisFire := BotList[num].invis_fire;
@@ -1263,90 +1227,91 @@ var
   path: AnsiString;
 begin
   BotNames := nil;
+  BotList := nil;
 
   path := BOTNAMES_FILENAME;
-  if e_FindResource(DataDirs, path) = false then
-    Exit;
-
-// ×èòàåì âîçìîæíûå èìåíà áîòîâ èç ôàéëà:
-  AssignFile(F, path);
-  Reset(F);
-
-  while not EOF(F) do
+  if e_FindResource(DataDirs, path) then
   begin
-    ReadLn(F, s);
+    // ×èòàåì âîçìîæíûå èìåíà áîòîâ èç ôàéëà:
+    AssignFile(F, path);
+    Reset(F);
 
-    s := Trim(s);
-    if s = '' then
-      Continue;
+    while not EOF(F) do
+    begin
+      ReadLn(F, s);
 
-    SetLength(BotNames, Length(BotNames)+1);
-    BotNames[High(BotNames)] := s;
-  end;
+      s := Trim(s);
+      if s = '' then
+        Continue;
 
-  CloseFile(F);
+      SetLength(BotNames, Length(BotNames)+1);
+      BotNames[High(BotNames)] := s;
+    end;
 
-// Ïåðåìåøèâàåì èõ:
-  g_Bot_MixNames();
+    CloseFile(F);
 
-// ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ:
-  config := TConfig.CreateFile(path);
-  BotList := nil;
-  a := 0;
+    // Ïåðåìåøèâàåì èõ:
+    g_Bot_MixNames();
+  end;
 
-  while config.SectionExists(IntToStr(a)) do
+  path := BOTLIST_FILENAME;
+  if e_FindResource(DataDirs, path) then
   begin
-    SetLength(BotList, Length(BotList)+1);
+    // ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ:
+    config := TConfig.CreateFile(path);
+    a := 0;
 
-    with BotList[High(BotList)] do
+    while config.SectionExists(IntToStr(a)) do
     begin
-    // Èìÿ áîòà:
-      name := config.ReadStr(IntToStr(a), 'name', '');
-    // Ìîäåëü:
-      model := config.ReadStr(IntToStr(a), 'model', '');
-    // Êîìàíäà:
-      if config.ReadStr(IntToStr(a), 'team', 'red') = 'red' then
-        team := TEAM_RED
-      else
-        team := TEAM_BLUE;
-    // Öâåò ìîäåëè:
-      sa := parse(config.ReadStr(IntToStr(a), 'color', ''));
-      color.R := StrToIntDef(sa[0], 0);
-      color.G := StrToIntDef(sa[1], 0);
-      color.B := StrToIntDef(sa[2], 0);
-    // Âåðîÿòíîñòü ñòðåëüáû ïîä óãëîì:
-      diag_fire := config.ReadInt(IntToStr(a), 'diag_fire', 0);
-    // Âåðîÿòíîñòü îòâåòíîãî îãíÿ ïî íåâèäèìîìó ñîïåðíèêó:
-      invis_fire := config.ReadInt(IntToStr(a), 'invis_fire', 0);
-    // Òî÷íîñòü ñòðåëüáû ïîä óãëîì:
-      diag_precision := config.ReadInt(IntToStr(a), 'diag_precision', 0);
-    // Òî÷íîñòü ñòðåëüáû â ïîëåòå:
-      fly_precision := config.ReadInt(IntToStr(a), 'fly_precision', 0);
-    // Òî÷íîñòü óêëîíåíèÿ îò ñíàðÿäîâ:
-      cover := config.ReadInt(IntToStr(a), 'cover', 0);
-    // Âåðîÿòíîñòü ïðûæêà ïðè ïðèáëèæåíèè ñîïåðíèêà:
-      close_jump := config.ReadInt(IntToStr(a), 'close_jump', 0);
-    // Ïðèîðèòåòû îðóæèÿ äëÿ äàëüíåãî áîÿ:
-      sa := parse(config.ReadStr(IntToStr(a), 'w_prior1', ''));
-      if Length(sa) = 10 then
-        for b := 0 to 9 do
-          w_prior1[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
-    // Ïðèîðèòåòû îðóæèÿ äëÿ áëèæíåãî áîÿ:
-      sa := parse(config.ReadStr(IntToStr(a), 'w_prior2', ''));
-      if Length(sa) = 10 then
-        for b := 0 to 9 do
-          w_prior2[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
-
-      {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
-      if Length(sa) = 10 then
-        for b := 0 to 9 do
-          w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
+      SetLength(BotList, Length(BotList)+1);
+
+      with BotList[High(BotList)] do
+      begin
+        name := config.ReadStr(IntToStr(a), 'name', '');  // Èìÿ áîòà
+        model := config.ReadStr(IntToStr(a), 'model', '');  // Ìîäåëü
+
+        // Êîìàíäà
+        if config.ReadStr(IntToStr(a), 'team', 'red') = 'red'
+          then team := TEAM_RED
+          else team := TEAM_BLUE;
+
+        // Öâåò ìîäåëè
+        sa := parse(config.ReadStr(IntToStr(a), 'color', ''));
+        color.R := StrToIntDef(sa[0], 0);
+        color.G := StrToIntDef(sa[1], 0);
+        color.B := StrToIntDef(sa[2], 0);
+
+        diag_fire := config.ReadInt(IntToStr(a), 'diag_fire', 0);  // Âåðîÿòíîñòü ñòðåëüáû ïîä óãëîì
+        invis_fire := config.ReadInt(IntToStr(a), 'invis_fire', 0);  // Âåðîÿòíîñòü îòâåòíîãî îãíÿ ïî íåâèäèìîìó ñîïåðíèêó
+        diag_precision := config.ReadInt(IntToStr(a), 'diag_precision', 0);  // Òî÷íîñòü ñòðåëüáû ïîä óãëîì
+        fly_precision := config.ReadInt(IntToStr(a), 'fly_precision', 0);  // Òî÷íîñòü ñòðåëüáû â ïîëåòå
+        cover := config.ReadInt(IntToStr(a), 'cover', 0);  // Òî÷íîñòü óêëîíåíèÿ îò ñíàðÿäîâ
+        close_jump := config.ReadInt(IntToStr(a), 'close_jump', 0);  // Âåðîÿòíîñòü ïðûæêà ïðè ïðèáëèæåíèè ñîïåðíèêà
+
+        // Ïðèîðèòåòû îðóæèÿ äëÿ äàëüíåãî áîÿ
+        sa := parse(config.ReadStr(IntToStr(a), 'w_prior1', ''));
+        if Length(sa) = 10 then
+          for b := 0 to 9 do
+            w_prior1[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
+
+        // Ïðèîðèòåòû îðóæèÿ äëÿ áëèæíåãî áîÿ
+        sa := parse(config.ReadStr(IntToStr(a), 'w_prior2', ''));
+        if Length(sa) = 10 then
+          for b := 0 to 9 do
+            w_prior2[b] := EnsureRange(StrToInt(sa[b]), 0, 9);
+
+        {sa := parse(config.ReadStr(IntToStr(a), 'w_prior3', ''));
+        if Length(sa) = 10 then
+          for b := 0 to 9 do
+            w_prior3[b] := EnsureRange(StrToInt(sa[b]), 0, 9);}
+      end;
+
+      a += 1;
     end;
 
-    a := a + 1;
+    config.Free();
   end;
 
-  config.Free();
   SetLength(SavedStates, 0);
 end;
 
@@ -1437,6 +1402,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();
@@ -1491,6 +1458,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;
@@ -1516,6 +1497,7 @@ begin
         Color := gPlayers[a].FModel.Color;
         Lives := gPlayers[a].FLives;
         Spectator := gPlayers[a].FSpectator;
+        UID := gPlayers[a].FUID;
       end;
     end;
 end;
@@ -1549,8 +1531,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
@@ -1570,21 +1552,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;
@@ -1612,6 +1597,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,
@@ -2067,11 +2054,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
@@ -2199,6 +2246,7 @@ begin
   FFirePainTime := 0;
   FFireAttacker := 0;
   FHandicap := 100;
+  FCorpse := -1;
 
   FActualModelName := 'doomer';
 
@@ -2294,8 +2342,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;
 
   // Áóôåð óðîíà:
@@ -2443,8 +2496,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;
@@ -2730,7 +2786,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
@@ -2747,7 +2803,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]);
 
@@ -2762,7 +2818,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;
@@ -3468,10 +3524,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
@@ -3493,7 +3554,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);
@@ -3603,7 +3664,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
@@ -3654,7 +3715,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
@@ -3664,7 +3725,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
@@ -3756,6 +3817,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;
@@ -3783,6 +3853,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
@@ -3792,6 +3892,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
@@ -3812,9 +3913,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;
@@ -3832,6 +3933,7 @@ begin
       wantThisWeapon[i] := true;
       Inc(wwc);
     end;
+
   // exclude currently selected weapon from the set
   wantThisWeapon[FCurrWeap] := false;
   // slow down alterations a little
@@ -3862,6 +3964,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;
@@ -3900,6 +4003,7 @@ begin
   end;
 
   nw := getNextWeaponIndex();
+  //
   if nw = 255 then exit; // don't reset anything here
   if nw > High(FWeapon) then
   begin
@@ -3943,18 +4047,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;
@@ -3962,7 +4058,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
@@ -4028,6 +4123,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);
@@ -4039,7 +4136,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;
@@ -4051,7 +4149,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;
@@ -4063,7 +4162,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;
@@ -4075,7 +4175,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;
@@ -4087,7 +4188,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;
@@ -4099,7 +4201,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;
@@ -4111,7 +4214,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;
@@ -4123,7 +4227,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;
@@ -4297,7 +4402,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();
@@ -4370,6 +4475,9 @@ begin
         if gFlash = 2 then Inc(FPickup, 5);
       end;
   end;
+
+  if (shouldSwitch(switchWeapon, hadWeapon)) then
+    QueueWeaponSwitch(switchWeapon);
 end;
 
 procedure TPlayer.Touch();
@@ -4414,6 +4522,7 @@ begin
   FDeath := 0;
   FSecrets := 0;
   FSpawnInvul := 0;
+  FCorpse := -1;
   FReady := False;
   if FNoRespawn then
   begin
@@ -4457,143 +4566,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);
@@ -4611,6 +4635,7 @@ begin
   FPain := 0;
   FLastHit := 0;
   FSpawnInvul := 0;
+  FCorpse := -1;
 
   if not g_Game_IsServer then
     Exit;
@@ -4793,6 +4818,7 @@ begin
   FPhysics := False;
   FWantsInGame := False;
   FSpawned := False;
+  FCorpse := -1;
 
   if FNoRespawn then
   begin
@@ -5025,11 +5051,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
@@ -5039,16 +5066,26 @@ 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;
@@ -5069,11 +5106,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
@@ -5114,10 +5154,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
@@ -5130,8 +5166,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;
 
@@ -5145,8 +5180,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
@@ -5265,8 +5298,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
@@ -5389,10 +5421,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;
@@ -5533,7 +5564,7 @@ begin
             (y >= 0) and (y <= PLAYER_RECT.Height);
 end;
 
-function g_Player_ValidName(Name: string): Boolean;
+function g_Player_ExistingName(Name: string): Boolean;
 var
   a: Integer;
 begin
@@ -5600,10 +5631,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;
@@ -5612,6 +5642,7 @@ begin
   case FCurrWeap of
     WEAPON_KASTET:
     begin
+      visible := False;
       DoPunch();
       if R_BERSERK in FRulez then
       begin
@@ -5653,14 +5684,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;
@@ -5669,7 +5698,6 @@ begin
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
       FFireAngle := FAngle;
-      f := True;
       FShellTimer := 10;
       FShellType := SHELL_SHELL;
     end;
@@ -5678,7 +5706,6 @@ begin
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex, Gamey);
       FFireAngle := FAngle;
-      f := True;
       FShellTimer := 13;
       FShellType := SHELL_DBLSHELL;
     end;
@@ -5687,7 +5714,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;
@@ -5696,28 +5722,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;
@@ -5727,11 +5749,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)
@@ -5748,16 +5769,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
@@ -5820,7 +5845,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
@@ -5914,10 +5939,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
@@ -5930,8 +5964,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
@@ -6480,10 +6524,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;
 
@@ -6589,7 +6632,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;
@@ -6620,7 +6663,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
@@ -8048,7 +8091,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.