DEADSOFTWARE

game: remove unneded render imports
[d2df-sdl.git] / src / game / g_player.pas
index 7436e7a95a77d928002f0dd974b74fe045cebda9..92afc636f73aa29a5a0922fbc8493cb8b8b7bf28 100644 (file)
@@ -2,8 +2,7 @@
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, version 3 of the License ONLY.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -22,7 +21,7 @@ interface
 uses
   SysUtils, Classes,
   {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
-  e_graphics, g_playermodel, g_basic, g_textures,
+  g_base, g_playermodel, g_basic, g_textures,
   g_weapons, g_phys, g_sound, g_saveload, MAPDEF,
   g_panel;
 
@@ -95,12 +94,27 @@ const
   PLAYER_AP_SOFT  = 100;
   PLAYER_AP_LIMIT = 200;
   SUICIDE_DAMAGE  = 112;
+  WEAPON_DELAY    = 5;
+
+  PLAYER_BURN_TIME = 110;
 
   PLAYER1_DEF_COLOR: TRGB = (R:64; G:175; B:48);
   PLAYER2_DEF_COLOR: TRGB = (R:96; G:96; B:96);
 
+  AIR_DEF = 360;
+  AIR_MAX = 1091;
+  JET_MAX = 540; // ~30 sec
+  ANGLE_RIGHTUP   = 55;
+  ANGLE_RIGHTDOWN = -35;
+  ANGLE_LEFTUP    = 125;
+  ANGLE_LEFTDOWN  = -145;
+  WEAPONPOINT: Array [TDirection] of TDFPoint = ((X:16; Y:32), (X:47; Y:32));
+  TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0),
+                                                   (R:0; G:0; B:255));
+
 type
   TPlayerStat = record
+    Num: Integer;
     Ping: Word;
     Loss: Byte;
     Name: String;
@@ -111,6 +125,7 @@ type
     Kills: Word;
     Color: TRGB;
     Spectator: Boolean;
+    UID: Word;
   end;
 
   TPlayerStatArray = Array of TPlayerStat;
@@ -127,7 +142,7 @@ type
     MaxAmmo:    Array [A_BULLETS..A_HIGH] of Word;
     Weapon:     Array [WP_FIRST..WP_LAST] of Boolean;
     Rulez:      Set of R_ITEM_BACKPACK..R_BERSERK;
-    WaitRecall: Boolean;
+    Used:       Boolean;
   end;
 
   TKeyState = record
@@ -162,11 +177,8 @@ type
     FFlag:      Byte;
     FSecrets:   Integer;
     FCurrWeap:  Byte;
-    //FNetForceWeap: Byte; // spam server with this -- this is new weapon we want to use
-    FNetForceWeapFIdx: LongWord; // frame index; ignore weapon change if it is lesser than this
-    //FCurrFrameIdx: LongWord; // increased in each `Update()`
     FNextWeap:  WORD;
-    FNextWeapDelay: Byte; // frames (unused)
+    FNextWeapDelay: Byte; // frames
     FBFGFireCounter: SmallInt;
     FLastSpawnerUID: Word;
     FLastHit:   Byte;
@@ -176,23 +188,28 @@ type
     FFirePainTime:   Integer;
     FFireAttacker:   Word;
 
-    FSavedState: TPlayerSavedState;
+    FSavedStateNum:   Integer;
 
     FModel:     TPlayerModel;
-    FPunchAnim: TAnimation;
+    FPunchAnim: TAnimationState;
     FActionPrior:    Byte;
     FActionAnim:     Byte;
     FActionForce:    Boolean;
     FActionChanged:  Boolean;
     FAngle:     SmallInt;
     FFireAngle: SmallInt;
+    FIncCamOld:      Integer;
     FIncCam:         Integer;
+    FSlopeOld:       Integer;
     FShellTimer:     Integer;
     FShellType:      Byte;
     FSawSound:       TPlayableSound;
     FSawSoundIdle:   TPlayableSound;
     FSawSoundHit:    TPlayableSound;
     FSawSoundSelect: TPlayableSound;
+    FFlameSoundOn:   TPlayableSound;
+    FFlameSoundOff:  TPlayableSound;
+    FFlameSoundWork: TPlayableSound;
     FJetSoundOn:     TPlayableSound;
     FJetSoundOff:    TPlayableSound;
     FJetSoundFly:    TPlayableSound;
@@ -203,9 +220,6 @@ type
     FNetTime: LongWord;
     mEDamageType: Integer;
 
-    // client-side only
-    weaponSwitchKeyReleased: array[0..16] of Byte; // bit 0: was released on prev frame; bit 1: new status
-
 
     function CollideLevel(XInc, YInc: Integer): Boolean;
     function StayOnStep(XInc, YInc: Integer): Boolean;
@@ -216,7 +230,6 @@ type
     {procedure CollideItem();}
     procedure FlySmoke(Times: DWORD = 1);
     procedure OnFireFlame(Times: DWORD = 1);
-    function GetAmmoByWeapon(Weapon: Byte): Word;
     procedure SetAction(Action: Byte; Force: Boolean = False);
     procedure OnDamage(Angle: SmallInt); virtual;
     function firediry(): Integer;
@@ -237,7 +250,7 @@ type
 
     procedure doDamage (v: Integer);
 
-    function followCorpse(): Boolean;
+    function refreshCorpse(): Boolean;
 
   public
     FDamageBuffer:   Integer;
@@ -258,13 +271,19 @@ type
     FWantsInGame: Boolean;
     FGhost:     Boolean;
     FPhysics:   Boolean;
+    FFlaming:   Boolean;
     FJetpack:   Boolean;
     FActualModelName: string;
     FClientID:  SmallInt;
     FPing:      Word;
     FLoss:      Byte;
+    FReady:     Boolean;
     FDummy:     Boolean;
     FFireTime:  Integer;
+    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;
@@ -277,9 +296,9 @@ type
     function    GetRespawnPoint(): Byte;
     procedure   PressKey(Key: Byte; Time: Word = 1);
     procedure   ReleaseKeys();
-    procedure   ReleaseKeysNoWeapon();
     procedure   SetModel(ModelName: String);
     procedure   SetColor(Color: TRGB);
+    function    GetColor(): TRGB;
     procedure   SetWeapon(W: Byte);
     function    IsKeyPressed(K: Byte): Boolean;
     function    GetKeys(): Byte;
@@ -298,7 +317,7 @@ type
     procedure   BFGHit();
     function    GetFlag(Flag: Byte): Boolean;
     procedure   SetFlag(Flag: Byte);
-    function    DropFlag(): Boolean;
+    function    DropFlag(Silent: Boolean = True): Boolean;
     procedure   AllRulez(Health: Boolean);
     procedure   RestoreHealthArmor();
     procedure   FragCombo();
@@ -312,14 +331,7 @@ type
     procedure   Spectate(NoMove: Boolean = False);
     procedure   SwitchNoClip;
     procedure   SoftReset();
-    procedure   Draw(); virtual;
-    procedure   DrawPain();
-    procedure   DrawPickup();
-    procedure   DrawRulez();
-    procedure   DrawAim();
-    procedure   DrawIndicator();
-    procedure   DrawBubble();
-    procedure   DrawGUI();
+    procedure   PreUpdate();
     procedure   Update(); virtual;
     procedure   RememberState();
     procedure   RecallState();
@@ -331,9 +343,11 @@ type
     procedure   SetLerp(XTo, YTo: Integer);
     procedure   QueueWeaponSwitch(Weapon: Byte);
     procedure   RealizeCurrentWeapon();
+    procedure   FlamerOn;
+    procedure   FlamerOff;
     procedure   JetpackOn;
     procedure   JetpackOff;
-    procedure   CatchFire(Attacker: Word);
+    procedure   CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
 
     //WARNING! this does nothing for now, but still call it!
     procedure positionChanged (); //WARNING! call this after entity position was changed, or coldet will not work right!
@@ -341,10 +355,9 @@ type
     procedure getMapBox (out x, y, w, h: Integer); inline;
     procedure moveBy (dx, dy: Integer); inline;
 
-    procedure releaseAllWeaponSwitchKeys ();
-    procedure weaponSwitchKeysStateChange (index: Integer; pressed: Boolean);
-    function isWeaponSwitchKeyReleased (index: Integer): Boolean;
-    procedure weaponSwitchKeysShiftNewStates ();
+    function getCameraObj(): TObj;
+
+    function GetAmmoByWeapon(Weapon: Byte): Word; // private state
 
   public
     property    Vel: TPoint2i read FObj.Vel;
@@ -361,9 +374,6 @@ type
     property    Death: Integer read FDeath write FDeath;
     property    Kills: Integer read FKills write FKills;
     property    CurrWeap: Byte read FCurrWeap write FCurrWeap;
-    //property    NetForceWeap: Byte read FNetForceWeap write FNetForceWeap;
-    property    NetForceWeapFIdx: LongWord read FNetForceWeapFIdx write FNetForceWeapFIdx;
-    //property    CurrFrameIdx: LongWord read FCurrFrameIdx write FCurrFrameIdx;
     property    MonsterKills: Integer read FMonsterKills write FMonsterKills;
     property    Secrets: Integer read FSecrets;
     property    GodMode: Boolean read FGodMode write FGodMode;
@@ -380,10 +390,23 @@ type
     property    GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
     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;
 
+    (* internal state *)
+    property    Angle_: SmallInt read FAngle;
+    property    Spectator: Boolean read FSpectator;
+    property    NoRespawn: Boolean read FNoRespawn;
+    property    Berserk: Integer read FBerserk;
+    property    Pain: Integer read FPain;
+    property    Pickup: Integer read FPickup;
+    property    PunchAnim: TAnimationState read FPunchAnim write FPunchAnim;
+    property    SpawnInvul: Integer read FSpawnInvul;
+    property    Ghost: Boolean read FGhost;
+
   published
     property eName: String read FName write FName;
     property eHealth: Integer read FHealth write FHealth;
@@ -471,7 +494,6 @@ type
     procedure   Respawn(Silent: Boolean; Force: Boolean = False); override;
     constructor Create(); override;
     destructor  Destroy(); override;
-    procedure   Draw(); override;
     function    PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean; override;
     function    Heal(value: Word; Soft: Boolean): Boolean; override;
     procedure   Update(); override;
@@ -482,12 +504,13 @@ type
   PGib = ^TGib;
   TGib = record
     alive:    Boolean;
-    ID:       DWORD;
-    MaskID:   DWORD;
     RAngle:   Integer;
     Color:    TRGB;
     Obj:      TObj;
 
+    ModelID: Integer;
+    GibID: Integer;
+
     procedure getMapBox (out x, y, w, h: Integer); inline;
     procedure moveBy (dx, dy: Integer); inline;
 
@@ -497,12 +520,10 @@ type
 
   PShell = ^TShell;
   TShell = record
-    SpriteID: DWORD;
-    alive:     Boolean;
+    alive:    Boolean;
     SType:    Byte;
     RAngle:   Integer;
     Timeout:  Cardinal;
-    CX, CY:   Integer;
     Obj:      TObj;
 
     procedure getMapBox (out x, y, w, h: Integer); inline;
@@ -513,22 +534,18 @@ type
 
   TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
   private
-    FModelName:     String;
     FMess:          Boolean;
     FState:         Byte;
     FDamage:        Byte;
-    FColor:         TRGB;
     FObj:           TObj;
     FPlayerUID:     Word;
-    FAnimation:     TAnimation;
-    FAnimationMask: TAnimation;
+    FModel:   TPlayerModel;
 
   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);
     procedure   LoadState (st: TStream);
 
@@ -542,6 +559,7 @@ type
     property    Obj: TObj read FObj; // copies object
     property    State: Byte read FState;
     property    Mess: Boolean read FMess;
+    property    Model: TPlayerModel read FModel;
   end;
 
   TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
@@ -557,11 +575,12 @@ var
   gTeamStat: TTeamStat;
   gFly: Boolean = False;
   gAimLine: Boolean = False;
-  gChatBubble: Byte = 0;
-  gPlayerIndicator: Boolean = True;
+  gChatBubble: Integer = 0;
+  gPlayerIndicator: Integer = 1;
+  gPlayerIndicatorStyle: Integer = 0;
   gNumBots: Word = 0;
-  gLMSPID1: Word = 0;
-  gLMSPID2: Word = 0;
+  gSpectLatchPID1: Word = 0;
+  gSpectLatchPID2: Word = 0;
   MAX_RUNVEL: Integer = 8;
   VEL_JUMP: Integer = 10;
   SHELL_TIMEOUT: Cardinal = 60000;
@@ -581,41 +600,37 @@ function  g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boole
 function  g_Player_CreateFromState (st: TStream): Word;
 procedure g_Player_Remove(UID: Word);
 procedure g_Player_ResetTeams();
+procedure g_Player_PreUpdate();
 procedure g_Player_UpdateAll();
-procedure g_Player_DrawAll();
-procedure g_Player_DrawDebug(p: TPlayer);
-procedure g_Player_DrawHealth();
 procedure g_Player_RememberAll();
 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);
-procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: String; fColor: TRGB);
+function  g_Player_CreateCorpse(Player: TPlayer): Integer;
+procedure g_Player_CreateGibs (fX, fY, mid: Integer; fColor: TRGB);
 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
 procedure g_Player_UpdatePhysicalObjects();
-procedure g_Player_DrawCorpses();
-procedure g_Player_DrawShells();
 procedure g_Player_RemoveAllCorpses();
 procedure g_Player_Corpses_SaveState (st: TStream);
 procedure g_Player_Corpses_LoadState (st: TStream);
-procedure g_Bot_Add(Team, Difficult: Byte);
-procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1);
+procedure g_Player_ResetReady();
+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();
 
 implementation
 
 uses
-{$INCLUDE ../nogl/noGLuses.inc}
 {$IFDEF ENABLE_HOLMES}
   g_holmes,
 {$ENDIF}
-  e_log, g_map, g_items, g_console, g_gfx, Math,
-  g_options, g_triggers, g_menu, g_game, g_grid,
-  wadreader, g_main, g_monsters, CONFIG, g_language,
-  g_net, g_netmsg, g_window,
+  e_log, g_map, g_items, g_console, g_gfx, Math, r_playermodel, r_gfx,
+  g_options, g_triggers, g_menu, g_game, g_grid, e_res,
+  wadreader, g_monsters, CONFIG, g_language,
+  g_net, g_netmsg,
   utils, xstreams;
 
 const PLR_SAVE_VERSION = 0;
@@ -641,26 +656,16 @@ const
   TIME_RESPAWN1 = 1500;
   TIME_RESPAWN2 = 2000;
   TIME_RESPAWN3 = 3000;
-  AIR_DEF = 360;
-  AIR_MAX = 1091;
-  JET_MAX = 540; // ~30 sec
   PLAYER_SUIT_TIME    = 30000;
   PLAYER_INVUL_TIME   = 30000;
   PLAYER_INVIS_TIME   = 35000;
   FRAG_COMBO_TIME = 3000;
   VEL_SW  = 4;
   VEL_FLY = 6;
-  ANGLE_RIGHTUP   = 55;
-  ANGLE_RIGHTDOWN = -35;
-  ANGLE_LEFTUP    = 125;
-  ANGLE_LEFTDOWN  = -145;
   PLAYER_HEADRECT: TRectWH = (X:24; Y:12; Width:20; Height:12);
-  WEAPONPOINT: Array [TDirection] of TDFPoint = ((X:16; Y:32), (X:47; Y:32));
   BOT_MAXJUMP = 84;
   BOT_LONGDIST   = 300;
   BOT_UNSAFEDIST = 128;
-  TEAMCOLOR: Array [TEAM_RED..TEAM_BLUE] of TRGB = ((R:255; G:0; B:0),
-                                                   (R:0; G:0; B:255));
   DIFFICULT_EASY: TDifficult = (DiagFire: 32; InvisFire: 32; DiagPrecision: 32;
                                 FlyPrecision: 32; Cover: 32; CloseJump: 32;
                                 WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
@@ -702,6 +707,7 @@ var
   CurrentShell: Integer = 0;
   BotNames: Array of String;
   BotList: Array of TBotProfile;
+  SavedStates: Array of TPlayerSavedState;
 
 
 function Lerp(X, Y, Factor: Integer): Integer;
@@ -774,7 +780,7 @@ begin
   ok := False;
   a := 0;
 
-// Åñòü ëè ìåñòî â gPlayers:
+// Есть ли место в gPlayers:
   if gPlayers <> nil then
     for a := 0 to High(gPlayers) do
       if gPlayers[a] = nil then
@@ -783,14 +789,14 @@ begin
         Break;
       end;
 
-// Íåò ìåñòà - ðàñøèðÿåì gPlayers:
+// Нет места - расширяем gPlayers:
   if not ok then
   begin
     SetLength(gPlayers, Length(gPlayers)+1);
     a := High(gPlayers);
   end;
 
-// Ñîçäàåì îáúåêò èãðîêà:
+// Создаем объект игрока:
   if Bot then
     gPlayers[a] := TBot.Create()
   else
@@ -800,7 +806,7 @@ begin
   gPlayers[a].FActualModelName := ModelName;
   gPlayers[a].SetModel(ModelName);
 
-// Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî:
+// Нет модели - создание не возможно:
   if gPlayers[a].FModel = nil then
   begin
     gPlayers[a].Free();
@@ -824,7 +830,7 @@ begin
     GM_COOP: gPlayers[a].FTeam := TEAM_COOP;
   end;
 
-// Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû:
+// Если командная игра - красим модель в цвет команды:
   gPlayers[a].FColor := Color;
   if gPlayers[a].FTeam in [TEAM_RED, TEAM_BLUE] then
     gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
@@ -846,27 +852,27 @@ begin
   result := 0;
   if (st = nil) then exit; //???
 
-  // Ñèãíàòóðà èãðîêà
+  // Сигнатура игрока
   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);
 
   ok := false;
   a := 0;
 
-  // Åñòü ëè ìåñòî â gPlayers:
+  // Есть ли место в gPlayers:
   for a := 0 to High(gPlayers) do if (gPlayers[a] = nil) then begin ok := true; break; end;
 
-  // Íåò ìåñòà - ðàñøèðÿåì gPlayers
+  // Нет места - расширяем gPlayers
   if not ok then
   begin
     SetLength(gPlayers, Length(gPlayers)+1);
     a := High(gPlayers);
   end;
 
-  // Ñîçäàåì îáúåêò èãðîêà
+  // Создаем объект игрока
   if Bot then
     gPlayers[a] := TBot.Create()
   else
@@ -874,98 +880,99 @@ begin
   gPlayers[a].FIamBot := Bot;
   gPlayers[a].FPhysics := True;
 
-  // UID èãðîêà
+  // 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].FNetForceWeap := gPlayers[a].FCurrWeap;
-  // Ñëåäóþùåå æåëàåìîå îðóæèå
+  // Следующее желаемое оружие
   gPlayers[a].FNextWeap := utils.readWord(st);
-  // ...è ïàóçà
+  // ...и пауза
   gPlayers[a].FNextWeapDelay := utils.readByte(st);
-  // Âðåìÿ çàðÿäêè BFG
+  // Время зарядки 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();
@@ -974,7 +981,7 @@ begin
     exit;
   end;
 
-  // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû
+  // Если командная игра - красим модель в цвет команды
   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
     gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
   else
@@ -1012,7 +1019,7 @@ begin
       end;
 end;
 
-procedure g_Bot_Add(Team, Difficult: Byte);
+procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
 var
   m: SSArray;
   _name, _model: String;
@@ -1020,12 +1027,12 @@ var
 begin
   if not g_Game_IsServer then Exit;
 
-// Ñïèñîê íàçâàíèé ìîäåëåé:
+// Список названий моделей:
   m := g_PlayerModel_GetNames();
   if m = nil then
     Exit;
 
-// Êîìàíäà:
+// Команда:
   if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
     Team := TEAM_COOP // COOP
   else
@@ -1034,7 +1041,7 @@ begin
     else
       if Team = TEAM_NONE then // CTF / TDM
       begin
-       // Àâòîáàëàíñ êîìàíä:
+       // Автобаланс команд:
         tr := 0;
         tb := 0;
 
@@ -1060,7 +1067,7 @@ begin
               Team := TEAM_BLUE;
       end;
 
-// Âûáèðàåì áîòó èìÿ:
+// Выбираем боту имя:
   _name := '';
   if BotNames <> nil then
     for a := 0 to High(BotNames) do
@@ -1070,23 +1077,21 @@ begin
         Break;
       end;
 
-// Èìåíè íåò, çàäàåì ñëó÷àéíîå:
-  if _name = '' then
-    repeat
-      _name := Format('DFBOT%.2d', [Random(100)]);
-    until g_Player_ValidName(_name);
-
-// Âûáèðàåì ñëó÷àéíóþ ìîäåëü:
+// Выбираем случайную модель:
   _model := m[Random(Length(m))];
 
-// Ñîçäàåì áîòà:
+// Создаем бота:
   with g_Player_Get(g_Player_Create(_model,
                                     _RGB(Min(Random(9)*32, 255),
                                          Min(Random(9)*32, 255),
                                          Min(Random(9)*32, 255)),
                                     Team, True)) as TBot do
   begin
-    Name := _name;
+  // Если имени нет, делаем его из UID бота
+    if _name = '' then
+      Name := Format('DFBOT%.5d', [UID])
+    else
+      Name := _name;
 
     case Difficult of
       1: FDifficult := DIFFICULT_EASY;
@@ -1101,6 +1106,8 @@ begin
       //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
     end;
 
+    FHandicap := Handicap;
+
     g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
 
     if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
@@ -1109,7 +1116,7 @@ begin
   end;
 end;
 
-procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1);
+procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1; Handicap: Integer = 100);
 var
   m: SSArray;
   _name, _model: String;
@@ -1117,12 +1124,12 @@ var
 begin
   if not g_Game_IsServer then Exit;
 
-// Ñïèñîê íàçâàíèé ìîäåëåé:
+// Список названий моделей:
   m := g_PlayerModel_GetNames();
   if m = nil then
     Exit;
 
-// Êîìàíäà:
+// Команда:
   if (gGameSettings.GameType = GT_SINGLE) or (gGameSettings.GameMode = GM_COOP) then
     Team := TEAM_COOP // COOP
   else
@@ -1132,7 +1139,7 @@ begin
       if Team = TEAM_NONE then
         Team := BotList[num].team; // CTF / TDM
 
-// Âûáèðàåì íàñòðîéêè áîòà èç ñïèñêà ïî íîìåðó èëè èìåíè:
+// Выбираем настройки бота из списка по номеру или имени:
   lName := AnsiLowerCase(lName);
   if (num < 0) or (num > Length(BotList)-1) then
     num := -1;
@@ -1146,21 +1153,21 @@ begin
   if num = -1 then
     Exit;
 
-// Èìÿ áîòà:
+// Имя бота:
   _name := BotList[num].name;
-// Çàíÿòî - âûáèðàåì ñëó÷àéíîå:
+// Занято - выбираем случайное:
   if not g_Player_ValidName(_name) then
   repeat
     _name := Format('DFBOT%.2d', [Random(100)]);
   until g_Player_ValidName(_name);
 
-// Ìîäåëü:
+// Модель:
   _model := BotList[num].model;
-// Íåò òàêîé - âûáèðàåì ñëó÷àéíóþ:
+// Нет такой - выбираем случайную:
   if not InSArray(_model, m) then
     _model := m[Random(Length(m))];
 
-// Ñîçäàåì áîòà:
+// Создаем бота:
   with g_Player_Get(g_Player_Create(_model, BotList[num].color, Team, True)) as TBot do
   begin
     Name := _name;
@@ -1172,6 +1179,8 @@ begin
     FDifficult.Cover := BotList[num].cover;
     FDifficult.CloseJump := BotList[num].close_jump;
 
+    FHandicap := Handicap;
+
     for a := WP_FIRST to WP_LAST do
     begin
       FDifficult.WeaponPrior[a] := BotList[num].w_prior1[a];
@@ -1249,14 +1258,16 @@ var
   a, b: Integer;
   config: TConfig;
   sa: SSArray;
+  path: AnsiString;
 begin
   BotNames := nil;
 
-  if not FileExists(DataDir + BOTNAMES_FILENAME) then
+  path := BOTNAMES_FILENAME;
+  if e_FindResource(DataDirs, path) = false then
     Exit;
 
-// ×èòàåì âîçìîæíûå èìåíà áîòîâ èç ôàéëà:
-  AssignFile(F, DataDir + BOTNAMES_FILENAME);
+// Читаем возможные имена ботов из файла:
+  AssignFile(F, path);
   Reset(F);
 
   while not EOF(F) do
@@ -1273,11 +1284,11 @@ begin
 
   CloseFile(F);
 
-// Ïåðåìåøèâàåì èõ:
+// Перемешиваем их:
   g_Bot_MixNames();
 
-// ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ:
-  config := TConfig.CreateFile(DataDir + BOTLIST_FILENAME);
+// Читаем файл с параметрами ботов:
+  config := TConfig.CreateFile(path);
   BotList := nil;
   a := 0;
 
@@ -1287,38 +1298,38 @@ begin
 
     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
@@ -1334,6 +1345,7 @@ begin
   end;
 
   config.Free();
+  SetLength(SavedStates, 0);
 end;
 
 procedure g_Player_Free();
@@ -1357,6 +1369,17 @@ begin
 
   gPlayer1 := nil;
   gPlayer2 := nil;
+  SetLength(SavedStates, 0);
+end;
+
+procedure g_Player_PreUpdate();
+var
+  i: Integer;
+begin
+  if gPlayers = nil then Exit;
+  for i := 0 to High(gPlayers) do
+    if gPlayers[i] <> nil then
+      gPlayers[i].PreUpdate();
 end;
 
 procedure g_Player_UpdateAll();
@@ -1373,11 +1396,7 @@ begin
       if gPlayers[i] is TPlayer then
       begin
         gPlayers[i].Update();
-        if (not gPlayers[i].alive) then gPlayers[i].NetForceWeapFIdx := 0; // just in case
-        //if g_Game_IsClient or not g_Game_IsNet then
-        begin
-          gPlayers[i].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
-        end;
+        gPlayers[i].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
       end
       else
       begin
@@ -1389,55 +1408,6 @@ begin
   //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING);
 end;
 
-procedure g_Player_DrawAll();
-var
-  i: Integer;
-begin
-  if gPlayers = nil then Exit;
-
-  for i := 0 to High(gPlayers) do
-    if gPlayers[i] <> nil then
-      if gPlayers[i] is TPlayer then gPlayers[i].Draw()
-      else TBot(gPlayers[i]).Draw();
-end;
-
-procedure g_Player_DrawDebug(p: TPlayer);
-var
-  fW, fH: Byte;
-begin
-  if p = nil then Exit;
-  if (@p.FObj) = nil then Exit;
-
-  e_TextureFontGetSize(gStdFont, fW, fH);
-
-  e_TextureFontPrint(0, 0     , 'Pos X: ' + IntToStr(p.FObj.X), gStdFont);
-  e_TextureFontPrint(0, fH    , 'Pos Y: ' + IntToStr(p.FObj.Y), gStdFont);
-  e_TextureFontPrint(0, fH * 2, 'Vel X: ' + IntToStr(p.FObj.Vel.X), gStdFont);
-  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);
-end;
-
-procedure g_Player_DrawHealth();
-var
-  i: Integer;
-  fW, fH: Byte;
-begin
-  if gPlayers = nil then Exit;
-  e_TextureFontGetSize(gStdFont, fW, fH);
-
-  for i := 0 to High(gPlayers) do
-    if gPlayers[i] <> nil then
-    begin
-      e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X,
-      gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH * 2,
-      IntToStr(gPlayers[i].FHealth), gStdFont);
-      e_TextureFontPrint(gPlayers[i].FObj.X + gPlayers[i].FObj.Rect.X,
-      gPlayers[i].FObj.Y + gPlayers[i].FObj.Rect.Y + gPlayers[i].FObj.Rect.Height - fH,
-      IntToStr(gPlayers[i].FArmor), gStdFont);
-    end;
-end;
-
 function g_Player_Get(UID: Word): TPlayer;
 var
   a: Integer;
@@ -1484,6 +1454,7 @@ begin
       SetLength(Result, Length(Result)+1);
       with Result[High(Result)] do
       begin
+        Num := a;
         Ping := gPlayers[a].FPing;
         Loss := gPlayers[a].FLoss;
         Name := gPlayers[a].FName;
@@ -1494,10 +1465,27 @@ begin
         Color := gPlayers[a].FModel.Color;
         Lives := gPlayers[a].FLives;
         Spectator := gPlayers[a].FSpectator;
+        UID := gPlayers[a].FUID;
       end;
     end;
 end;
 
+procedure g_Player_ResetReady();
+var
+  a: Integer;
+begin
+  if not g_Game_IsServer then Exit;
+  if gPlayers = nil then Exit;
+
+  for a := 0 to High(gPlayers) do
+    if gPlayers[a] <> nil then
+    begin
+      gPlayers[a].FReady := False;
+      if g_Game_IsNet then
+        MH_SEND_GameEvent(NET_EV_INTER_READY, gPlayers[a].UID, 'N');
+    end;
+end;
+
 procedure g_Player_RememberAll;
 var
   i: Integer;
@@ -1532,21 +1520,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;
@@ -1569,47 +1560,36 @@ begin
         if not ok then
           find_id := Random(Length(gCorpses));
 
-        gCorpses[find_id] := TCorpse.Create(FObj.X, FObj.Y, FModel.Name, FHealth < -20);
-        gCorpses[find_id].FColor := FModel.Color;
+        gCorpses[find_id] := TCorpse.Create(FObj.X, FObj.Y, FModel.GetName(), FHealth < -20);
+        gCorpses[find_id].FModel.Color := FModel.Color;
         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,
-                          FObj.Y + PLAYER_RECT_CY,
-                          FModel.Name, FModel.Color);
+      g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX, FObj.Y + PLAYER_RECT_CY, FModel.id, FModel.Color);
   end;
 end;
 
 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
-var
-  SID: DWORD;
 begin
   if (gShells = nil) or (Length(gShells) = 0) then
     Exit;
 
   with gShells[CurrentShell] do
   begin
-    SpriteID := 0;
     g_Obj_Init(@Obj);
     Obj.Rect.X := 0;
     Obj.Rect.Y := 0;
     if T = SHELL_BULLET then
     begin
-      if g_Texture_Get('TEXTURE_SHELL_BULLET', SID) then
-        SpriteID := SID;
-      CX := 2;
-      CY := 1;
       Obj.Rect.Width := 4;
       Obj.Rect.Height := 2;
     end
     else
     begin
-      if g_Texture_Get('TEXTURE_SHELL_SHELL', SID) then
-        SpriteID := SID;
-      CX := 4;
-      CY := 2;
       Obj.Rect.Width := 7;
       Obj.Rect.Height := 3;
     end;
@@ -1629,29 +1609,31 @@ begin
   end;
 end;
 
-procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: string; fColor: TRGB);
+procedure g_Player_CreateGibs (fX, fY, mid: Integer; fColor: TRGB);
 var
   a: Integer;
   GibsArray: TGibsArray;
   Blood: TModelBlood;
 begin
+  if mid = -1 then
+    Exit;
   if (gGibs = nil) or (Length(gGibs) = 0) then
     Exit;
-  if not g_PlayerModel_GetGibs(ModelName, GibsArray) then
+  if not g_PlayerModel_GetGibs(mid, GibsArray) then
     Exit;
-  Blood := g_PlayerModel_GetBlood(ModelName);
+  Blood := PlayerModelsArray[mid].Blood;
 
   for a := 0 to High(GibsArray) do
     with gGibs[CurrentGib] do
     begin
+      ModelID := mid;
+      GibID := GibsArray[a];
       Color := fColor;
-      ID := GibsArray[a].ID;
-      MaskID := GibsArray[a].MaskID;
       alive := True;
       g_Obj_Init(@Obj);
-      Obj.Rect := GibsArray[a].Rect;
-      Obj.X := fX-GibsArray[a].Rect.X-(GibsArray[a].Rect.Width div 2);
-      Obj.Y := fY-GibsArray[a].Rect.Y-(GibsArray[a].Rect.Height div 2);
+      Obj.Rect := r_PlayerModel_GetGibRect(ModelID, GibID);
+      Obj.X := fX - Obj.Rect.X - (Obj.Rect.Width div 2);
+      Obj.Y := fY - Obj.Rect.Y - (Obj.Rect.Height div 2);
       g_Obj_PushA(@Obj, 25 + Random(10), Random(361));
       positionChanged(); // this updates spatial accelerators
       RAngle := Random(360);
@@ -1685,12 +1667,15 @@ var
   end;
 
 begin
-// Êóñêè ìÿñà:
+// Куски мяса:
   if gGibs <> nil then
     for i := 0 to High(gGibs) do
       if gGibs[i].alive then
         with gGibs[i] do
         begin
+          Obj.oldX := Obj.X;
+          Obj.oldY := Obj.Y;
+
           vel := Obj.Vel;
           mr := g_Obj_Move(@Obj, True, False, True);
           positionChanged(); // this updates spatial accelerators
@@ -1701,7 +1686,7 @@ begin
             Continue;
           end;
 
-        // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
+        // Отлетает от удара о стену/потолок/пол:
           if WordBool(mr and MOVE_HITWALL) then
             Obj.Vel.X := -(vel.X div 2);
           if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
@@ -1718,12 +1703,12 @@ begin
               RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
           end;
 
-        // Ñîïðîòèâëåíèå âîçäóõà äëÿ êóñêà òðóïà:
+        // Сопротивление воздуха для куска трупа:
           if gTime mod (GAME_TICK*3) = 0 then
             Obj.Vel.X := z_dec(Obj.Vel.X, 1);
         end;
 
-// Òðóïû:
+// Трупы:
   if gCorpses <> nil then
     for i := 0 to High(gCorpses) do
       if gCorpses[i] <> nil then
@@ -1735,12 +1720,15 @@ begin
         else
           gCorpses[i].Update();
 
-// Ãèëüçû:
+// Гильзы:
   if gShells <> nil then
     for i := 0 to High(gShells) do
       if gShells[i].alive then
         with gShells[i] do
         begin
+          Obj.oldX := Obj.X;
+          Obj.oldY := Obj.Y;
+
           vel := Obj.Vel;
           mr := g_Obj_Move(@Obj, True, False, True);
           positionChanged(); // this updates spatial accelerators
@@ -1751,7 +1739,7 @@ begin
             Continue;
           end;
 
-        // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
+        // Отлетает от удара о стену/потолок/пол:
           if WordBool(mr and MOVE_HITWALL) then
           begin
             Obj.Vel.X := -(vel.X div 2);
@@ -1827,57 +1815,6 @@ procedure TGib.positionChanged (); inline; begin end;
 procedure TShell.positionChanged (); inline; begin end;
 
 
-procedure g_Player_DrawCorpses();
-var
-  i: Integer;
-  a: TDFPoint;
-begin
-  if gGibs <> nil then
-    for i := 0 to High(gGibs) do
-      if gGibs[i].alive then
-        with gGibs[i] do
-        begin
-          if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
-            Continue;
-
-          a.X := Obj.Rect.X+(Obj.Rect.Width div 2);
-          a.y := Obj.Rect.Y+(Obj.Rect.Height div 2);
-
-          e_DrawAdv(ID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
-
-          e_Colors := Color;
-          e_DrawAdv(MaskID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
-          e_Colors.R := 255;
-          e_Colors.G := 255;
-          e_Colors.B := 255;
-        end;
-
-  if gCorpses <> nil then
-    for i := 0 to High(gCorpses) do
-      if gCorpses[i] <> nil then
-        gCorpses[i].Draw();
-end;
-
-procedure g_Player_DrawShells();
-var
-  i: Integer;
-  a: TDFPoint;
-begin
-  if gShells <> nil then
-    for i := 0 to High(gShells) do
-      if gShells[i].alive then
-        with gShells[i] do
-        begin
-          if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
-            Continue;
-
-          a.X := CX;
-          a.Y := CY;
-
-          e_DrawAdv(SpriteID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
-        end;
-end;
-
 procedure g_Player_RemoveAllCorpses();
 var
   i: Integer;
@@ -1901,25 +1838,25 @@ procedure g_Player_Corpses_SaveState (st: TStream);
 var
   count, i: Integer;
 begin
-  // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðóïîâ
+  // Считаем количество существующих трупов
   count := 0;
   for i := 0 to High(gCorpses) do if (gCorpses[i] <> nil) then Inc(count);
 
-  // Êîëè÷åñòâî òðóïîâ
+  // Количество трупов
   utils.writeInt(st, LongInt(count));
 
   if (count = 0) then exit;
 
-  // Ñîõðàíÿåì òðóïû
+  // Сохраняем трупы
   for i := 0 to High(gCorpses) do
   begin
     if gCorpses[i] <> nil then
     begin
-      // Íàçâàíèå ìîäåëè
-      utils.writeStr(st, gCorpses[i].FModelName);
-      // Òèï ñìåðòè
+      // Название модели
+      utils.writeStr(st, gCorpses[i].FModel.GetName());
+      // Тип смерти
       utils.writeBool(st, gCorpses[i].Mess);
-      // Ñîõðàíÿåì äàííûå òðóïà:
+      // Сохраняем данные трупа:
       gCorpses[i].SaveState(st);
     end;
   end;
@@ -1936,22 +1873,22 @@ begin
 
   g_Player_RemoveAllCorpses();
 
-  // Êîëè÷åñòâî òðóïîâ:
+  // Количество трупов:
   count := utils.readLongInt(st);
   if (count < 0) or (count > Length(gCorpses)) then raise XStreamError.Create('invalid number of corpses');
 
   if (count = 0) then exit;
 
-  // Çàãðóæàåì òðóïû
+  // Загружаем трупы
   for i := 0 to count-1 do
   begin
-    // Íàçâàíèå ìîäåëè:
+    // Название модели:
     str := utils.readStr(st);
-    // Òèï ñìåðòè
+    // Тип смерти
     b := utils.readBool(st);
-    // Ñîçäàåì òðóï
+    // Создаем труп
     gCorpses[i] := TCorpse.Create(0, 0, str, b);
-    // Çàãðóæàåì äàííûå òðóïà
+    // Загружаем данные трупа
     gCorpses[i].LoadState(st);
   end;
 end;
@@ -2019,6 +1956,11 @@ begin
     if FModel <> nil then FModel.Color := Color;
 end;
 
+function TPlayer.GetColor(): TRGB;
+begin
+  result := FModel.Color;
+end;
+
 procedure TPlayer.SwitchTeam;
 begin
   if g_Game_IsClient then
@@ -2085,7 +2027,7 @@ begin
      g_Sound_PlayExAt('SOUND_ITEM_GETMED', FObj.X, FObj.Y)
     else g_Sound_PlayExAt('SOUND_ITEM_GETITEM', FObj.X, FObj.Y);
 
-    // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòñÿ ñ äðóãèì èãðîêîì:
+    // Надо убрать с карты, если это не ключ, которым нужно поделится с другим игроком:
     if r and not ((ItemType in [ITEM_KEY_RED, ITEM_KEY_GREEN, ITEM_KEY_BLUE]) and
                   (gGameSettings.GameType = GT_SINGLE) and
                   (g_Player_GetCount() > 1)) then
@@ -2118,6 +2060,9 @@ begin
   FSawSoundIdle := TPlayableSound.Create();
   FSawSoundHit := TPlayableSound.Create();
   FSawSoundSelect := TPlayableSound.Create();
+  FFlameSoundOn := TPlayableSound.Create();
+  FFlameSoundOff := TPlayableSound.Create();
+  FFlameSoundWork := TPlayableSound.Create();
   FJetSoundFly := TPlayableSound.Create();
   FJetSoundOn := TPlayableSound.Create();
   FJetSoundOff := TPlayableSound.Create();
@@ -2126,6 +2071,9 @@ begin
   FSawSoundIdle.SetByName('SOUND_WEAPON_IDLESAW');
   FSawSoundHit.SetByName('SOUND_WEAPON_HITSAW');
   FSawSoundSelect.SetByName('SOUND_WEAPON_SELECTSAW');
+  FFlameSoundOn.SetByName('SOUND_WEAPON_FLAMEON');
+  FFlameSoundOff.SetByName('SOUND_WEAPON_FLAMEOFF');
+  FFlameSoundWork.SetByName('SOUND_WEAPON_FLAMEWORK');
   FJetSoundFly.SetByName('SOUND_PLAYER_JETFLY');
   FJetSoundOn.SetByName('SOUND_PLAYER_JETON');
   FJetSoundOff.SetByName('SOUND_PLAYER_JETOFF');
@@ -2134,11 +2082,13 @@ begin
   FClientID := -1;
   FPing := 0;
   FLoss := 0;
-  FSavedState.WaitRecall := False;
+  FSavedStateNum := -1;
   FShellTimer := -1;
   FFireTime := 0;
   FFirePainTime := 0;
   FFireAttacker := 0;
+  FHandicap := 100;
+  FCorpse := -1;
 
   FActualModelName := 'doomer';
 
@@ -2149,57 +2099,13 @@ begin
   FJustTeleported := False;
   FNetTime := 0;
 
-  //FNetForceWeap := FCurrWeap;
-  FNetForceWeapFIdx := 0;
-  //FCurrFrameIdx := 0;
+  FWaitForFirstSpawn := false;
+  FPunchAnim := TAnimationState.Create(False, 1, 4);
+  FPunchAnim.Disable;
 
   resetWeaponQueue();
-  releaseAllWeaponSwitchKeys();
-end;
-
-
-procedure TPlayer.releaseAllWeaponSwitchKeys ();
-var
-  f: Integer;
-begin
-  for f := 0 to High(weaponSwitchKeyReleased) do weaponSwitchKeyReleased[f] := $03;
 end;
 
-procedure TPlayer.weaponSwitchKeysStateChange (index: Integer; pressed: Boolean);
-begin
-  Inc(index, 2); // -2: prev; -1: next
-  if (index < 0) or (index > High(weaponSwitchKeyReleased)) then exit;
-  weaponSwitchKeyReleased[index] := weaponSwitchKeyReleased[index] or $02;
-  if (pressed) then weaponSwitchKeyReleased[index] := weaponSwitchKeyReleased[index] xor $02;
-end;
-
-function TPlayer.isWeaponSwitchKeyReleased (index: Integer): Boolean;
-begin
-  Inc(index, 2); // -2: prev; -1: next
-  if (index < 0) or (index > High(weaponSwitchKeyReleased)) then
-  begin
-    result := true;
-  end
-  else
-  begin
-    result := (weaponSwitchKeyReleased[index] and $01) <> 0;
-  end;
-end;
-
-procedure TPlayer.weaponSwitchKeysShiftNewStates ();
-var
-  f: Integer;
-begin
-  // copy bit 1 to bit 0
-  for f := 0 to High(weaponSwitchKeyReleased) do
-  begin
-    weaponSwitchKeyReleased[f] :=
-      (weaponSwitchKeyReleased[f] and $02) or
-      ((weaponSwitchKeyReleased[f] shr 1) and $01);
-  end;
-end;
-
-
 procedure TPlayer.positionChanged (); inline;
 begin
 end;
@@ -2220,7 +2126,7 @@ begin
 
   FLastHit := t;
 
-// Íåóÿçâèìîñòü íå ñïàñàåò îò ëîâóøåê:
+// Неуязвимость не спасает от ловушек:
   if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then
   begin
     if not g_Game_IsClient then
@@ -2228,40 +2134,41 @@ begin
       FArmor := 0;
       if t = HIT_TRAP then
       begin
-        // Ëîâóøêà óáèâàåò ñðàçó:
+        // Ловушка убивает сразу:
         FHealth := -100;
         Kill(K_EXTRAHARDKILL, SpawnerUID, t);
       end;
       if t = HIT_SELF then
       begin
-        // Ñàìîóáèéñòâî:
+        // Самоубийство:
         FHealth := 0;
         Kill(K_SIMPLEKILL, SpawnerUID, t);
       end;
     end;
-    // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë
+    // Обнулить действия примочек, чтобы фон пропал
     FMegaRulez[MR_SUIT] := 0;
     FMegaRulez[MR_INVUL] := 0;
     FMegaRulez[MR_INVIS] := 0;
+    FSpawnInvul := 0;
     FBerserk := 0;
   end;
 
-// Íî îò îñòàëüíîãî ñïàñàåò:
+// Но от остального спасает:
   if FMegaRulez[MR_INVUL] >= gTime then
     Exit;
 
-// ×èò-êîä "ÃÎÐÅÖ":
+// Чит-код "ГОРЕЦ":
   if FGodMode then
     Exit;
 
-// Åñëè åñòü óðîí ñâîèì, èëè ðàíèë ñàì ñåáÿ, èëè òåáÿ ðàíèë ïðîòèâíèê:
+// Если есть урон своим, или ранил сам себя, или тебя ранил противник:
   if LongBool(gGameSettings.Options and GAME_OPTION_TEAMDAMAGE) or
      (SpawnerUID = FUID) or
      (not SameTeam(FUID, SpawnerUID)) then
   begin
     FLastSpawnerUID := SpawnerUID;
 
-  // Êðîâü (ïóçûðüêè, åñëè â âîäå):
+  // Кровь (пузырьки, если в воде):
     if gBloodCount > 0 then
     begin
       c := Min(value, 200)*gBloodCount + Random(Min(value, 200) div 2);
@@ -2283,11 +2190,11 @@ begin
                       FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
     end;
 
-  // Áóôåð óðîíà:
+  // Буфер урона:
     if FAlive then
       Inc(FDamageBuffer, value);
 
-  // Âñïûøêà áîëè:
+  // Вспышка боли:
     if gFlash <> 0 then
       FPain := FPain + value;
   end;
@@ -2333,597 +2240,23 @@ begin
   FSawSound.Free();
   FSawSoundIdle.Free();
   FSawSoundHit.Free();
+  FSawSoundSelect.Free();
+  FFlameSoundOn.Free();
+  FFlameSoundOff.Free();
+  FFlameSoundWork.Free();
   FJetSoundFly.Free();
   FJetSoundOn.Free();
   FJetSoundOff.Free();
   FModel.Free();
-  if FPunchAnim <> nil then
-    FPunchAnim.Free();
+  FPunchAnim.Free();
 
   inherited;
 end;
 
-procedure TPlayer.DrawIndicator();
-var
-  indX, indY: Integer;
-  indW, indH: Word;
-  ID: DWORD;
-begin
-  if FAlive then
-    begin
-      indX := FObj.X+FObj.Rect.X;
-      indY := FObj.Y - 12;
-      if g_Texture_Get('TEXTURE_PLAYER_INDICATOR', ID) then
-        begin
-          e_GetTextureSize(ID, @indW, @indH);
-          e_Draw(ID, indX + indW div 2, indY, 0, True, False);
-        end;
-    end;
-  //e_TextureFontPrint(indX, indY, FName, gStdFont); // Shows player name overhead
-end;
-
-procedure TPlayer.DrawBubble();
-var
-  bubX, bubY: Integer;
-  ID: LongWord;
-  Rb, Gb, Bb,
-  Rw, Gw, Bw: SmallInt;
-  Dot: Byte;
-begin
-  bubX := FObj.X+FObj.Rect.X + IfThen(FDirection = TDirection.D_LEFT, -4, 18);
-  bubY := FObj.Y+FObj.Rect.Y - 18;
-  Rb := 64;
-  Gb := 64;
-  Bb := 64;
-  Rw := 240;
-  Gw := 240;
-  Bw := 240;
-  case gChatBubble of
-    1: // simple textual non-bubble
-    begin
-      bubX := FObj.X+FObj.Rect.X - 11;
-      bubY := FObj.Y+FObj.Rect.Y - 17;
-      e_TextureFontPrint(bubX, bubY, '[...]', gStdFont);
-      Exit;
-    end;
-    2: // advanced pixel-perfect bubble
-    begin
-      if FTeam = TEAM_RED then
-        Rb := 255
-      else
-        if FTeam = TEAM_BLUE then
-          Bb := 255;
-    end;
-    3: // colored bubble
-    begin
-      Rb := FModel.Color.R;
-      Gb := FModel.Color.G;
-      Bb := FModel.Color.B;
-      Rw := Min(Rb * 2 + 64, 255);
-      Gw := Min(Gb * 2 + 64, 255);
-      Bw := Min(Bb * 2 + 64, 255);
-      if (Abs(Rw - Rb) < 32)
-      or (Abs(Gw - Gb) < 32)
-      or (Abs(Bw - Bb) < 32) then
-      begin
-        Rb := Max(Rw div 2 - 16, 0);
-        Gb := Max(Gw div 2 - 16, 0);
-        Bb := Max(Bw div 2 - 16, 0);
-      end;
-    end;
-    4: // custom textured bubble
-    begin
-      if g_Texture_Get('TEXTURE_PLAYER_TALKBUBBLE', ID) then
-        if FDirection = TDirection.D_RIGHT then
-          e_Draw(ID, bubX - 6, bubY - 7, 0, True, False)
-        else
-          e_Draw(ID, bubX - 6, bubY - 7, 0, True, False, TMirrorType.Horizontal);
-      Exit;
-    end;
-  end;
-
-  // Outer borders
-  e_DrawQuad(bubX + 1, bubY    , bubX + 18, bubY + 13, Rb, Gb, Bb);
-  e_DrawQuad(bubX    , bubY + 1, bubX + 19, bubY + 12, Rb, Gb, Bb);
-  // Inner box
-  e_DrawFillQuad(bubX + 1, bubY + 1, bubX + 18, bubY + 12, Rw, Gw, Bw, 0);
-
-  // Tail
-  Dot := IfThen(FDirection = TDirection.D_LEFT, 14, 5);
-  e_DrawLine(1, bubX + Dot, bubY + 14, bubX + Dot, bubY + 16, Rb, Gb, Bb);
-  e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 15, Rw, Gw, Bw);
-  e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 2, Dot + 2), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 2, Dot + 2), bubY + 14, Rw, Gw, Bw);
-  e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 13, Rw, Gw, Bw);
-  e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 14, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 16, Rb, Gb, Bb);
-
-  // Dots
-  Dot := 6;
-  e_DrawFillQuad(bubX + Dot,     bubY + 8, bubX + Dot + 1, bubY + 9, Rb, Gb, Bb, 0);
-  e_DrawFillQuad(bubX + Dot + 3, bubY + 8, bubX + Dot + 4, bubY + 9, Rb, Gb, Bb, 0);
-  e_DrawFillQuad(bubX + Dot + 6, bubY + 8, bubX + Dot + 7, bubY + 9, Rb, Gb, Bb, 0);
-end;
-
-procedure TPlayer.Draw();
-var
-  ID: DWORD;
-  w, h: Word;
-  dr: Boolean;
-  Mirror: TMirrorType;
-begin
-  if FAlive then
-  begin
-    if Direction = TDirection.D_RIGHT then
-      Mirror := TMirrorType.None
-    else
-      Mirror := TMirrorType.Horizontal;
-
-    if FPunchAnim <> nil then
-    begin
-      FPunchAnim.Draw(FObj.X+IfThen(Direction = TDirection.D_LEFT, 15-FObj.Rect.X, FObj.Rect.X-15),
-                      FObj.Y+FObj.Rect.Y-11, Mirror);
-      if FPunchAnim.played then
-      begin
-        FPunchAnim.Free;
-        FPunchAnim := nil;
-      end;
-    end;
-
-    if (FMegaRulez[MR_INVUL] > gTime) and (gPlayerDrawn <> Self) then
-      if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
-      begin
-        e_GetTextureSize(ID, @w, @h);
-        if FDirection = TDirection.D_LEFT then
-          e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)+4,
-                     FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+FObj.slopeUpLeft, 0, True, False)
-        else
-          e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)-2,
-                     FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+FObj.slopeUpLeft, 0, True, False);
-      end;
-
-    if FMegaRulez[MR_INVIS] > gTime then
-    begin
-      if (gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or
-         ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM))) then
-      begin
-        if (FMegaRulez[MR_INVIS] - gTime) <= 2100 then
-          dr := not Odd((FMegaRulez[MR_INVIS] - gTime) div 300)
-        else
-          dr := True;
-        if dr then
-          FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft, 200)
-        else
-          FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft);
-      end
-      else
-        FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft, 254);
-    end
-    else
-      FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft);
-  end;
-
-  if g_debug_Frames then
-  begin
-    e_DrawQuad(FObj.X+FObj.Rect.X,
-               FObj.Y+FObj.Rect.Y,
-               FObj.X+FObj.Rect.X+FObj.Rect.Width-1,
-               FObj.Y+FObj.Rect.Y+FObj.Rect.Height-1,
-               0, 255, 0);
-  end;
-
-  if (gChatBubble > 0) and (FKeys[KEY_CHAT].Pressed) and not FGhost then
-    DrawBubble();
- // e_DrawPoint(5, 335, 288, 255, 0, 0); // DL, UR, DL, UR
-  if gAimLine and alive and
-  ((Self = gPlayer1) or (Self = gPlayer2)) then
-    DrawAim();
-end;
-
-
-procedure TPlayer.DrawAim();
-  procedure drawCast (sz: Integer; ax0, ay0, ax1, ay1: Integer);
-  var
-    ex, ey: Integer;
-  begin
-
-{$IFDEF ENABLE_HOLMES}
-    if isValidViewPort and (self = gPlayer1) then
-    begin
-      g_Holmes_plrLaser(ax0, ay0, ax1, ay1);
-    end;
-{$ENDIF}
-
-    e_DrawLine(sz, ax0, ay0, ax1, ay1, 255, 0, 0, 96);
-    if (g_Map_traceToNearestWall(ax0, ay0, ax1, ay1, @ex, @ey) <> nil) then
-    begin
-      e_DrawLine(sz, ax0, ay0, ex, ey, 0, 255, 0, 96);
-    end
-    else
-    begin
-      e_DrawLine(sz, ax0, ay0, ex, ey, 0, 0, 255, 96);
-    end;
-  end;
-
-var
-  wx, wy, xx, yy: Integer;
-  angle: SmallInt;
-  sz, len: Word;
-begin
-  wx := FObj.X + WEAPONPOINT[FDirection].X + IfThen(FDirection = TDirection.D_LEFT, 7, -7);
-  wy := FObj.Y + WEAPONPOINT[FDirection].Y;
-  angle := FAngle;
-  len := 1024;
-  sz := 2;
-  case FCurrWeap of
-    0: begin // Punch
-      len := 12;
-      sz := 4;
-    end;
-    1: begin // Chainsaw
-      len := 24;
-      sz := 6;
-    end;
-    2: begin // Pistol
-      len := 1024;
-      sz := 2;
-      if angle = ANGLE_RIGHTUP then Dec(angle, 2);
-      if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
-      if angle = ANGLE_LEFTUP then Inc(angle, 2);
-      if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
-    end;
-    3: begin // Shotgun
-      len := 1024;
-      sz := 3;
-      if angle = ANGLE_RIGHTUP then Dec(angle, 2);
-      if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
-      if angle = ANGLE_LEFTUP then Inc(angle, 2);
-      if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
-    end;
-    4: begin // Double Shotgun
-      len := 1024;
-      sz := 4;
-      if angle = ANGLE_RIGHTUP then Dec(angle, 2);
-      if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
-      if angle = ANGLE_LEFTUP then Inc(angle, 2);
-      if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
-    end;
-    5: begin // Chaingun
-      len := 1024;
-      sz := 3;
-      if angle = ANGLE_RIGHTUP then Dec(angle, 2);
-      if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
-      if angle = ANGLE_LEFTUP then Inc(angle, 2);
-      if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
-    end;
-    6: begin // Rocket Launcher
-      len := 1024;
-      sz := 7;
-      if angle = ANGLE_RIGHTUP then Inc(angle, 2);
-      if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
-      if angle = ANGLE_LEFTUP then Dec(angle, 2);
-      if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
-    end;
-    7: begin // Plasmagun
-      len := 1024;
-      sz := 5;
-      if angle = ANGLE_RIGHTUP then Inc(angle);
-      if angle = ANGLE_RIGHTDOWN then Inc(angle, 3);
-      if angle = ANGLE_LEFTUP then Dec(angle);
-      if angle = ANGLE_LEFTDOWN then Dec(angle, 3);
-    end;
-    8: begin // BFG
-      len := 1024;
-      sz := 12;
-      if angle = ANGLE_RIGHTUP then Inc(angle, 1);
-      if angle = ANGLE_RIGHTDOWN then Inc(angle, 2);
-      if angle = ANGLE_LEFTUP then Dec(angle, 1);
-      if angle = ANGLE_LEFTDOWN then Dec(angle, 2);
-    end;
-    9: begin // Super Chaingun
-      len := 1024;
-      sz := 4;
-      if angle = ANGLE_RIGHTUP then Dec(angle, 2);
-      if angle = ANGLE_RIGHTDOWN then Inc(angle, 4);
-      if angle = ANGLE_LEFTUP then Inc(angle, 2);
-      if angle = ANGLE_LEFTDOWN then Dec(angle, 4);
-    end;
-  end;
-  xx := Trunc(Cos(-DegToRad(angle)) * len) + wx;
-  yy := Trunc(Sin(-DegToRad(angle)) * len) + wy;
-  {$IF DEFINED(D2F_DEBUG)}
-  drawCast(sz, wx, wy, xx, yy);
-  {$ELSE}
-  e_DrawLine(sz, wx, wy, xx, yy, 255, 0, 0, 96);
-  {$ENDIF}
-end;
-
-procedure TPlayer.DrawGUI();
-var
-  ID: DWORD;
-  X, Y, SY, a, p, m: Integer;
-  tw, th: Word;
-  cw, ch: Byte;
-  s: string;
-  stat: TPlayerStatArray;
-begin
-  X := gPlayerScreenSize.X;
-  SY := gPlayerScreenSize.Y;
-  Y := 0;
-
-  if gShowGoals and (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then
-  begin
-    if gGameSettings.GameMode = GM_CTF then
-      a := 32 + 8
-    else
-      a := 0;
-    if gGameSettings.GameMode = GM_CTF then
-    begin
-      s := 'TEXTURE_PLAYER_REDFLAG';
-      if gFlags[FLAG_RED].State = FLAG_STATE_CAPTURED then
-        s := 'TEXTURE_PLAYER_REDFLAG_S';
-      if gFlags[FLAG_RED].State = FLAG_STATE_DROPPED then
-        s := 'TEXTURE_PLAYER_REDFLAG_D';
-      if g_Texture_Get(s, ID) then
-        e_Draw(ID, X-16-32, 240-72-4, 0, True, False);
-    end;
-
-    s := IntToStr(gTeamStat[TEAM_RED].Goals);
-    e_CharFont_GetSize(gMenuFont, s, tw, th);
-    e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-72-4, s, TEAMCOLOR[TEAM_RED]);
-
-    if gGameSettings.GameMode = GM_CTF then
-    begin
-      s := 'TEXTURE_PLAYER_BLUEFLAG';
-      if gFlags[FLAG_BLUE].State = FLAG_STATE_CAPTURED then
-        s := 'TEXTURE_PLAYER_BLUEFLAG_S';
-      if gFlags[FLAG_BLUE].State = FLAG_STATE_DROPPED then
-        s := 'TEXTURE_PLAYER_BLUEFLAG_D';
-      if g_Texture_Get(s, ID) then
-        e_Draw(ID,  X-16-32, 240-32-4, 0, True, False);
-    end;
-
-    s := IntToStr(gTeamStat[TEAM_BLUE].Goals);
-    e_CharFont_GetSize(gMenuFont, s, tw, th);
-    e_CharFont_PrintEx(gMenuFont, X-16-a-tw, 240-32-4, s, TEAMCOLOR[TEAM_BLUE]);
-  end;
-
-  if g_Texture_Get('TEXTURE_PLAYER_HUDBG', ID) then
-    e_DrawFill(ID, X, 0, 1, (gPlayerScreenSize.Y div 256)+IfThen(gPlayerScreenSize.Y mod 256 > 0, 1, 0),
-               0, False, False);
-
-  if g_Texture_Get('TEXTURE_PLAYER_HUD', ID) then
-    e_Draw(ID, X+2, Y, 0, True, False);
-
-  if gGameSettings.GameType in [GT_CUSTOM, GT_SERVER, GT_CLIENT] then
-  begin
-    if gShowStat then
-    begin
-      s := IntToStr(Frags);
-      e_CharFont_GetSize(gMenuFont, s, tw, th);
-      e_CharFont_PrintEx(gMenuFont, X-16-tw, Y, s, _RGB(255, 0, 0));
-
-      s := '';
-      p := 1;
-      m := 0;
-      stat := g_Player_GetStats();
-      if stat <> nil then
-      begin
-        p := 1;
-
-        for a := 0 to High(stat) do
-          if stat[a].Name <> Name then
-          begin
-            if stat[a].Frags > m then m := stat[a].Frags;
-            if stat[a].Frags > Frags then p := p+1;
-          end;
-      end;
-
-      s := IntToStr(p)+' / '+IntToStr(Length(stat))+' ';
-      if Frags >= m then s := s+'+' else s := s+'-';
-      s := s+IntToStr(Abs(Frags-m));
-
-      e_CharFont_GetSize(gMenuSmallFont, s, tw, th);
-      e_CharFont_PrintEx(gMenuSmallFont, X-16-tw, Y+32, s, _RGB(255, 0, 0));
-    end;
-
-    if gShowLives and (gGameSettings.MaxLives > 0) then
-    begin
-      s := IntToStr(Lives);
-      e_CharFont_GetSize(gMenuFont, s, tw, th);
-      e_CharFont_PrintEx(gMenuFont, X-16-tw, SY-32, s, _RGB(0, 255, 0));
-    end;
-  end;
-
-  e_CharFont_GetSize(gMenuSmallFont, FName, tw, th);
-  e_CharFont_PrintEx(gMenuSmallFont, X+98-(tw div 2), Y+8, FName, _RGB(255, 0, 0));
-
-  if R_BERSERK in FRulez then
-    e_Draw(gItemsTexturesID[ITEM_MEDKIT_BLACK], X+37, Y+45, 0, True, False)
-  else
-    e_Draw(gItemsTexturesID[ITEM_MEDKIT_LARGE], X+37, Y+45, 0, True, False);
-
-  if g_Texture_Get('TEXTURE_PLAYER_ARMORHUD', ID) then
-    e_Draw(ID, X+36, Y+77, 0, True, False);
-
-  s := IntToStr(IfThen(FHealth > 0, FHealth, 0));
-  e_CharFont_GetSize(gMenuFont, s, tw, th);
-  e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+40, s, _RGB(255, 0, 0));
-
-  s := IntToStr(FArmor);
-  e_CharFont_GetSize(gMenuFont, s, tw, th);
-  e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+68, s, _RGB(255, 0, 0));
-
-  s := IntToStr(GetAmmoByWeapon(FCurrWeap));
-
-  case FCurrWeap of
-    WEAPON_KASTET:
-    begin
-      s := '--';
-      ID := gItemsTexturesID[ITEM_WEAPON_KASTET];
-    end;
-    WEAPON_SAW:
-    begin
-      s := '--';
-      ID := gItemsTexturesID[ITEM_WEAPON_SAW];
-    end;
-    WEAPON_PISTOL: ID := gItemsTexturesID[ITEM_WEAPON_PISTOL];
-    WEAPON_CHAINGUN: ID := gItemsTexturesID[ITEM_WEAPON_CHAINGUN];
-    WEAPON_SHOTGUN1: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN1];
-    WEAPON_SHOTGUN2: ID := gItemsTexturesID[ITEM_WEAPON_SHOTGUN2];
-    WEAPON_SUPERPULEMET: ID := gItemsTexturesID[ITEM_WEAPON_SUPERPULEMET];
-    WEAPON_ROCKETLAUNCHER: ID := gItemsTexturesID[ITEM_WEAPON_ROCKETLAUNCHER];
-    WEAPON_PLASMA: ID := gItemsTexturesID[ITEM_WEAPON_PLASMA];
-    WEAPON_BFG: ID := gItemsTexturesID[ITEM_WEAPON_BFG];
-    WEAPON_FLAMETHROWER: ID := gItemsTexturesID[ITEM_WEAPON_FLAMETHROWER];
-  end;
-
-  e_CharFont_GetSize(gMenuFont, s, tw, th);
-  e_CharFont_PrintEx(gMenuFont, X+178-tw, Y+158, s, _RGB(255, 0, 0));
-  e_Draw(ID, X+20, Y+160, 0, True, False);
-
-  if R_KEY_RED in FRulez then
-    e_Draw(gItemsTexturesID[ITEM_KEY_RED], X+78, Y+214, 0, True, False);
-
-  if R_KEY_GREEN in FRulez then
-    e_Draw(gItemsTexturesID[ITEM_KEY_GREEN], X+95, Y+214, 0, True, False);
-
-  if R_KEY_BLUE in FRulez then
-    e_Draw(gItemsTexturesID[ITEM_KEY_BLUE], X+112, Y+214, 0, True, False);
-
-  if FJetFuel > 0 then
-  begin
-    if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then
-      e_Draw(ID, X+2, Y+116, 0, True, False);
-    if g_Texture_Get('TEXTURE_PLAYER_HUDJET', ID) then
-      e_Draw(ID, X+2, Y+126, 0, True, False);
-    e_DrawLine(4, X+16, Y+122, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+122, 0, 0, 196);
-    e_DrawLine(4, X+16, Y+132, X+16+Trunc(168*FJetFuel/JET_MAX), Y+132, 208, 0, 0);
-  end
-  else
-  begin
-    if g_Texture_Get('TEXTURE_PLAYER_HUDAIR', ID) then
-      e_Draw(ID, X+2, Y+124, 0, True, False);
-    e_DrawLine(4, X+16, Y+130, X+16+Trunc(168*IfThen(FAir > 0, FAir, 0)/AIR_MAX), Y+130, 0, 0, 196);
-  end;
-
-  if gShowPing and g_Game_IsClient then
-  begin
-    s := _lc[I_GAME_PING_HUD] + IntToStr(NetPeer.lastRoundTripTime) + _lc[I_NET_SLIST_PING_MS];
-    e_TextureFontPrint(X + 4, Y + 242, s, gStdFont);
-    Y := Y + 16;
-  end;
-
-  if FSpectator then
-  begin
-    e_TextureFontPrint(X + 4, Y + 242, _lc[I_PLAYER_SPECT], gStdFont);
-    e_TextureFontPrint(X + 4, Y + 258, _lc[I_PLAYER_SPECT2], gStdFont);
-    e_TextureFontPrint(X + 4, Y + 274, _lc[I_PLAYER_SPECT1], gStdFont);
-    if FNoRespawn then
-    begin
-      e_TextureFontGetSize(gStdFont, cw, ch);
-      s := _lc[I_PLAYER_SPECT4];
-      e_TextureFontPrintEx(gScreenWidth div 2 - cw*(Length(s) div 2),
-                         gScreenHeight-4-ch, s, gStdFont, 255, 255, 255, 1, True);
-      e_TextureFontPrint(X + 4, Y + 290, _lc[I_PLAYER_SPECT1S], gStdFont);
-    end;
-
-  end;
-end;
-
-procedure TPlayer.DrawRulez();
-var
-  dr: Boolean;
-begin
-  // Ïðè âçÿòèè íåóÿçâèìîñòè ðèñóåòñÿ èíâåðñèîííûé áåëûé ôîí
-  if FMegaRulez[MR_INVUL] >= gTime then
-  begin
-    if (FMegaRulez[MR_INVUL]-gTime) <= 2100 then
-      dr := not Odd((FMegaRulez[MR_INVUL]-gTime) div 300)
-    else
-      dr := True;
-
-    if dr then
-      e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
-                     191, 191, 191, 0, TBlending.Invert);
-  end;
-
-  // Ïðè âçÿòèè çàùèòíîãî êîñòþìà ðèñóåòñÿ çåëåíîâàòûé ôîí
-  if FMegaRulez[MR_SUIT] >= gTime then
-  begin
-    if (FMegaRulez[MR_SUIT]-gTime) <= 2100 then
-      dr := not Odd((FMegaRulez[MR_SUIT]-gTime) div 300)
-    else
-      dr := True;
-
-    if dr then
-      e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
-                     0, 96, 0, 200, TBlending.None);
-  end;
-
-  // Ïðè âçÿòèè áåðñåðêà ðèñóåòñÿ êðàñíîâàòûé ôîí
-  if (FBerserk >= 0) and (LongWord(FBerserk) >= gTime) and (gFlash = 2) then
-  begin
-    e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
-                     255, 0, 0, 200, TBlending.None);
-  end;
-end;
-
-procedure TPlayer.DrawPain();
-var
-  a, h: Integer;
-begin
-  if FPain = 0 then Exit;
-
-  a := FPain;
-
-  if a < 15 then h := 0
-  else if a < 35 then h := 1
-  else if a < 55 then h := 2
-  else if a < 75 then h := 3
-  else if a < 95 then h := 4
-  else h := 5;
-
-  //if a > 255 then a := 255;
-
-  e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255, 0, 0, 255-h*50);
-  //e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 255-min(128, a), 255-a, 255-a, 0, B_FILTER);
-end;
-
-procedure TPlayer.DrawPickup();
-var
-  a, h: Integer;
-begin
-  if FPickup = 0 then Exit;
-
-  a := FPickup;
-
-  if a < 15 then h := 1
-  else if a < 35 then h := 2
-  else if a < 55 then h := 3
-  else if a < 75 then h := 4
-  else h := 5;
-
-  e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 150, 200, 150, 255-h*50);
-end;
-
 procedure TPlayer.DoPunch();
-var
-  id: DWORD;
-  st: String;
 begin
-  if FPunchAnim <> nil then begin
-    FPunchAnim.reset();
-    FPunchAnim.Free;
-    FPunchAnim := nil;
-  end;
-  st := 'FRAMES_PUNCH';
-  if R_BERSERK in FRulez then
-    st := st + '_BERSERK';
-  if FKeys[KEY_UP].Pressed then
-    st := st + '_UP'
-  else if FKeys[KEY_DOWN].Pressed then
-    st := st + '_DN';
-  g_Frames_Get(id, st);
-  FPunchAnim := TAnimation.Create(id, False, 1);
+  FPunchAnim.Reset;
+  FPunchAnim.Enable;
 end;
 
 procedure TPlayer.Fire();
@@ -2933,8 +2266,8 @@ var
   locobj: TObj;
 begin
   if g_Game_IsClient then Exit;
-// FBFGFireCounter - âðåìÿ ïåðåä âûñòðåëîì (äëÿ BFG)
-// FReloading - âðåìÿ ïîñëå âûñòðåëà (äëÿ âñåãî)
+// FBFGFireCounter - время перед выстрелом (для BFG)
+// FReloading - время после выстрела (для всего)
 
   if FSpectator then
   begin
@@ -3110,11 +2443,17 @@ begin
       if FAmmo[A_FUEL] > 0 then
       begin
         g_Weapon_flame(wx, wy, xd, yd, FUID);
+        FlamerOn;
         FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
         Dec(FAmmo[A_FUEL]);
         FFireAngle := FAngle;
         f := True;
         DidFire := True;
+      end
+      else
+      begin
+        FlamerOff;
+        if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID);
       end;
   end;
 
@@ -3158,6 +2497,35 @@ begin
                                PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, True);
 end;
 
+procedure TPlayer.FlamerOn;
+begin
+  FFlameSoundOff.Stop();
+  FFlameSoundOff.SetPosition(0);
+  if FFlaming then
+  begin
+    if (not FFlameSoundOn.IsPlaying()) and (not FFlameSoundWork.IsPlaying()) then
+      FFlameSoundWork.PlayAt(FObj.X, FObj.Y);
+  end
+  else
+  begin
+    FFlameSoundOn.PlayAt(FObj.X, FObj.Y);
+    FFlaming := True;
+  end;
+end;
+
+procedure TPlayer.FlamerOff;
+begin
+  if FFlaming then
+  begin
+    FFlameSoundOn.Stop();
+    FFlameSoundOn.SetPosition(0);
+    FFlameSoundWork.Stop();
+    FFlameSoundWork.SetPosition(0);
+    FFlameSoundOff.PlayAt(FObj.X, FObj.Y);
+    FFlaming := False;
+  end;
+end;
+
 procedure TPlayer.JetpackOn;
 begin
   FJetSoundFly.Stop;
@@ -3175,9 +2543,17 @@ begin
   FJetSoundOff.PlayAt(FObj.X, FObj.Y);
 end;
 
-procedure TPlayer.CatchFire(Attacker: Word);
+procedure TPlayer.CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
 begin
-  FFireTime := 100;
+  if Timeout <= 0 then
+    exit;
+  if (FMegaRulez[MR_SUIT] > gTime) or (FMegaRulez[MR_INVUL] > gTime) then
+    exit; // Не загораемся когда есть защита
+  if g_Obj_CollidePanel(@FObj, 0, 0, PANEL_WATER or PANEL_ACID1 or PANEL_ACID2) then
+    exit; // Не подгораем в воде на всякий случай
+  if FFireTime <= 0 then
+    g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y);
+  FFireTime := Timeout;
   FFireAttacker := Attacker;
   if g_Game_IsNet and g_Game_IsServer then
     MH_SEND_PlayerStats(FUID);
@@ -3187,7 +2563,7 @@ procedure TPlayer.Jump();
 begin
   if gFly or FJetpack then
   begin
-    // Ïîëåò (÷èò-êîä èëè äæåòïàê):
+    // Полет (чит-код или джетпак):
     if FObj.Vel.Y > -VEL_FLY then
       FObj.Vel.Y := FObj.Vel.Y - 3;
     if FJetpack then
@@ -3205,15 +2581,15 @@ begin
     Exit;
   end;
 
-// Íå âêëþ÷àòü äæåòïàê â ðåæèìå ïðîõîæäåíèÿ ñêâîçü ñòåíû
+// Не включать джетпак в режиме прохождения сквозь стены
   if FGhost then
     FCanJetpack := False;
 
-// Ïðûãàåì èëè âñïëûâàåì:
+// Прыгаем или всплываем:
   if (CollideLevel(0, 1) or
       g_Map_CollidePanel(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y+36, PLAYER_RECT.Width,
                        PLAYER_RECT.Height-33, PANEL_STEP, False)
-     ) and (FObj.Accel.Y = 0) then // Íå ïðûãàòü, åñëè åñòü âåðòèêàëüíîå óñêîðåíèå
+     ) and (FObj.Accel.Y = 0) then // Не прыгать, если есть вертикальное ускорение
   begin
     FObj.Vel.Y := -VEL_JUMP;
     FCanJetpack := False;
@@ -3297,7 +2673,7 @@ begin
     if FLives = 0 then FNoRespawn := True;
   end;
 
-// Íîìåð òèïà ñìåðòè:
+// Номер типа смерти:
   a := 1;
   case KillType of
     K_SIMPLEKILL:    a := 1;
@@ -3306,13 +2682,13 @@ begin
     K_FALLKILL:      a := 4;
   end;
 
-// Çâóê ñìåðòè:
+// Звук смерти:
   if not FModel.PlaySound(MODELSOUND_DIE, a, FObj.X, FObj.Y) then
     for i := 1 to 3 do
       if FModel.PlaySound(MODELSOUND_DIE, i, FObj.X, FObj.Y) then
         Break;
 
-// Âðåìÿ ðåñïàóíà:
+// Время респауна:
   if Srv then
     case KillType of
       K_SIMPLEKILL:
@@ -3323,7 +2699,7 @@ begin
         FTime[T_RESPAWN] := gTime + TIME_RESPAWN3;
     end;
 
-// Ïåðåêëþ÷àåì ñîñòîÿíèå:
+// Переключаем состояние:
   case KillType of
     K_SIMPLEKILL:
       SetAction(A_DIE1);
@@ -3331,12 +2707,12 @@ begin
       SetAction(A_DIE2);
   end;
 
-// Ðåàêöèÿ ìîíñòðîâ íà ñìåðòü èãðîêà:
+// Ð ÐµÐ°ÐºÑ\86иÑ\8f Ð¼Ð¾Ð½Ñ\81Ñ\82Ñ\80ов Ð½Ð° Ñ\81меÑ\80Ñ\82Ñ\8c Ð¸Ð³Ñ\80ока:
   if (KillType <> K_FALLKILL) and (Srv) then
     g_Monsters_killedp();
 
   if SpawnerUID = FUID then
-    begin // Ñàìîóáèëñÿ
+    begin // Самоубился
       if Srv and (DoFrags or (gGameSettings.GameMode = GM_TDM)) then
       begin
         Dec(FFrags);
@@ -3346,7 +2722,7 @@ begin
     end
   else
     if g_GetUIDType(SpawnerUID) = UID_PLAYER then
-      begin // Óáèò äðóãèì èãðîêîì
+      begin // Убит другим игроком
         KP := g_Player_Get(SpawnerUID);
         if (KP <> nil) and Srv then
         begin
@@ -3390,7 +2766,7 @@ begin
         end;
       end
     else if g_GetUIDType(SpawnerUID) = UID_MONSTER then
-      begin // Óáèò ìîíñòðîì
+      begin // Убит монстром
         mon := g_Monsters_ByUID(SpawnerUID);
         if mon = nil then
           s := '?'
@@ -3412,7 +2788,7 @@ begin
                           gShowKillMsg);
         end;
       end
-    else // Îñîáûå òèïû ñìåðòè
+    else // Особые типы смерти
       case t of
         HIT_DISCON: ;
         HIT_SELF: g_Console_Add(Format(_lc[I_PLAYER_KILL_SELF], [FName]), True);
@@ -3425,7 +2801,7 @@ begin
 
   if Srv then
   begin
-// Âûáðîñ îðóæèÿ:
+// Выброс оружия:
     for a := WP_FIRST to WP_LAST do
       if FWeapon[a] then
       begin
@@ -3446,16 +2822,17 @@ begin
           PushItem(i);
       end;
 
-// Âûáðîñ ðþêçàêà:
+// Выброс рюкзака:
     if R_ITEM_BACKPACK in FRulez then
       PushItem(ITEM_AMMO_BACKPACK);
 
-// Âûáðîñ ðàêåòíîãî ðàíöà:
+// Выброс ракетного ранца:
     if FJetFuel > 0 then
       PushItem(ITEM_JETPACK);
 
-// Âûáðîñ êëþ÷åé:
-    if not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
+// Выброс ключей:
+    if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) or
+       (not LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS)) then
     begin
       if R_KEY_RED in FRulez then
         PushItem(ITEM_KEY_RED);
@@ -3467,11 +2844,11 @@ begin
         PushItem(ITEM_KEY_BLUE);
     end;
 
-// Âûáðîñ ôëàãà:
-    DropFlag();
+// Выброс флага:
+    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
@@ -3575,7 +2952,7 @@ begin
     if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then
     begin
       if NetMode = NET_SERVER then
-        MH_SEND_GameEvent(NET_EV_LMS_WARMUP, (gLMSRespawnTime - gTime) div 1000)
+        MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime)
       else
         g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
     end;
@@ -3605,28 +2982,32 @@ begin
 end;
 
 procedure TPlayer.MakeBloodSimple(Count: Word);
+  var Blood: TModelBlood;
 begin
+  Blood := SELF.FModel.GetBlood();
   g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
               FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
               Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
-              FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
+              Blood.R, Blood.G, Blood.B, Blood.Kind);
   g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
               FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
               Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
-              FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
+              Blood.R, Blood.G, Blood.B, Blood.Kind);
 end;
 
 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
+  var Blood: TModelBlood;
 begin
+  Blood := SELF.FModel.GetBlood();
   g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
               FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
               Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
-              FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
+              Blood.R, Blood.G, Blood.B, Blood.Kind);
 end;
 
 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
 begin
-  //if g_Game_IsClient then Exit;
+  if g_Game_IsClient then Exit;
   if Weapon > High(FWeapon) then Exit;
   FNextWeap := FNextWeap or (1 shl Weapon);
 end;
@@ -3656,170 +3037,83 @@ function TPlayer.getNextWeaponIndex (): Byte;
 var
   i: Word;
   wantThisWeapon: array[0..64] of Boolean;
-  weaponOrder: array[0..16] of Integer; // value: index in `FWeapon`
-  wwc: Integer;
-  f, dir, cwi, rwidx, curlidx: Integer;
-
-  function real2log (ridx: Integer): Integer;
-  var
-    f: Integer;
-  begin
-    if (ridx >= 0) then
-    begin
-      for f := 0 to High(weaponOrder) do if (weaponOrder[f] = ridx) then begin result := f; exit; end;
-    end;
-    result := -1;
-  end;
-
+  wwc: Integer = 0; //HACK!
+  dir, cwi: Integer;
 begin
   result := 255; // default result: "no switch"
-
   // had weapon cycling on previous frame? remove that flag
   if (FNextWeap and $2000) <> 0 then
   begin
     FNextWeap := FNextWeap and $1FFF;
     FNextWeapDelay := 0;
   end;
-
-  for f := 0 to High(weaponOrder) do weaponOrder[f] := -1;
-
-  // build weapon order (k8: i know, i know, learn how to do constants and such... gtfo, please!)
-  // two knuckles are for "normal" and "berserk" (see `if` below -- it removes the one that is not needed)
-  // priorities:
-  //   bfg, launcher, plasma, flamethrower, ssg, minigun, sg, pistol, berserk, chainsaw, fist
-  weaponOrder[0] := WEAPON_SUPERPULEMET;
-  weaponOrder[1] := WEAPON_BFG;
-  weaponOrder[2] := WEAPON_ROCKETLAUNCHER;
-  weaponOrder[3] := WEAPON_PLASMA;
-  weaponOrder[4] := WEAPON_FLAMETHROWER;
-  weaponOrder[5] := WEAPON_SHOTGUN2;
-  weaponOrder[6] := WEAPON_CHAINGUN;
-  weaponOrder[7] := WEAPON_SHOTGUN1;
-  weaponOrder[8] := WEAPON_PISTOL;
-  weaponOrder[9] := WEAPON_KASTET+666; // berserk fist
-  weaponOrder[10] := WEAPON_SAW;
-  weaponOrder[11] := WEAPON_KASTET; // normal fist
-
-  for f := 0 to High(weaponOrder) do
-  begin
-    if (weaponOrder[f] = WEAPON_KASTET) then
-    begin
-      // normal fist: remove if we have a berserk pack
-      if (R_BERSERK in FRulez) then weaponOrder[f] := -1;
-    end
-    else
-    if (weaponOrder[f] = WEAPON_KASTET+666) then
-    begin
-      // berserk fist: remove if we don't have a berserk pack
-      if (R_BERSERK in FRulez) then weaponOrder[f] := WEAPON_KASTET else weaponOrder[f] := -1;
-    end;
-  end;
-
-  (*
-  WEAPON_KASTET         = 0;
-  WEAPON_SAW            = 1;
-  WEAPON_PISTOL         = 2;
-  WEAPON_SHOTGUN1       = 3;
-  WEAPON_SHOTGUN2       = 4;
-  WEAPON_CHAINGUN       = 5;
-  WEAPON_ROCKETLAUNCHER = 6;
-  WEAPON_PLASMA         = 7;
-  WEAPON_BFG            = 8;
-  WEAPON_SUPERPULEMET   = 9;
-  WEAPON_FLAMETHROWER   = 10;
-  *)
-
   // cycling has priority
   if (FNextWeap and $C000) <> 0 then
   begin
-    if (FNextWeap and $8000) <> 0 then dir := 1 else dir := -1; // should be reversed if we want "priority-driven cycling"
+    if (FNextWeap and $8000) <> 0 then
+      dir := 1
+    else
+      dir := -1;
     FNextWeap := FNextWeap or $2000; // we need this
-    if FNextWeapDelay > 0 then exit; // cooldown time
-    //cwi := real2log(FCurrWeap);
-    //if (cwi < 0) then cwi := 0;
+    if FNextWeapDelay > 0 then
+      exit; // cooldown time
     cwi := FCurrWeap;
     for i := 0 to High(FWeapon) do
     begin
       cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
-      //rwidx := weaponOrder[cwi];
-      rwidx := cwi; // sorry
-      if (rwidx < 0) then continue;
-      if FWeapon[rwidx] then
+      if FWeapon[cwi] then
       begin
-        //e_WriteLog(Format(' SWITCH: cur=%d; new=%d (dir=%d; log=%d)', [FCurrWeap, rwidx, dir, cwi]), TMsgType.Warning);
-        result := Byte(rwidx);
-        //FNextWeapDelay := 10; //k8: not needed anymore
+        //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING);
+        result := Byte(cwi);
+        FNextWeapDelay := WEAPON_DELAY;
         exit;
       end;
     end;
     resetWeaponQueue();
     exit;
   end;
-
   // no cycling
-  for i := 0 to High(wantThisWeapon) do wantThisWeapon[i] := false;
-  wwc := 0;
-
-  curlidx := -1; // start from a weapon with a highest priority (-1, 'cause loop will immediately increment this index)
-
+  for i := 0 to High(wantThisWeapon) do
+    wantThisWeapon[i] := false;
   for i := 0 to High(FWeapon) do
-  begin
     if (FNextWeap and (1 shl i)) <> 0 then
     begin
-      cwi := real2log(i);
-      if (cwi >= 0) then
-      begin
-        wantThisWeapon[cwi] := true;
-        Inc(wwc);
-        // if we hit currently selected weapon, start seachring from it, so we'll get "next weaker" weapon
-        if (i = FCurrWeap) then curlidx := cwi; // compare real, start from logical
-      end;
+      wantThisWeapon[i] := true;
+      Inc(wwc);
     end;
-  end;
-
+  // exclude currently selected weapon from the set
+  wantThisWeapon[FCurrWeap] := false;
   // slow down alterations a little
-  if (wwc > 1) then
+  if wwc > 1 then
   begin
-    // more than one weapon requested, assume "alteration", and check alteration delay
+    //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
+    // more than one weapon requested, assume "alteration" and check alteration delay
     if FNextWeapDelay > 0 then
     begin
-      //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), TMsgType.Warning);
       FNextWeap := 0;
       exit;
     end; // yeah
   end;
-
   // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
   // but clear all counters if no weapon should be switched
-  if (wwc < 1) then
+  if wwc < 1 then
   begin
     resetWeaponQueue();
     exit;
   end;
-
-  //e_WriteLog(Format('*** wwc=%d; currweap=%d; logweap=%d', [wwc, FCurrWeap, curlidx]), TMsgType.Warning);
-
-  // find next weapon to switch onto
-  cwi := curlidx;
-  for i := 0 to High(weaponOrder) do
+  //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
+  // try weapons in descending order
+  for i := High(FWeapon) downto 0 do
   begin
-    cwi := (cwi+length(weaponOrder)+1) mod length(weaponOrder);
-    if (cwi = curlidx) then continue; // skip current weapon
-    if not wantThisWeapon[cwi] then continue;
-    rwidx := weaponOrder[cwi];
-    if (rwidx < 0) then continue;
-    //e_WriteLog(Format('  trying logical %d (real %d); has=%d, hasammo=%d', [cwi, rwidx, Integer(FWeapon[rwidx]), Integer(hasAmmoForWeapon(rwidx))]), TMsgType.Warning);
-    if FWeapon[rwidx] and ((wwc = 1) or hasAmmoForWeapon(rwidx)) then
+    if wantThisWeapon[i] and FWeapon[i] and ((wwc = 1) or hasAmmoForWeapon(i)) then
     begin
-      //e_WriteLog('    I FOUND HER!', TMsgType.Warning);
       // i found her!
-      result := Byte(rwidx);
+      result := Byte(i);
       resetWeaponQueue();
-      //FNextWeapDelay := 10; // anyway, 'cause why not; k8: not needed anymore
+      FNextWeapDelay := WEAPON_DELAY * 2; // anyway, 'cause why not
       exit;
     end;
   end;
-
   // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
   resetWeaponQueue();
 end;
@@ -3845,9 +3139,7 @@ var
 begin
   //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
   //FNextWeap := FNextWeap and $1FFF;
-  //HACK: alteration delay will be reset when player released any weapon switch key
-  FNextWeapDelay := 0; //k8: just in case
-  //if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
+  if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
 
   if not switchAllowed then
   begin
@@ -3861,55 +3153,42 @@ begin
   if nw > High(FWeapon) then
   begin
     // don't forget to reset queue here!
-    //e_WriteLog(Format(' RealizeCurrentWeapon: WUTAFUUUU?! (%d)', [nw]), TMsgType.Warning);
+    //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
     resetWeaponQueue();
     exit;
   end;
 
   if FWeapon[nw] then
   begin
-    //k8: emulate this on client immediately, or wait for server confirmation?
-    {
-    if g_Game_IsClient then
-    begin
-      FNetForceWeap := nw;
-      //FNetForceWeapFIdx := FCurrFrameIdx+5; // force for ~5 frames
-      FNetForceWeapFIdx := gTime+5; // force for ~5 frames
-      writeln('NETWEAPONSWITCH: fw=', NetForceWeap, '; fwfidx=', NetForceWeapFIdx, '; cfrm=', gTime);
-    end;
-    }
-    FNetForceWeapFIdx := gTime+5; // force for ~5 frames
     FCurrWeap := nw;
-    if nw = WEAPON_SAW then FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
-    FModel.SetWeapon(nw);
     FTime[T_SWITCH] := gTime+156;
+    if FCurrWeap = WEAPON_SAW then FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
+    FModel.SetWeapon(FCurrWeap);
     if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
-    //if g_Game_IsNet and g_Game_IsClient then
   end;
 end;
 
 procedure TPlayer.NextWeapon();
 begin
-  //if g_Game_IsClient then Exit;
+  if g_Game_IsClient then Exit;
   FNextWeap := $8000;
 end;
 
 procedure TPlayer.PrevWeapon();
 begin
-  //if g_Game_IsClient then Exit;
+  if g_Game_IsClient then Exit;
   FNextWeap := $4000;
 end;
 
-// used by network layer
 procedure TPlayer.SetWeapon(W: Byte);
 begin
   if FCurrWeap <> W then
-    if (W = WEAPON_SAW) then
+    if W = WEAPON_SAW then
       FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
 
   FCurrWeap := W;
   FModel.SetWeapon(CurrWeap);
-  //if g_Game_IsClient then resetWeaponQueue();
+  resetWeaponQueue();
 end;
 
 function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean;
@@ -3929,15 +3208,15 @@ begin
   Result := False;
   if g_Game_IsClient then Exit;
 
-  // a = true - ìåñòî ñïàâíà ïðåäìåòà:
+  // 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 then
+      if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
       begin
-        IncMax(FHealth, 10, PLAYER_HP_SOFT);
+        if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
         Result := True;
         remove := True;
         FFireTime := 0;
@@ -3945,9 +3224,9 @@ begin
       end;
 
     ITEM_MEDKIT_LARGE:
-      if FHealth < PLAYER_HP_SOFT then
+      if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
       begin
-        IncMax(FHealth, 25, PLAYER_HP_SOFT);
+        if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
         Result := True;
         remove := True;
         FFireTime := 0;
@@ -3973,9 +3252,9 @@ begin
       end;
 
     ITEM_SPHERE_BLUE:
-      if FHealth < PLAYER_HP_LIMIT then
+      if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
       begin
-        IncMax(FHealth, 100, PLAYER_HP_LIMIT);
+        if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
         Result := True;
         remove := True;
         FFireTime := 0;
@@ -3983,7 +3262,7 @@ begin
       end;
 
     ITEM_SPHERE_WHITE:
-      if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
+      if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) or (FFireTime > 0) then
       begin
         if FHealth < PLAYER_HP_LIMIT then
           FHealth := PLAYER_HP_LIMIT;
@@ -4007,7 +3286,7 @@ begin
     ITEM_WEAPON_SHOTGUN1:
       if (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or not FWeapon[WEAPON_SHOTGUN1] then
       begin
-        // Íóæíî, ÷òîáû íå âçÿòü âñå ïóëè ñðàçó:
+        // Нужно, чтобы не взять все пули сразу:
         if a and FWeapon[WEAPON_SHOTGUN1] then Exit;
 
         IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
@@ -4188,7 +3467,7 @@ begin
             (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
             (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
             (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
-            (FMaxAmmo[A_FUEL] < AmmoLimits[1, A_FUEL]) then
+            (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) then
       begin
         FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
         FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
@@ -4204,6 +3483,8 @@ begin
           IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
         if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then
           IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
+        if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
+          IncMax(FAmmo[A_FUEL], 50, FMaxAmmo[A_FUEL]);
 
         FRulez := FRulez + [R_ITEM_BACKPACK];
         Result := True;
@@ -4268,7 +3549,6 @@ begin
           if allowBerserkSwitching then
           begin
             FCurrWeap := WEAPON_KASTET;
-            //FNetForceWeap := FCurrWeap;
             resetWeaponQueue();
             FModel.SetWeapon(WEAPON_KASTET);
           end;
@@ -4281,11 +3561,10 @@ begin
           Result := True;
           remove := True;
           FFireTime := 0;
-          //k8:do we need it? if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID);
         end;
-        if FHealth < PLAYER_HP_SOFT then
+        if (FHealth < PLAYER_HP_SOFT) or (FFireTime > 0) then
         begin
-          FHealth := PLAYER_HP_SOFT;
+          if FHealth < PLAYER_HP_SOFT then FHealth := PLAYER_HP_SOFT;
           FBerserk := gTime+30000;
           Result := True;
           remove := True;
@@ -4297,15 +3576,16 @@ begin
       if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
       begin
         FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
+        FSpawnInvul := 0;
         Result := True;
         remove := True;
         if gFlash = 2 then Inc(FPickup, 5);
       end;
 
     ITEM_BOTTLE:
-      if FHealth < PLAYER_HP_LIMIT then
+      if (FHealth < PLAYER_HP_LIMIT) or (FFireTime > 0) then
       begin
-        IncMax(FHealth, 4, PLAYER_HP_LIMIT);
+        if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
         Result := True;
         remove := True;
         FFireTime := 0;
@@ -4348,7 +3628,7 @@ begin
   //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
   if FIamBot then
   begin
-  // Áðîñèòü ôëàã òîâàðèùó:
+  // Бросить флаг товарищу:
     if gGameSettings.GameMode = GM_CTF then
       DropFlag();
   end;
@@ -4382,9 +3662,9 @@ begin
   FMonsterKills := 0;
   FDeath := 0;
   FSecrets := 0;
-  //FCurrFrameIdx := 0;
-  //FNetForceWeap := FCurrWeap;
-  FNetForceWeapFIdx := 0;
+  FSpawnInvul := 0;
+  FCorpse := -1;
+  FReady := False;
   if FNoRespawn then
   begin
     FSpectator := False;
@@ -4403,6 +3683,8 @@ begin
   ReleaseKeys();
 
   FDamageBuffer := 0;
+  FSlopeOld := 0;
+  FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
   FShellTimer := -1;
@@ -4410,7 +3692,6 @@ begin
   FLastHit := 0;
   FLastFrag := 0;
   FComboEvnt := -1;
-  FNetForceWeapFIdx := 0;
 
   SetFlag(FLAG_NONE);
   SetAction(A_STAND, True);
@@ -4421,164 +3702,79 @@ var
   c: Byte;
 begin
   Result := 255;
-  // Íà áóäóùåå: FSpawn - èãðîê óæå èãðàë è ïåðåðîæäàåòñÿ
+  // На будущее: FSpawn - игрок уже играл и перерождается
 
-  // Îäèíî÷íàÿ èãðà/êîîïåðàòèâ
+  // Одиночная игра/кооператив
   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);
 var
   RespawnPoint: TRespawnPoint;
   a, b, c: Byte;
-  Anim: TAnimation;
-  ID: DWORD;
 begin
+  FSlopeOld := 0;
+  FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
   FShellTimer := -1;
   FPain := 0;
   FLastHit := 0;
-  //FNetForceWeap := FCurrWeap;
-  FNetForceWeapFIdx := 0;
+  FSpawnInvul := 0;
+  FCorpse := -1;
 
   if not g_Game_IsServer then
     Exit;
@@ -4595,11 +3791,11 @@ begin
   // if server changes MaxLives we gotta be ready
   if gGameSettings.MaxLives = 0 then FNoRespawn := False;
 
-// Åùå íåëüçÿ âîçðîäèòüñÿ:
+// Еще нельзя возродиться:
   if FTime[T_RESPAWN] > gTime then
     Exit;
 
-// Ïðîñðàë âñå æèçíè:
+// Просрал все жизни:
   if FNoRespawn then
   begin
     if not FSpectator then Spectate(True);
@@ -4608,26 +3804,26 @@ begin
   end;
 
   if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.GameMode <> GM_COOP) then
-    begin // "Ñâîÿ èãðà"
-    // Áåðñåðê íå ñîõðàíÿåòñÿ ìåæäó óðîâíÿìè:
+    begin // "Своя игра"
+    // Берсерк не сохраняется между уровнями:
       FRulez := FRulez-[R_BERSERK];
     end
-  else // "Îäèíî÷íàÿ èãðà"/"Êîîï"
+  else // "Одиночная игра"/"Кооп"
     begin
-    // Áåðñåðê è êëþ÷è íå ñîõðàíÿþòñÿ ìåæäó óðîâíÿìè:
+    // Берсерк и ключи не сохраняются между уровнями:
       FRulez := FRulez-[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE, R_BERSERK];
     end;
 
-// Ïîëó÷àåì òî÷êó ñïàóíà èãðîêà:
+// Получаем точку спауна игрока:
   c := GetRespawnPoint();
 
   ReleaseKeys();
   SetFlag(FLAG_NONE);
 
-// Âîñêðåøåíèå áåç îðóæèÿ:
+// Воскрешение без оружия:
   if not FAlive then
   begin
-    FHealth := PLAYER_HP_SOFT;
+    FHealth := Round(PLAYER_HP_SOFT * (FHandicap / 100));
     FArmor := 0;
     FAlive := True;
     FAir := AIR_DEF;
@@ -4642,7 +3838,6 @@ begin
     FWeapon[WEAPON_PISTOL] := True;
     FWeapon[WEAPON_KASTET] := True;
     FCurrWeap := WEAPON_PISTOL;
-    //FNetForceWeap := FCurrWeap;
     resetWeaponQueue();
 
     FModel.SetWeapon(FCurrWeap);
@@ -4658,22 +3853,25 @@ begin
     FMaxAmmo[A_CELLS] := AmmoLimits[0, A_CELLS];
     FMaxAmmo[A_FUEL] := AmmoLimits[0, A_FUEL];
 
-    if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
+    if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
+       LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS) then
       FRulez := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]
     else
       FRulez := [];
   end;
 
-// Ïîëó÷àåì êîîðäèíàòû òî÷êè âîçðîæäåíèÿ:
+// Получаем координаты точки возрождения:
   if not g_Map_GetPoint(c, RespawnPoint) then
   begin
     g_FatalError(_lc[I_GAME_ERROR_GET_SPAWN]);
     Exit;
   end;
 
-// Óñòàíîâêà êîîðäèíàò è ñáðîñ âñåõ ïàðàìåòðîâ:
+// Установка координат и сброс всех параметров:
   FObj.X := RespawnPoint.X-PLAYER_RECT.X;
   FObj.Y := RespawnPoint.Y-PLAYER_RECT.Y;
+  FObj.oldX := FObj.X; // don't interpolate after respawn
+  FObj.oldY := FObj.Y;
   FObj.Vel.X := 0;
   FObj.Vel.Y := 0;
   FObj.Accel.X := 0;
@@ -4694,22 +3892,28 @@ begin
   for a := Low(FMegaRulez) to High(FMegaRulez) do
     FMegaRulez[a] := 0;
 
+// Respawn invulnerability
+  if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.SpawnInvul > 0) then
+  begin
+    FMegaRulez[MR_INVUL] := gTime + gGameSettings.SpawnInvul * 1000;
+    FSpawnInvul := FMegaRulez[MR_INVUL];
+  end;
+
   FDamageBuffer := 0;
   FJetpack := False;
   FCanJetpack := False;
+  FFlaming := False;
   FFireTime := 0;
   FFirePainTime := 0;
   FFireAttacker := 0;
 
-// Àíèìàöèÿ âîçðîæäåíèÿ:
+// Анимация возрождения:
   if (not gLoadGameMode) and (not Silent) then
-    if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
-    begin
-      Anim := TAnimation.Create(ID, False, 3);
-      g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
-                     FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
-      Anim.Free();
-    end;
+    r_GFX_OnceAnim(
+      R_GFX_TELEPORT_FAST,
+      FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
+      FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32
+    );
 
   FSpectator := False;
   FGhost := False;
@@ -4717,13 +3921,11 @@ begin
   FSpectatePlayer := -1;
   FSpawned := True;
 
-  if (gPlayer1 = nil) and (gLMSPID1 = FUID) then
+  if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then
     gPlayer1 := self;
-  if (gPlayer2 = nil) and (gLMSPID2 = FUID) then
+  if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then
     gPlayer2 := self;
 
-  //FNetForceWeap := FCurrWeap;
-
   if g_Game_IsNet then
   begin
     MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
@@ -4753,17 +3955,18 @@ begin
   FPhysics := False;
   FWantsInGame := False;
   FSpawned := False;
+  FCorpse := -1;
 
   if FNoRespawn then
   begin
     if Self = gPlayer1 then
     begin
-      gLMSPID1 := FUID;
+      gSpectLatchPID1 := FUID;
       gPlayer1 := nil;
-    end;
-    if Self = gPlayer2 then
+    end
+    else if Self = gPlayer2 then
     begin
-      gLMSPID2 := FUID;
+      gSpectLatchPID2 := FUID;
       gPlayer2 := nil;
     end;
   end;
@@ -4796,7 +3999,7 @@ begin
   if MAX_RUNVEL > 8 then
     FlySmoke();
 
-// Áåæèì:
+// Бежим:
   if Direction = TDirection.D_LEFT then
     begin
       if FObj.Vel.X > -MAX_RUNVEL then
@@ -4806,7 +4009,7 @@ begin
     if FObj.Vel.X < MAX_RUNVEL then
       FObj.Vel.X := FObj.Vel.X + (MAX_RUNVEL shr 3);
 
-// Âîçìîæíî, ïèíàåì êóñêè:
+// Возможно, пинаем куски:
   if (FObj.Vel.X <> 0) and (gGibs <> nil) then
   begin
     b := Abs(FObj.Vel.X);
@@ -4817,14 +4020,14 @@ begin
          g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
                        FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
       begin
-        // Ïèíàåì êóñêè
+        // Пинаем куски
         if FObj.Vel.X < 0 then
         begin
-          g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî
+          g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // налево
         end
         else
         begin
-          g_Obj_PushA(@gGibs[a].Obj, b, Random(61));    // íàïðàâî
+          g_Obj_PushA(@gGibs[a].Obj, b, Random(61));    // направо
         end;
         gGibs[a].positionChanged(); // this updates spatial accelerators
       end;
@@ -4890,9 +4093,6 @@ begin
 end;
 
 function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
-var
-  Anim: TAnimation;
-  ID: DWORD;
 begin
   Result := False;
 
@@ -4906,17 +4106,14 @@ begin
 
   FJustTeleported := True;
 
-  Anim := nil;
   if not silent then
   begin
-    if g_Frames_Get(ID, 'FRAMES_TELEPORT') then
-    begin
-      Anim := TAnimation.Create(ID, False, 3);
-    end;
-
     g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj.X, FObj.Y);
-    g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
-                   FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
+    r_GFX_OnceAnim(
+      R_GFX_TELEPORT_FAST,
+      FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
+      FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32
+    );
     if g_Game_IsServer and g_Game_IsNet then
       MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
                      FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 1,
@@ -4925,6 +4122,8 @@ begin
 
   FObj.X := X-PLAYER_RECT.X;
   FObj.Y := Y-PLAYER_RECT.Y;
+  FObj.oldX := FObj.X; // don't interpolate after respawn
+  FObj.oldY := FObj.Y;
   if FAlive and FGhost then
   begin
     FXTo := FObj.X;
@@ -4946,7 +4145,7 @@ begin
       end
       else
         if dir = 3 then
-        begin // îáðàòíîå
+        begin // обратное
           if FDirection = TDirection.D_RIGHT then
           begin
             SetDirection(TDirection.D_LEFT);
@@ -4960,12 +4159,13 @@ begin
         end;
   end;
 
-  if not silent and (Anim <> nil) then
+  if not silent then
   begin
-    g_GFX_OnceAnim(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
-                   FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, Anim);
-    Anim.Free();
-
+    r_GFX_OnceAnim(
+      R_GFX_TELEPORT_FAST,
+      FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
+      FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32
+    );
     if g_Game_IsServer and g_Game_IsNet then
       MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
                      FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 0,
@@ -4983,11 +4183,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
@@ -4997,16 +4198,34 @@ 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;
+end;
+
 procedure TPlayer.Update();
 var
   b: Byte;
@@ -5016,8 +4235,6 @@ var
   AnyServer: Boolean;
   SetSpect: Boolean;
 begin
-  //Inc(FCurrFrameIdx);
-
   NetServer := g_Game_IsNet and g_Game_IsServer;
   AnyServer := g_Game_IsServer;
 
@@ -5028,7 +4245,6 @@ begin
       DoLerp(4);
 
   if NetServer then
-  begin
     if FClientID >= 0 then
     begin
       FPing := NetClients[FClientID].Peer^.lastRoundTripTime;
@@ -5041,10 +4257,11 @@ begin
       FPing := 0;
       FLoss := 0;
     end;
-  end;
 
-  if FAlive and (FPunchAnim <> nil) then
-    FPunchAnim.Update();
+  if FAlive then
+    FPunchAnim.Update;
+  if FPunchAnim.played then
+    FPunchAnim.Disable;
 
   if FAlive and (gFly or FJetpack) then
     FlySmoke();
@@ -5072,8 +4289,8 @@ begin
   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 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
@@ -5087,8 +4304,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;
 
@@ -5104,7 +4320,15 @@ begin
     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();
+    if FKeys[KEY_FIRE].Pressed and AnyServer then Fire()
+    else
+    begin
+      if AnyServer then
+      begin
+        FlamerOff;
+        if NetServer then MH_SEND_PlayerStats(FUID);
+      end;
+    end;
     if FKeys[KEY_OPEN].Pressed and AnyServer then Use();
     if FKeys[KEY_JUMP].Pressed then Jump()
     else
@@ -5214,8 +4438,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
@@ -5237,7 +4460,7 @@ begin
                                  PANEL_BLOCKMON, True);
   headwater := HeadInLiquid(0, 0);
 
-// Ñîïðîòèâëåíèå âîçäóõà:
+// Сопротивление воздуха:
   if (not FAlive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then
     if FObj.Vel.X <> 0 then
       FObj.Vel.X := z_dec(FObj.Vel.X, 1);
@@ -5248,7 +4471,7 @@ begin
 
   if FAlive and (FObj.Y > Integer(gMapInfo.Height)+128) and AnyServer then
   begin
-    // Îáíóëèòü äåéñòâèÿ ïðèìî÷åê, ÷òîáû ôîí ïðîïàë
+    // Обнулить действия примочек, чтобы фон пропал
     FMegaRulez[MR_SUIT] := 0;
     FMegaRulez[MR_INVUL] := 0;
     FMegaRulez[MR_INVIS] := 0;
@@ -5365,11 +4588,13 @@ begin
         if FFirePainTime <= 0 then
         begin
           if g_Game_IsServer then
-            Damage(5, FFireAttacker, 0, 0, HIT_FLAME);
-          FFirePainTime := 18;
+            Damage(2, FFireAttacker, 0, 0, HIT_FLAME);
+          FFirePainTime := 12 - FFireTime div 12;
         end;
         FFirePainTime := FFirePainTime - 1;
         FFireTime := FFireTime - 1;
+        if ((FFireTime mod 33) = 0) and (FMegaRulez[MR_INVUL] < gTime) then
+          FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
         if (FFireTime = 0) and g_Game_IsNet and g_Game_IsServer then
           MH_SEND_PlayerStats(FUID);
       end;
@@ -5401,7 +4626,7 @@ begin
             else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
               else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
 
-      if FAlive then
+      if FAlive and ((FLastHit <> HIT_FLAME) or (FFireTime <= 0)) then
       begin
         if FDamageBuffer <= 20 then FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y)
           else if FDamageBuffer <= 55 then FModel.PlaySound(MODELSOUND_PAIN, 2, FObj.X, FObj.Y)
@@ -5418,14 +4643,13 @@ begin
   if (FActionAnim = A_PAIN) and (FModel.Animation <> A_PAIN) then
   begin
     FModel.ChangeAnimation(FActionAnim, FActionForce);
-    FModel.GetCurrentAnimation.MinLength := i;
-    FModel.GetCurrentAnimationMask.MinLength := i;
+    FModel.AnimState.MinLength := i;
   end else FModel.ChangeAnimation(FActionAnim, FActionForce and (FModel.Animation <> A_STAND));
 
-  if (FModel.GetCurrentAnimation.Played or ((not FActionChanged) and (FModel.Animation = A_WALK)))
+  if (FModel.AnimState.Played or ((not FActionChanged) and (FModel.Animation = A_WALK)))
   then SetAction(A_STAND, True);
 
-  if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.Fire) then FModel.Update;
+  if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.GetFire()) then FModel.Update;
 
   for b := Low(FKeys) to High(FKeys) do
     if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time);
@@ -5672,6 +4896,7 @@ begin
     WEAPON_FLAMETHROWER:
     begin
       g_Weapon_flame(wx, wy, xd, yd, FUID, WID);
+      FlamerOn;
       FFireAngle := FAngle;
       f := True;
     end;
@@ -5694,16 +4919,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
@@ -5728,7 +4957,7 @@ end;
 function TPlayer.GetFlag(Flag: Byte): Boolean;
 var
   s, ts: String;
-  evtype: Byte;
+  evtype, a: Byte;
 begin
   Result := False;
 
@@ -5737,7 +4966,7 @@ begin
 
   if not g_Game_IsServer then Exit;
 
-// Ïðèíåñ ÷óæîé ôëàã íà ñâîþ áàçó:
+// Принес чужой флаг на свою базу:
   if (Flag = FTeam) and
      (gFlags[Flag].State = FLAG_STATE_NORMAL) and
      (FFlag <> FLAG_NONE) then
@@ -5756,6 +4985,16 @@ begin
     g_Map_ResetFlag(FFlag);
     g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
 
+    if ((Self = gPlayer1) or (Self = gPlayer2)
+    or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
+    or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
+      a := 0
+    else
+      a := 1;
+
+    if not sound_cap_flag[a].IsPlaying() then
+      sound_cap_flag[a].Play();
+
     gTeamStat[FTeam].Goals := gTeamStat[FTeam].Goals + 1;
 
     Result := True;
@@ -5770,7 +5009,7 @@ begin
     Exit;
   end;
 
-// Ïîäîáðàë ñâîé ôëàã - âåðíóë åãî íà áàçó:
+// Подобрал свой флаг - вернул его на базу:
   if (Flag = FTeam) and
      (gFlags[Flag].State = FLAG_STATE_DROPPED) then
   begin
@@ -5787,6 +5026,16 @@ begin
     g_Map_ResetFlag(Flag);
     g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
 
+    if ((Self = gPlayer1) or (Self = gPlayer2)
+    or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
+    or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
+      a := 0
+    else
+      a := 1;
+
+    if not sound_ret_flag[a].IsPlaying() then
+      sound_ret_flag[a].Play();
+
     Result := True;
     if g_Game_IsNet then
     begin
@@ -5796,7 +5045,7 @@ begin
     Exit;
   end;
 
-// Ïîäîáðàë ÷óæîé ôëàã:
+// Подобрал чужой флаг:
   if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then
   begin
     SetFlag(Flag);
@@ -5814,6 +5063,16 @@ begin
 
     gFlags[Flag].State := FLAG_STATE_CAPTURED;
 
+    if ((Self = gPlayer1) or (Self = gPlayer2)
+    or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
+    or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
+      a := 0
+    else
+      a := 1;
+
+    if not sound_get_flag[a].IsPlaying() then
+      sound_get_flag[a].Play();
+
     Result := True;
     if g_Game_IsNet then
     begin
@@ -5830,9 +5089,10 @@ begin
     FModel.SetFlag(FFlag);
 end;
 
-function TPlayer.DropFlag(): Boolean;
+function TPlayer.DropFlag(Silent: Boolean = True): Boolean;
 var
   s: String;
+  a: Byte;
 begin
   Result := False;
   if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then
@@ -5857,6 +5117,16 @@ begin
     g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [FName, s]), True);
     g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
 
+    if ((Self = gPlayer1) or (Self = gPlayer2)
+    or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
+    or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
+      a := 0
+    else
+      a := 1;
+
+    if (not Silent) and (not sound_lost_flag[a].IsPlaying()) then
+      sound_lost_flag[a].Play();
+
     if g_Game_IsNet then
       MH_SEND_FlagEvent(FLAG_STATE_DROPPED, Flag, FUID, False);
   end;
@@ -5866,6 +5136,11 @@ end;
 
 procedure TPlayer.GetSecret();
 begin
+  if (self = gPlayer1) or (self = gPlayer2) then
+  begin
+    g_Console_Add(Format(_lc[I_PLAYER_SECRET], [FName]), True);
+    g_Sound_PlayEx('SOUND_GAME_SECRET');
+  end;
   Inc(FSecrets);
 end;
 
@@ -5893,18 +5168,6 @@ begin
   end;
 end;
 
-procedure TPlayer.ReleaseKeysNoWeapon();
-var
-  a: Integer;
-begin
-  for a := Low(FKeys) to High(FKeys) do
-  begin
-    if (a = KEY_PREVWEAPON) or (a = KEY_NEXTWEAPON) then continue;
-    FKeys[a].Pressed := False;
-    FKeys[a].Time := 0;
-  end;
-end;
-
 procedure TPlayer.OnDamage(Angle: SmallInt);
 begin
 end;
@@ -5919,46 +5182,66 @@ end;
 procedure TPlayer.RememberState();
 var
   i: Integer;
-begin
-  FSavedState.Health := FHealth;
-  FSavedState.Armor := FArmor;
-  FSavedState.Air := FAir;
-  FSavedState.JetFuel := FJetFuel;
-  FSavedState.CurrWeap := FCurrWeap;
-  FSavedState.NextWeap := FNextWeap;
-  FSavedState.NextWeapDelay := FNextWeapDelay;
-
-  for i := 0 to 3 do
-    FSavedState.Ammo[i] := FAmmo[i];
-  for i := 0 to 3 do
-    FSavedState.MaxAmmo[i] := FMaxAmmo[i];
+  SavedState: TPlayerSavedState;
+begin
+  SavedState.Health := FHealth;
+  SavedState.Armor := FArmor;
+  SavedState.Air := FAir;
+  SavedState.JetFuel := FJetFuel;
+  SavedState.CurrWeap := FCurrWeap;
+  SavedState.NextWeap := FNextWeap;
+  SavedState.NextWeapDelay := FNextWeapDelay;
+  for i := Low(FWeapon) to High(FWeapon) do
+    SavedState.Weapon[i] := FWeapon[i];
+  for i := Low(FAmmo) to High(FAmmo) do
+    SavedState.Ammo[i] := FAmmo[i];
+  for i := Low(FMaxAmmo) to High(FMaxAmmo) do
+    SavedState.MaxAmmo[i] := FMaxAmmo[i];
+  SavedState.Rulez := FRulez - [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
+
+  FSavedStateNum := -1;
+  for i := Low(SavedStates) to High(SavedStates) do
+    if not SavedStates[i].Used then
+    begin
+      FSavedStateNum := i;
+      break;
+    end;
+  if FSavedStateNum < 0 then
+  begin
+    SetLength(SavedStates, Length(SavedStates) + 1);
+    FSavedStateNum := High(SavedStates);
+  end;
 
-  FSavedState.Rulez := FRulez;
-  FSavedState.WaitRecall := True;
+  SavedState.Used := True;
+  SavedStates[FSavedStateNum] := SavedState;
 end;
 
 procedure TPlayer.RecallState();
 var
   i: Integer;
+  SavedState: TPlayerSavedState;
 begin
-  if not FSavedState.WaitRecall then Exit;
-
-  FHealth := FSavedState.Health;
-  FArmor := FSavedState.Armor;
-  FAir := FSavedState.Air;
-  FJetFuel := FSavedState.JetFuel;
-  FCurrWeap := FSavedState.CurrWeap;
-  //FNetForceWeap := FCurrWeap;
-  FNextWeap := FSavedState.NextWeap;
-  FNextWeapDelay := FSavedState.NextWeapDelay;
-
-  for i := 0 to 3 do
-    FAmmo[i] := FSavedState.Ammo[i];
-  for i := 0 to 3 do
-    FMaxAmmo[i] := FSavedState.MaxAmmo[i];
+  if(FSavedStateNum < 0) or (FSavedStateNum > High(SavedStates)) then
+    Exit;
 
-  FRulez := FSavedState.Rulez;
-  FSavedState.WaitRecall := False;
+  SavedState := SavedStates[FSavedStateNum];
+  SavedStates[FSavedStateNum].Used := False;
+  FSavedStateNum := -1;
+
+  FHealth := SavedState.Health;
+  FArmor := SavedState.Armor;
+  FAir := SavedState.Air;
+  FJetFuel := SavedState.JetFuel;
+  FCurrWeap := SavedState.CurrWeap;
+  FNextWeap := SavedState.NextWeap;
+  FNextWeapDelay := SavedState.NextWeapDelay;
+  for i := Low(FWeapon) to High(FWeapon) do
+    FWeapon[i] := SavedState.Weapon[i];
+  for i := Low(FAmmo) to High(FAmmo) do
+    FAmmo[i] := SavedState.Ammo[i];
+  for i := Low(FMaxAmmo) to High(FMaxAmmo) do
+    FMaxAmmo[i] := SavedState.MaxAmmo[i];
+  FRulez := SavedState.Rulez;
 
   if gGameSettings.GameType = GT_SERVER then
     MH_SEND_PlayerStats(FUID);
@@ -5969,93 +5252,95 @@ var
   i: Integer;
   b: Byte;
 begin
-  // Ñèãíàòóðà èãðîêà
+  // Сигнатура игрока
   utils.writeSign(st, 'PLYR');
   utils.writeInt(st, Byte(PLR_SAVE_VERSION)); // version
-  // Áîò èëè ÷åëîâåê
+  // Бот или человек
   utils.writeBool(st, FIamBot);
-  // UID èãðîêà
+  // UID игрока
   utils.writeInt(st, Word(FUID));
-  // Èìÿ èãðîêà
+  // Имя игрока
   utils.writeStr(st, FName);
-  // Êîìàíäà
+  // Команда
   utils.writeInt(st, Byte(FTeam));
-  // Æèâ ëè
+  // Жив ли
   utils.writeBool(st, FAlive);
-  // Èçðàñõîäîâàë ëè âñå æèçíè
+  // Израсходовал ли все жизни
   utils.writeBool(st, FNoRespawn);
-  // Íàïðàâëåíèå
+  // Направление
   if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
   utils.writeInt(st, Byte(b));
-  // Çäîðîâüå
+  // Здоровье
   utils.writeInt(st, LongInt(FHealth));
-  // Æèçíè
+  // Коэффициент инвалидности
+  utils.writeInt(st, LongInt(FHandicap));
+  // Жизни
   utils.writeInt(st, Byte(FLives));
-  // Áðîíÿ
+  // Броня
   utils.writeInt(st, LongInt(FArmor));
-  // Çàïàñ âîçäóõà
+  // Запас воздуха
   utils.writeInt(st, LongInt(FAir));
-  // Çàïàñ ãîðþ÷åãî
+  // Запас горючего
   utils.writeInt(st, LongInt(FJetFuel));
-  // Áîëü
+  // Боль
   utils.writeInt(st, LongInt(FPain));
-  // Óáèë
+  // Убил
   utils.writeInt(st, LongInt(FKills));
-  // Óáèë ìîíñòðîâ
+  // Убил монстров
   utils.writeInt(st, LongInt(FMonsterKills));
-  // Ôðàãîâ
+  // Фрагов
   utils.writeInt(st, LongInt(FFrags));
-  // Ôðàãîâ ïîäðÿä
+  // Фрагов подряд
   utils.writeInt(st, Byte(FFragCombo));
-  // Âðåìÿ ïîñëåäíåãî ôðàãà
+  // Время последнего фрага
   utils.writeInt(st, LongWord(FLastFrag));
-  // Ñìåðòåé
+  // Смертей
   utils.writeInt(st, LongInt(FDeath));
-  // Êàêîé ôëàã íåñåò
+  // Какой флаг несет
   utils.writeInt(st, Byte(FFlag));
-  // Íàøåë ñåêðåòîâ
+  // Нашел секретов
   utils.writeInt(st, LongInt(FSecrets));
-  // Òåêóùåå îðóæèå
+  // Текущее оружие
   utils.writeInt(st, Byte(FCurrWeap));
-  // Æåëàåìîå îðóæèå
+  // Желаемое оружие
   utils.writeInt(st, Word(FNextWeap));
-  // ...è ïàóçà
+  // ...и пауза
   utils.writeInt(st, Byte(FNextWeapDelay));
-  // Âðåìÿ çàðÿäêè BFG
+  // Время зарядки BFG
   utils.writeInt(st, SmallInt(FBFGFireCounter));
-  // Áóôåð óðîíà
+  // Буфер урона
   utils.writeInt(st, LongInt(FDamageBuffer));
-  // Ïîñëåäíèé óäàðèâøèé
+  // Последний ударивший
   utils.writeInt(st, Word(FLastSpawnerUID));
-  // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
+  // Тип последнего полученного урона
   utils.writeInt(st, Byte(FLastHit));
-  // Îáúåêò èãðîêà
+  // Объект игрока
   Obj_SaveState(st, @FObj);
-  // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
+  // Текущее количество патронов
   for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FAmmo[i]));
-  // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
+  // Максимальное количество патронов
   for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FMaxAmmo[i]));
-  // Íàëè÷èå îðóæèÿ
+  // Наличие оружия
   for i := WP_FIRST to WP_LAST do utils.writeBool(st, FWeapon[i]);
-  // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
+  // Время перезарядки оружия
   for i := WP_FIRST to WP_LAST do utils.writeInt(st, Word(FReloading[i]));
-  // Íàëè÷èå ðþêçàêà
+  // Наличие рюкзака
   utils.writeBool(st, (R_ITEM_BACKPACK in FRulez));
-  // Íàëè÷èå êðàñíîãî êëþ÷à
+  // Наличие красного ключа
   utils.writeBool(st, (R_KEY_RED in FRulez));
-  // Íàëè÷èå çåëåíîãî êëþ÷à
+  // Наличие зеленого ключа
   utils.writeBool(st, (R_KEY_GREEN in FRulez));
-  // Íàëè÷èå ñèíåãî êëþ÷à
+  // Наличие синего ключа
   utils.writeBool(st, (R_KEY_BLUE in FRulez));
-  // Íàëè÷èå áåðñåðêà
+  // Наличие берсерка
   utils.writeBool(st, (R_BERSERK in FRulez));
-  // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
+  // Время действия специальных предметов
   for i := MR_SUIT to MR_MAX do utils.writeInt(st, LongWord(FMegaRulez[i]));
-  // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
+  // Время до повторного респауна, смены оружия, исользования, захвата флага
   for i := T_RESPAWN to T_FLAGCAP do utils.writeInt(st, LongWord(FTime[i]));
-  // Íàçâàíèå ìîäåëè
-  utils.writeStr(st, FModel.Name);
-  // Öâåò ìîäåëè
+  // Название модели
+  utils.writeStr(st, FModel.GetName());
+  // Цвет модели
   utils.writeInt(st, Byte(FColor.R));
   utils.writeInt(st, Byte(FColor.G));
   utils.writeInt(st, Byte(FColor.B));
@@ -6070,95 +5355,96 @@ var
 begin
   assert(st <> nil);
 
-  // Ñèãíàòóðà èãðîêà
+  // Сигнатура игрока
   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');
-  // Áîò èëè ÷åëîâåê:
+  // Бот или человек:
   FIamBot := utils.readBool(st);
-  // UID èãðîêà
+  // UID игрока
   FUID := utils.readWord(st);
-  // Èìÿ èãðîêà
+  // Имя игрока
   str := utils.readStr(st);
   if (self <> gPlayer1) and (self <> gPlayer2) then FName := str;
-  // Êîìàíäà
+  // Команда
   FTeam := utils.readByte(st);
-  // Æèâ ëè
+  // Жив ли
   FAlive := utils.readBool(st);
-  // Èçðàñõîäîâàë ëè âñå æèçíè
+  // Израсходовал ли все жизни
   FNoRespawn := utils.readBool(st);
-  // Íàïðàâëåíèå
+  // Направление
   b := utils.readByte(st);
   if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2
-  // Çäîðîâüå
+  // Здоровье
   FHealth := utils.readLongInt(st);
-  // Æèçíè
+  // Коэффициент инвалидности
+  FHandicap := utils.readLongInt(st);
+  // Жизни
   FLives := utils.readByte(st);
-  // Áðîíÿ
+  // Броня
   FArmor := utils.readLongInt(st);
-  // Çàïàñ âîçäóõà
+  // Запас воздуха
   FAir := utils.readLongInt(st);
-  // Çàïàñ ãîðþ÷åãî
+  // Запас горючего
   FJetFuel := utils.readLongInt(st);
-  // Áîëü
+  // Боль
   FPain := utils.readLongInt(st);
-  // Óáèë
+  // Убил
   FKills := utils.readLongInt(st);
-  // Óáèë ìîíñòðîâ
+  // Убил монстров
   FMonsterKills := utils.readLongInt(st);
-  // Ôðàãîâ
+  // Фрагов
   FFrags := utils.readLongInt(st);
-  // Ôðàãîâ ïîäðÿä
+  // Фрагов подряд
   FFragCombo := utils.readByte(st);
-  // Âðåìÿ ïîñëåäíåãî ôðàãà
+  // Время последнего фрага
   FLastFrag := utils.readLongWord(st);
-  // Ñìåðòåé
+  // Смертей
   FDeath := utils.readLongInt(st);
-  // Êàêîé ôëàã íåñåò
+  // Какой флаг несет
   FFlag := utils.readByte(st);
-  // Íàøåë ñåêðåòîâ
+  // Нашел секретов
   FSecrets := utils.readLongInt(st);
-  // Òåêóùåå îðóæèå
+  // Текущее оружие
   FCurrWeap := utils.readByte(st);
-  //FNetForceWeap := FCurrWeap;
-  // Æåëàåìîå îðóæèå
+  // Желаемое оружие
   FNextWeap := utils.readWord(st);
-  // ...è ïàóçà
+  // ...и пауза
   FNextWeapDelay := utils.readByte(st);
-  // Âðåìÿ çàðÿäêè BFG
+  // Время зарядки BFG
   FBFGFireCounter := utils.readSmallInt(st);
-  // Áóôåð óðîíà
+  // Буфер урона
   FDamageBuffer := utils.readLongInt(st);
-  // Ïîñëåäíèé óäàðèâøèé
+  // Последний ударивший
   FLastSpawnerUID := utils.readWord(st);
-  // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
+  // Тип последнего полученного урона
   FLastHit := utils.readByte(st);
-  // Îáúåêò èãðîêà
+  // Объект игрока
   Obj_LoadState(@FObj, st);
-  // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
+  // Текущее количество патронов
   for i := A_BULLETS to A_HIGH do FAmmo[i] := utils.readWord(st);
-  // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
+  // Максимальное количество патронов
   for i := A_BULLETS to A_HIGH do FMaxAmmo[i] := utils.readWord(st);
-  // Íàëè÷èå îðóæèÿ
+  // Наличие оружия
   for i := WP_FIRST to WP_LAST do FWeapon[i] := utils.readBool(st);
-  // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
+  // Время перезарядки оружия
   for i := WP_FIRST to WP_LAST do FReloading[i] := utils.readWord(st);
-  // Íàëè÷èå ðþêçàêà
+  // Наличие рюкзака
   if utils.readBool(st) then Include(FRulez, R_ITEM_BACKPACK);
-  // Íàëè÷èå êðàñíîãî êëþ÷à
+  // Наличие красного ключа
   if utils.readBool(st) then Include(FRulez, R_KEY_RED);
-  // Íàëè÷èå çåëåíîãî êëþ÷à
+  // Наличие зеленого ключа
   if utils.readBool(st) then Include(FRulez, R_KEY_GREEN);
-  // Íàëè÷èå ñèíåãî êëþ÷à
+  // Наличие синего ключа
   if utils.readBool(st) then Include(FRulez, R_KEY_BLUE);
-  // Íàëè÷èå áåðñåðêà
+  // Наличие берсерка
   if utils.readBool(st) then Include(FRulez, R_BERSERK);
-  // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
+  // Время действия специальных предметов
   for i := MR_SUIT to MR_MAX do FMegaRulez[i] := utils.readLongWord(st);
-  // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
+  // Время до повторного респауна, смены оружия, исользования, захвата флага
   for i := T_RESPAWN to T_FLAGCAP do FTime[i] := utils.readLongWord(st);
-  // Íàçâàíèå ìîäåëè
+  // Название модели
   str := utils.readStr(st);
-  // Öâåò ìîäåëè
+  // Цвет модели
   FColor.R := utils.readByte(st);
   FColor.G := utils.readByte(st);
   FColor.B := utils.readByte(st);
@@ -6172,7 +5458,7 @@ begin
     str := gPlayer2Settings.Model;
     FColor := gPlayer2Settings.Color;
   end;
-  // Îáíîâëÿåì ìîäåëü èãðîêà
+  // Обновляем модель игрока
   SetModel(str);
   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
     FModel.Color := TEAMCOLOR[FTeam]
@@ -6255,7 +5541,6 @@ begin
           if FBFGFireCounter < 1 then
           begin
             FCurrWeap := WEAPON_KASTET;
-            //FNetForceWeap := FCurrWeap;
             resetWeaponQueue();
             FModel.SetWeapon(WEAPON_KASTET);
           end;
@@ -6274,6 +5559,7 @@ begin
       if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
       begin
         FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
+        FSpawnInvul := 0;
       end;
 
     ITEM_INVIS:
@@ -6358,9 +5644,7 @@ begin
 end;
 
 procedure TPlayer.FlySmoke(Times: DWORD = 1);
-var
-  id, i: DWORD;
-  Anim: TAnimation;
+  var i: DWORD;
 begin
   if (Random(5) = 1) and (Times = 1) then
     Exit;
@@ -6376,37 +5660,29 @@ begin
     Exit;
   end;
 
-  if g_Frames_Get(id, 'FRAMES_SMOKE') then
+  for i := 1 to Times do
   begin
-    for i := 1 to Times do
-    begin
-      Anim := TAnimation.Create(id, False, 3);
-      Anim.Alpha := 150;
-      g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2),
-                   Obj.Y+Obj.Rect.Height-4+Random(8+Times*2), Anim, ONCEANIM_SMOKE);
-      Anim.Free();
-    end;
+    r_GFX_OnceAnim(
+      R_GFX_SMOKE_TRANS,
+      Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_SMOKE_WIDTH div 2),
+      Obj.Y+Obj.Rect.Height-4+Random(8+Times*2)
+    );
   end;
 end;
 
 procedure TPlayer.OnFireFlame(Times: DWORD = 1);
-var
-  id, i: DWORD;
-  Anim: TAnimation;
+  var i: DWORD;
 begin
   if (Random(10) = 1) and (Times = 1) then
     Exit;
 
-  if g_Frames_Get(id, 'FRAMES_FLAME') then
+  for i := 1 to Times do
   begin
-    for i := 1 to Times do
-    begin
-      Anim := TAnimation.Create(id, False, 3);
-      Anim.Alpha := 0;
-      g_GFX_OnceAnim(Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(Anim.Width div 2),
-                   Obj.Y+8+Random(8+Times*2), Anim, ONCEANIM_SMOKE);
-      Anim.Free();
-    end;
+    r_GFX_OnceAnim(
+      R_GFX_FLAME,
+      Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_FLAME_WIDTH div 2),
+      Obj.Y+8+Random(8+Times*2)
+    );
   end;
 end;
 
@@ -6416,6 +5692,12 @@ begin
   FSawSoundIdle.Pause(Enable);
   FSawSoundHit.Pause(Enable);
   FSawSoundSelect.Pause(Enable);
+  FFlameSoundOn.Pause(Enable);
+  FFlameSoundOff.Pause(Enable);
+  FFlameSoundWork.Pause(Enable);
+  FJetSoundFly.Pause(Enable);
+  FJetSoundOn.Pause(Enable);
+  FJetSoundOff.Pause(Enable);
 end;
 
 { T C o r p s e : }
@@ -6426,25 +5708,24 @@ begin
   FObj.X := X;
   FObj.Y := Y;
   FObj.Rect := PLAYER_CORPSERECT;
-  FModelName := ModelName;
   FMess := aMess;
+  FModel := g_PlayerModel_Get(ModelName);
 
   if FMess then
-    begin
-      FState := CORPSE_STATE_MESS;
-      g_PlayerModel_GetAnim(ModelName, A_DIE2, FAnimation, FAnimationMask);
-    end
+  begin
+    FState := CORPSE_STATE_MESS;
+    FModel.ChangeAnimation(A_DIE2);
+  end
   else
-    begin
-      FState := CORPSE_STATE_NORMAL;
-      g_PlayerModel_GetAnim(ModelName, A_DIE1, FAnimation, FAnimationMask);
-    end;
+  begin
+    FState := CORPSE_STATE_NORMAL;
+    FModel.ChangeAnimation(A_DIE1);
+  end;
 end;
 
 destructor TCorpse.Destroy();
 begin
-  FAnimation.Free();
-
+  FModel.Free;
   inherited;
 end;
 
@@ -6472,10 +5753,8 @@ begin
 end;
 
 
-procedure TCorpse.Damage(Value: Word; vx, vy: Integer);
-var
-  pm: TPlayerModel;
-  Blood: TModelBlood;
+procedure TCorpse.Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer);
+  var Blood: TModelBlood;
 begin
   if FState = CORPSE_STATE_REMOVEME then
     Exit;
@@ -6483,32 +5762,33 @@ begin
   FDamage := FDamage + Value;
 
   if FDamage > 150 then
+  begin
+    if FModel <> nil then
     begin
-      if FAnimation <> nil then
-      begin
-        FAnimation.Free();
-        FAnimation := nil;
-
-        FState := CORPSE_STATE_REMOVEME;
-
-        g_Player_CreateGibs(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
-                            FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
-                            FModelName, FColor);
-        // Çâóê ìÿñà îò òðóïà:
-        pm := g_PlayerModel_Get(FModelName);
-        pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
-        pm.Free;
-
-        // Çëîâåùèé ñìåõ:
-        if (gBodyKillEvent <> -1)
-        and gDelayedEvents[gBodyKillEvent].Pending then
-          gDelayedEvents[gBodyKillEvent].Pending := False;
-        gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, 0);
-      end;
+      FState := CORPSE_STATE_REMOVEME;
+
+      g_Player_CreateGibs(
+        FObj.X + FObj.Rect.X + (FObj.Rect.Width div 2),
+        FObj.Y + FObj.Rect.Y + (FObj.Rect.Height div 2),
+        FModel.id,
+        FModel.Color
+      );
+
+      // Звук мяса от трупа:
+      FModel.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
+
+      // Зловещий смех:
+      if (gBodyKillEvent <> -1) and gDelayedEvents[gBodyKillEvent].Pending then
+        gDelayedEvents[gBodyKillEvent].Pending := False;
+      gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, SpawnerUID);
+
+      FModel.Free;
+      FModel := nil;
     end
+  end
   else
     begin
-      Blood := g_PlayerModel_GetBlood(FModelName);
+      Blood := FModel.GetBlood();
       FObj.Vel.X := FObj.Vel.X + vx;
       FObj.Vel.Y := FObj.Vel.Y + vy;
       g_GFX_Blood(FObj.X+PLAYER_CORPSERECT.X+(PLAYER_CORPSERECT.Width div 2),
@@ -6518,24 +5798,6 @@ begin
     end;
 end;
 
-procedure TCorpse.Draw();
-begin
-  if FState = CORPSE_STATE_REMOVEME then
-    Exit;
-
-  if FAnimation <> nil then
-    FAnimation.Draw(FObj.X, FObj.Y, TMirrorType.None);
-
-  if FAnimationMask <> nil then
-  begin
-    e_Colors := FColor;
-    FAnimationMask.Draw(FObj.X, FObj.Y, TMirrorType.None);
-    e_Colors.R := 255;
-    e_Colors.G := 255;
-    e_Colors.B := 255;
-  end;
-end;
-
 procedure TCorpse.Update();
 var
   st: Word;
@@ -6543,6 +5805,9 @@ begin
   if FState = CORPSE_STATE_REMOVEME then
     Exit;
 
+  FObj.oldX := FObj.X;
+  FObj.oldY := FObj.Y;
+
   if gTime mod (GAME_TICK*2) <> 0 then
   begin
     g_Obj_Move(@FObj, True, True, True);
@@ -6550,7 +5815,7 @@ begin
     Exit;
   end;
 
-// Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà:
+// Сопротивление воздуха для трупа:
   FObj.Vel.X := z_dec(FObj.Vel.X, 1);
 
   st := g_Obj_Move(@FObj, True, True, True);
@@ -6562,82 +5827,78 @@ begin
     Exit;
   end;
 
-  if FAnimation <> nil then
-    FAnimation.Update();
-  if FAnimationMask <> nil then
-    FAnimationMask.Update();
+  if FModel <> nil then
+    FModel.Update;
 end;
 
 
 procedure TCorpse.SaveState (st: TStream);
-var
-  anim: Boolean;
+  var anim: Boolean;
 begin
   assert(st <> nil);
 
-  // Ñèãíàòóðà òðóïà
+  // Сигнатура трупа
   utils.writeSign(st, 'CORP');
   utils.writeInt(st, Byte(0));
-  // Ñîñòîÿíèå
+  // Состояние
   utils.writeInt(st, Byte(FState));
-  // Íàêîïëåííûé óðîí
+  // Накопленный урон
   utils.writeInt(st, Byte(FDamage));
-  // Öâåò
-  utils.writeInt(st, Byte(FColor.R));
-  utils.writeInt(st, Byte(FColor.G));
-  utils.writeInt(st, Byte(FColor.B));
-  // Îáúåêò òðóïà
+  // Цвет
+  utils.writeInt(st, Byte(FModel.Color.R));
+  utils.writeInt(st, Byte(FModel.Color.G));
+  utils.writeInt(st, Byte(FModel.Color.B));
+  // Объект трупа
   Obj_SaveState(st, @FObj);
   utils.writeInt(st, Word(FPlayerUID));
-  // Åñòü ëè àíèìàöèÿ
-  anim := (FAnimation <> nil);
+  // animation
+  anim := (FModel <> nil);
   utils.writeBool(st, anim);
-  // Åñëè åñòü - ñîõðàíÿåì
-  if anim then FAnimation.SaveState(st);
-  // Åñòü ëè ìàñêà àíèìàöèè
-  anim := (FAnimationMask <> nil);
+  if anim then FModel.AnimState.SaveState(st);
+  // animation for mask (same as animation, compat with older saves)
+  anim := (FModel <> nil);
   utils.writeBool(st, anim);
-  // Åñëè åñòü - ñîõðàíÿåì
-  if anim then FAnimationMask.SaveState(st);
+  if anim then FModel.AnimState.SaveState(st);
 end;
 
 
 procedure TCorpse.LoadState (st: TStream);
-var
-  anim: Boolean;
+  var anim: Boolean; r, g, b: Byte; stub: TAnimationState;
 begin
   assert(st <> nil);
 
-  // Ñèãíàòóðà òðóïà
+  // Сигнатура трупа
   if not utils.checkSign(st, 'CORP') then raise XStreamError.Create('invalid corpse signature');
   if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid corpse version');
-  // Ñîñòîÿíèå
+  // Состояние
   FState := utils.readByte(st);
-  // Íàêîïëåííûé óðîí
+  // Накопленный урон
   FDamage := utils.readByte(st);
-  // Öâåò
-  FColor.R := utils.readByte(st);
-  FColor.G := utils.readByte(st);
-  FColor.B := utils.readByte(st);
-  // Îáúåêò òðóïà
+  // Цвет
+  r := utils.readByte(st);
+  g := utils.readByte(st);
+  b := utils.readByte(st);
+  FModel.SetColor(r, g, b);
+  // Объект трупа
   Obj_LoadState(@FObj, st);
   FPlayerUID := utils.readWord(st);
-  // Åñòü ëè àíèìàöèÿ
+  // animation
+  stub := TAnimationState.Create(False, 0, 0);
   anim := utils.readBool(st);
-  // Åñëè åñòü - çàãðóæàåì
   if anim then
   begin
-    Assert(FAnimation <> nil, 'TCorpse.LoadState: no FAnimation');
-    FAnimation.LoadState(st);
-  end;
-  // Åñòü ëè ìàñêà àíèìàöèè
-  anim := utils.readBool(st);
-  // Åñëè åñòü - çàãðóæàåì
-  if anim then
+    stub.LoadState(st);
+    FModel.AnimState.CurrentFrame := Min(stub.CurrentFrame, FModel.AnimState.Length);
+  end
+  else
   begin
-    Assert(FAnimationMask <> nil, 'TCorpse.LoadState: no FAnimationMask');
-    FAnimationMask.LoadState(st);
+    FModel.Free;
+    FModel := nil
   end;
+  // animation for mask (same as animation, compat with older saves)
+  anim := utils.readBool(st);
+  if anim then stub.LoadState(st);
+  stub.Free;
 end;
 
 { T B o t : }
@@ -6670,14 +5931,6 @@ begin
   inherited Destroy();
 end;
 
-procedure TBot.Draw();
-begin
-  inherited Draw();
-
-  //if FTargetUID <> 0 then e_DrawLine(1, FObj.X, FObj.Y, g_Player_Get(FTargetUID).FObj.X,
-  //                                   g_Player_Get(FTargetUID).FObj.Y, 255, 0, 0);
-end;
-
 procedure TBot.Respawn(Silent: Boolean; Force: Boolean = False);
 begin
   inherited Respawn(Silent, Force);
@@ -6705,20 +5958,20 @@ type
 
   function Compare(a, b: TTarget): Integer;
   begin
-    if a.Line and not b.Line then // A íà ëèíèè îãíÿ
+    if a.Line and not b.Line then // A на линии огня
       Result := -1
    else
-     if not a.Line and b.Line then // B íà ëèíèè îãíÿ
+     if not a.Line and b.Line then // B на линии огня
        Result := 1
-     else // È A, è B íà ëèíèè èëè íå íà ëèíèè îãíÿ
+     else // И A, и B на линии или не на линии огня
        if (a.Line and b.Line) or ((not a.Line) and (not b.Line)) then
          begin
-           if a.Dist > b.Dist then // B áëèæå
+           if a.Dist > b.Dist then // B ближе
              Result := 1
-           else // A áëèæå èëè ðàâíîóäàëåííî ñ B
+           else // A ближе или равноудаленно с B
              Result := -1;
          end
-       else // Ñòðàííî -> A
+       else // Странно -> A
          Result := -1;
   end;
 
@@ -6744,10 +5997,10 @@ var
       x2 := mon.Obj.X+mon.Obj.Rect.X+(mon.Obj.Rect.Width div 2);
       y2 := mon.Obj.Y+mon.Obj.Rect.Y+(mon.Obj.Rect.Height div 2);
 
-      // Åñëè ìîíñòð íà ýêðàíå è íå ïðèêðûò ñòåíîé
+      // Если монстр на экране и не прикрыт стеной
       if g_TraceVector(x1, y1, x2, y2) then
       begin
-        // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé
+        // Добавляем к списку возможных целей
         SetLength(targets, Length(targets)+1);
         with targets[High(targets)] do
         begin
@@ -6771,11 +6024,11 @@ begin
   vsPlayer := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER);
   vsMonster := LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER);
 
-// Åñëè òåêóùåå îðóæèå íå òî, ÷òî íóæíî, òî ìåíÿåì:
+// Если текущее оружие не то, что нужно, то меняем:
   if FCurrWeap <> FSelectedWeapon then
     NextWeapon();
 
-// Åñëè íóæíî ñòðåëÿòü è íóæíîå îðóæèå, òî íàæàòü "Ñòðåëÿòü":
+// Если нужно стрелять и нужное оружие, то нажать "Стрелять":
   if (GetAIFlag('NEEDFIRE') <> '') and (FCurrWeap = FSelectedWeapon) then
     begin
       RemoveAIFlag('NEEDFIRE');
@@ -6787,7 +6040,7 @@ begin
       end;
     end;
 
-// Êîîðäèíàòû ñòâîëà:
+// Координаты ствола:
   x1 := FObj.X + WEAPONPOINT[FDirection].X;
   y1 := FObj.Y + WEAPONPOINT[FDirection].Y;
 
@@ -6795,10 +6048,10 @@ begin
 
   ok := False;
   if Target.UID <> 0 then
-    begin // Öåëü åñòü - íàñòðàèâàåì
+    begin // Цель есть - настраиваем
       if (g_GetUIDType(Target.UID) = UID_PLAYER) and
           vsPlayer then
-        begin // Èãðîê
+        begin // Игрок
           tpla := g_Player_Get(Target.UID);
           if tpla <> nil then
             with tpla do
@@ -6822,7 +6075,7 @@ begin
       else
         if (g_GetUIDType(Target.UID) = UID_MONSTER) and
             vsMonster then
-          begin // Ìîíñòð
+          begin // Монстр
             mon := g_Monsters_ByUID(Target.UID);
             if mon <> nil then
               begin
@@ -6842,7 +6095,7 @@ begin
     end;
 
   if not ok then
-    begin // Öåëè íåò - îáíóëÿåì
+    begin // Цели нет - обнуляем
       Target.X := 0;
       Target.Y := 0;
       Target.cX := 0;
@@ -6854,10 +6107,10 @@ begin
 
   targets := nil;
 
-// Åñëè öåëü íå âèäèìà èëè íå íà ëèíèè îãíÿ, òî èùåì âñå âîçìîæíûå öåëè:
+// Если цель не видима или не на линии огня, то ищем все возможные цели:
   if (not Target.Line) or (not Target.Visible) then
   begin
-  // Èãðîêè:
+  // Игроки:
     if vsPlayer then
       for a := 0 to High(gPlayers) do
         if (gPlayers[a] <> nil) and (gPlayers[a].alive) and
@@ -6873,10 +6126,10 @@ begin
             x2 := gPlayers[a].FObj.X + PLAYER_RECT_CX;
             y2 := gPlayers[a].FObj.Y + PLAYER_RECT_CY;
 
-          // Åñëè èãðîê íà ýêðàíå è íå ïðèêðûò ñòåíîé:
+          // Если игрок на экране и не прикрыт стеной:
             if g_TraceVector(x1, y1, x2, y2) then
               begin
-              // Äîáàâëÿåì ê ñïèñêó âîçìîæíûõ öåëåé:
+              // Добавляем к списку возможных целей:
                 SetLength(targets, Length(targets)+1);
                 with targets[High(targets)] do
                   begin
@@ -6895,64 +6148,64 @@ begin
               end;
           end;
 
-  // Ìîíñòðû:
+  // Монстры:
     if vsMonster then g_Mons_ForEach(monsUpdate);
   end;
 
-// Åñëè åñòü âîçìîæíûå öåëè:
-// (Âûáèðàåì ëó÷øóþ, ìåíÿåì îðóæèå è áåæèì ê íåé/îò íåå)
+// Если есть возможные цели:
+// (Выбираем лучшую, меняем оружие и бежим к ней/от нее)
   if targets <> nil then
   begin
-  // Âûáèðàåì íàèëó÷øóþ öåëü:
+  // Выбираем наилучшую цель:
     BestTarget := targets[0];
     if Length(targets) > 1 then
       for a := 1 to High(targets) do
         if Compare(BestTarget, targets[a]) = 1 then
           BestTarget := targets[a];
 
-  // Åñëè ëó÷øàÿ öåëü "âèäíåå" òåêóùåé, òî òåêóùàÿ := ëó÷øàÿ:
+  // Если лучшая цель "виднее" текущей, то текущая := лучшая:
     if ((not Target.Visible) and BestTarget.Visible and (Target.UID <> BestTarget.UID)) or
         ((not Target.Line) and BestTarget.Line and BestTarget.Visible) then
       begin
         Target := BestTarget;
 
         if (Healthy() = 3) or ((Healthy() = 2)) then
-          begin // Åñëè çäîðîâû - äîãîíÿåì
+          begin // Если здоровы - догоняем
             if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
               SetAIFlag('GORIGHT', '1');
             if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
               SetAIFlag('GOLEFT', '1');
           end
         else
-          begin // Åñëè ïîáèòû - óáåãàåì
+          begin // Если побиты - убегаем
             if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
               SetAIFlag('GORIGHT', '1');
             if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
               SetAIFlag('GOLEFT', '1');
           end;
 
-      // Âûáèðàåì îðóæèå íà îñíîâå ðàññòîÿíèÿ è ïðèîðèòåòîâ:
+      // Выбираем оружие на основе расстояния и приоритетов:
         SelectWeapon(Abs(x1-Target.cX));
       end;
   end;
 
-// Åñëè åñòü öåëü:
-// (Äîãîíÿåì/óáåãàåì, ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëè)
-// (Åñëè öåëü äàëåêî, òî õâàòèò ñëåäèòü çà íåé)
+// Если есть цель:
+// (Догоняем/убегаем, стреляем по направлению к цели)
+// (Если цель далеко, то хватит следить за ней)
   if Target.UID <> 0 then
   begin
     if not TargetOnScreen(Target.X + Target.Rect.X,
                           Target.Y + Target.Rect.Y) then
-      begin // Öåëü ñáåæàëà ñ "ýêðàíà"
+      begin // Цель сбежала с "экрана"
         if (Healthy() = 3) or ((Healthy() = 2)) then
-          begin // Åñëè çäîðîâû - äîãîíÿåì
+          begin // Если здоровы - догоняем
             if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
               SetAIFlag('GORIGHT', '1');
             if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
               SetAIFlag('GOLEFT', '1');
           end
         else
-          begin // Åñëè ïîáèòû - çàáûâàåì î öåëè è óáåãàåì
+          begin // Если побиты - забываем о цели и убегаем
             Target.UID := 0;
             if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
               SetAIFlag('GORIGHT', '1');
@@ -6961,11 +6214,11 @@ begin
           end;
       end
     else
-      begin // Öåëü ïîêà íà "ýêðàíå"
-      // Åñëè öåëü íå çàãîðîæåíà ñòåíîé, òî îòìå÷àåì, êîãäà åå âèäåëè:
+      begin // Цель пока на "экране"
+      // Если цель не загорожена стеной, то отмечаем, когда ее видели:
         if g_TraceVector(x1, y1, Target.cX, Target.cY) then
           FLastVisible := gTime;
-      // Åñëè ðàçíèöà âûñîò íå âåëèêà, òî äîãîíÿåì:
+      // Если разница высот не велика, то догоняем:
         if (Abs(FObj.Y-Target.Y) <= 128) then
           begin
             if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
@@ -6975,7 +6228,7 @@ begin
           end;
       end;
 
-  // Âûáèðàåì óãîë ââåðõ:
+  // Выбираем угол вверх:
     if FDirection = TDirection.D_LEFT then
       angle := ANGLE_LEFTUP
     else
@@ -6984,18 +6237,18 @@ begin
     firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
     fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
 
-  // Åñëè ïðè óãëå ââåðõ ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè:
+  // Если при угле вверх можно попасть в приблизительное положение цели:
     if g_CollideLine(x1, y1, x1+firew, y1+fireh,
           Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128), //96
           Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
           Target.Rect.Width, Target.Rect.Height) and
         g_TraceVector(x1, y1, Target.cX, Target.cY) then
-      begin // òî íóæíî ñòðåëÿòü ââåðõ
+      begin // то нужно стрелять вверх
         SetAIFlag('NEEDFIRE', '1');
         SetAIFlag('NEEDSEEUP', '1');
       end;
 
-  // Âûáèðàåì óãîë âíèç:
+  // Выбираем угол вниз:
     if FDirection = TDirection.D_LEFT then
       angle := ANGLE_LEFTDOWN
     else
@@ -7004,34 +6257,34 @@ begin
     firew := Trunc(Cos(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
     fireh := Trunc(Sin(DegToRad(-angle))*gPlayerScreenSize.X*0.6);
 
-  // Åñëè ïðè óãëå âíèç ìîæíî ïîïàñòü â ïðèáëèçèòåëüíîå ïîëîæåíèå öåëè:
+  // Если при угле вниз можно попасть в приблизительное положение цели:
     if g_CollideLine(x1, y1, x1+firew, y1+fireh,
           Target.X+Target.Rect.X+GetInterval(FDifficult.DiagPrecision, 128),
           Target.Y+Target.Rect.Y+GetInterval(FDifficult.DiagPrecision, 128),
           Target.Rect.Width, Target.Rect.Height) and
         g_TraceVector(x1, y1, Target.cX, Target.cY) then
-      begin // òî íóæíî ñòðåëÿòü âíèç
+      begin // то нужно стрелять вниз
         SetAIFlag('NEEDFIRE', '1');
         SetAIFlag('NEEDSEEDOWN', '1');
       end;
 
-  // Åñëè öåëü âèäíî è îíà íà òàêîé æå âûñîòå:
+  // Если цель видно и она на такой же высоте:
     if Target.Visible and
         (y1+4 < Target.Y+Target.Rect.Y+Target.Rect.Height) and
         (y1-4 > Target.Y+Target.Rect.Y) then
       begin
-      // Åñëè èäåì â ñòîðîíó öåëè, òî íàäî ñòðåëÿòü:
+      // Если идем в сторону цели, то надо стрелять:
         if ((FDirection = TDirection.D_LEFT) and (Target.X < FObj.X)) or
             ((FDirection = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
-        begin // òî íóæíî ñòðåëÿòü âïåðåä
+        begin // то нужно стрелять вперед
           SetAIFlag('NEEDFIRE', '1');
           SetAIFlag('NEEDSEEDOWN', '');
           SetAIFlag('NEEDSEEUP', '');
         end;
-      // Åñëè öåëü â ïðåäåëàõ "ýêðàíà" è ñëîæíîñòü ïîçâîëÿåò ïðûæêè ñáëèæåíèÿ:
+      // Если цель в пределах "экрана" и сложность позволяет прыжки сближения:
         if Abs(FObj.X-Target.X) < Trunc(gPlayerScreenSize.X*0.75) then
           if GetRnd(FDifficult.CloseJump) then
-            begin // òî åñëè ïîâåçåò - ïðûãàåì (îñîáåííî, åñëè áëèçêî)
+            begin // то если повезет - прыгаем (особенно, если близко)
               if Abs(FObj.X-Target.X) < 128 then
                 a := 4
               else
@@ -7041,91 +6294,91 @@ begin
             end;
       end;
 
-  // Åñëè öåëü âñå åùå åñòü:
+  // Если цель все еще есть:
     if Target.UID <> 0 then
-      if gTime-FLastVisible > 2000 then // Åñëè âèäåëè äàâíî
-        Target.UID := 0 // òî çàáûòü öåëü
-      else // Åñëè âèäåëè íåäàâíî
-        begin // íî öåëü óáèëè
+      if gTime-FLastVisible > 2000 then // Если видели давно
+        Target.UID := 0 // то забыть цель
+      else // Если видели недавно
+        begin // но цель убили
           if Target.IsPlayer then
-            begin // Öåëü - èãðîê
+            begin // Цель - игрок
               pla := g_Player_Get(Target.UID);
               if (pla = nil) or (not pla.alive) or pla.NoTarget or
                  (pla.FMegaRulez[MR_INVIS] >= gTime) then
-                Target.UID := 0; // òî çàáûòü öåëü
+                Target.UID := 0; // то забыть цель
             end
           else
-            begin // Öåëü - ìîíñòð
+            begin // Цель - монстр
               mon := g_Monsters_ByUID(Target.UID);
               if (mon = nil) or (not mon.alive) then
-                Target.UID := 0; // òî çàáûòü öåëü
+                Target.UID := 0; // то забыть цель
             end;
         end;
   end; // if Target.UID <> 0
 
   FTargetUID := Target.UID;
 
-// Åñëè âîçìîæíûõ öåëåé íåò:
-// (Àòàêà ÷åãî-íèáóäü ñëåâà èëè ñïðàâà)
+// Если возможных целей нет:
+// (Атака чего-нибудь слева или справа)
   if targets = nil then
     if GetAIFlag('ATTACKLEFT') <> '' then
-      begin // Åñëè íóæíî àòàêîâàòü íàëåâî
+      begin // Если нужно атаковать налево
         RemoveAIFlag('ATTACKLEFT');
 
         SetAIFlag('NEEDJUMP', '1');
 
         if RunDirection() = TDirection.D_RIGHT then
-          begin // Èäåì íå â òó ñòîðîíó
+          begin // Идем не в ту сторону
             if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
-              begin // Åñëè çäîðîâû, òî, âîçìîæíî, ñòðåëÿåì áåæèì âëåâî è ñòðåëÿåì
+              begin // Если здоровы, то, возможно, стреляем бежим влево и стреляем
                 SetAIFlag('NEEDFIRE', '1');
                 SetAIFlag('GOLEFT', '1');
               end;
           end
         else
-          begin // Èäåì â íóæíóþ ñòîðîíó
-            if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ
+          begin // Идем в нужную сторону
+            if GetRnd(FDifficult.InvisFire) then // Возможно, стреляем вслепую
               SetAIFlag('NEEDFIRE', '1');
-            if Healthy() <= 1 then // Ïîáèòû - óáåãàåì
+            if Healthy() <= 1 then // Побиты - убегаем
               SetAIFlag('GORIGHT', '1');
           end;
       end
     else
       if GetAIFlag('ATTACKRIGHT') <> '' then
-        begin // Åñëè íóæíî àòàêîâàòü íàïðàâî
+        begin // Если нужно атаковать направо
           RemoveAIFlag('ATTACKRIGHT');
 
           SetAIFlag('NEEDJUMP', '1');
 
           if RunDirection() = TDirection.D_LEFT then
-            begin // Èäåì íå â òó ñòîðîíó
+            begin // Идем не в ту сторону
               if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
-                begin // Åñëè çäîðîâû, òî, âîçìîæíî, áåæèì âïðàâî è ñòðåëÿåì
+                begin // Если здоровы, то, возможно, бежим вправо и стреляем
                   SetAIFlag('NEEDFIRE', '1');
                   SetAIFlag('GORIGHT', '1');
                 end;
             end
           else
             begin
-              if GetRnd(FDifficult.InvisFire) then // Âîçìîæíî, ñòðåëÿåì âñëåïóþ
+              if GetRnd(FDifficult.InvisFire) then // Возможно, стреляем вслепую
                 SetAIFlag('NEEDFIRE', '1');
-              if Healthy() <= 1 then // Ïîáèòû - óáåãàåì
+              if Healthy() <= 1 then // Побиты - убегаем
                 SetAIFlag('GOLEFT', '1');
             end;
         end;
 
-  //HACK! (does it belong there?)
+  //HACK! (does it belongs there?)
   RealizeCurrentWeapon();
 
-// Åñëè åñòü âîçìîæíûå öåëè:
-// (Ñòðåëÿåì ïî íàïðàâëåíèþ ê öåëÿì)
+// Если есть возможные цели:
+// (Стреляем по направлению к целям)
   if (targets <> nil) and (GetAIFlag('NEEDFIRE') <> '') then
     for a := 0 to High(targets) do
       begin
-      // Åñëè ìîæåì ñòðåëÿòü ïî äèàãîíàëè:
+      // Если можем стрелять по диагонали:
         if GetRnd(FDifficult.DiagFire) then
           begin
-          // Èùåì öåëü ñâåðõó è ñòðåëÿåì, åñëè åñòü:
+          // Ищем цель сверху и стреляем, если есть:
             if FDirection = TDirection.D_LEFT then
               angle := ANGLE_LEFTUP
             else
@@ -7144,7 +6397,7 @@ begin
                 SetAIFlag('NEEDSEEUP', '1');
               end;
 
-          // Èùåì öåëü ñíèçó è ñòðåëÿåì, åñëè åñòü:
+          // Ищем цель снизу и стреляем, если есть:
             if FDirection = TDirection.D_LEFT then
               angle := ANGLE_LEFTDOWN
             else
@@ -7164,7 +6417,7 @@ begin
               end;
           end;
 
-      // Åñëè öåëü "ïåðåä íîñîì", òî ñòðåëÿåì:
+      // Если цель "перед носом", то стреляем:
         if targets[a].Line and targets[a].Visible and
             (((FDirection = TDirection.D_LEFT) and (targets[a].X < FObj.X)) or
             ((FDirection = TDirection.D_RIGHT) and (targets[a].X > FObj.X))) then
@@ -7174,20 +6427,20 @@ begin
         end;
       end;
 
-// Åñëè ëåòèò ïóëÿ, òî, âîçìîæíî, ïîäïðûãèâàåì:
+// Если летит пуля, то, возможно, подпрыгиваем:
   if g_Weapon_Danger(FUID, FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y,
                     PLAYER_RECT.Width, PLAYER_RECT.Height,
                     40+GetInterval(FDifficult.Cover, 40)) then
     SetAIFlag('NEEDJUMP', '1');
 
-// Åñëè êîí÷èëèñü ïàòîðíû, òî íóæíî ñìåíèòü îðóæèå:
+// Если кончились паторны, то нужно сменить оружие:
   ammo := GetAmmoByWeapon(FCurrWeap);
   if ((FCurrWeap = WEAPON_SHOTGUN2) and (ammo < 2)) or
       ((FCurrWeap = WEAPON_BFG) and (ammo < 40)) or
       (ammo = 0) then
     SetAIFlag('SELECTWEAPON', '1');
 
-// Åñëè íóæíî ñìåíèòü îðóæèå, òî âûáèðàåì íóæíîå:
+// Если нужно сменить оружие, то выбираем нужное:
   if GetAIFlag('SELECTWEAPON') = '1' then
   begin
     SelectWeapon(-1);
@@ -7208,7 +6461,7 @@ begin
   begin
     EnableAI := True;
 
-    // Ïðîâåðÿåì, îòêëþ÷¸í ëè AI áîòîâ
+    // Проверяем, отключён ли AI ботов
     if (g_debug_BotAIOff = 1) and (Team = TEAM_RED) then
       EnableAI := False;
     if (g_debug_BotAIOff = 2) and (Team = TEAM_BLUE) then
@@ -7369,7 +6622,7 @@ procedure TBot.UpdateMove;
   var
     x, sx: Integer;
   begin
-    { TODO 5 : Ëåñòíèöû }
+    { TODO 5 : Лестницы }
     sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
     for x := 1 to PLAYER_RECT.Width do
       if (not StayOnStep(x*sx, 0)) and
@@ -7387,7 +6640,7 @@ procedure TBot.UpdateMove;
   var
     x, sx, xx: Integer;
   begin
-    { TODO 5 : Ëåñòíèöû }
+    { TODO 5 : Лестницы }
     sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
     for x := 1 to PLAYER_RECT.Width do
       if (not StayOnStep(x*sx, 0)) and
@@ -7560,11 +6813,11 @@ procedure TBot.UpdateMove;
   end;
 
 begin
-// Âîçìîæíî, íàæèìàåì êíîïêó:
+// Возможно, нажимаем кнопку:
   if Rnd(16) and IsSafeTrigger() then
     PressKey(KEY_OPEN);
 
-// Åñëè ïîä ëèôòîì èëè ñòóïåíüêàìè, òî, âîçìîæíî, ïðûãàåì:
+// Если под лифтом или ступеньками, то, возможно, прыгаем:
   if OnLadder() or ((BelowLadder() or BelowLiftUp()) and Rnd(8)) then
     begin
       ReleaseKey(KEY_LEFT);
@@ -7572,7 +6825,7 @@ begin
       Jump();
     end;
 
-// Èäåì âëåâî, åñëè íàäî áûëî:
+// Идем влево, если надо было:
   if GetAIFlag('GOLEFT') <> '' then
     begin
       RemoveAIFlag('GOLEFT');
@@ -7580,7 +6833,7 @@ begin
         GoLeft(360);
     end;
 
-// Èäåì âïðàâî, åñëè íàäî áûëî:
+// Идем вправо, если надо было:
   if GetAIFlag('GORIGHT') <> '' then
     begin
       RemoveAIFlag('GORIGHT');
@@ -7588,21 +6841,21 @@ begin
         GoRight(360);
     end;
 
-// Åñëè âûëåòåëè çà êàðòó, òî ïðîáóåì âåðíóòüñÿ:
+// Если вылетели за карту, то пробуем вернуться:
   if FObj.X < -32 then
     GoRight(360)
   else
     if FObj.X+32 > gMapInfo.Width then
       GoLeft(360);
 
-// Ïðûãàåì, åñëè íàäî áûëî:
+// Прыгаем, если надо было:
   if GetAIFlag('NEEDJUMP') <> '' then
     begin
       Jump(0);
       RemoveAIFlag('NEEDJUMP');
     end;
 
-// Ñìîòðèì ââåðõ, åñëè íàäî áûëî:
+// Смотрим вверх, если надо было:
   if GetAIFlag('NEEDSEEUP') <> '' then
     begin
       ReleaseKey(KEY_UP);
@@ -7611,7 +6864,7 @@ begin
       RemoveAIFlag('NEEDSEEUP');
     end;
 
-// Ñìîòðèì âíèç, åñëè íàäî áûëî:
+// Смотрим вниз, если надо было:
   if GetAIFlag('NEEDSEEDOWN') <> '' then
     begin
       ReleaseKey(KEY_UP);
@@ -7620,7 +6873,7 @@ begin
       RemoveAIFlag('NEEDSEEDOWN');
     end;
 
-// Åñëè íóæíî áûëî â äûðó è ìû íå íà çåìëå, òî ïîêîðíî ëåòèì:
+// Если нужно было в дыру и мы не на земле, то покорно летим:
   if GetAIFlag('GOINHOLE') <> '' then
     if not OnGround() then
       begin
@@ -7630,12 +6883,12 @@ begin
         SetAIFlag('FALLINHOLE', '1');
       end;
 
-// Åñëè ïàäàëè è äîñòèãëè çåìëè, òî õâàòèò ïàäàòü:
+// Если падали и достигли земли, то хватит падать:
   if GetAIFlag('FALLINHOLE') <> '' then
     if OnGround() then
       RemoveAIFlag('FALLINHOLE');
 
-// Åñëè ëåòåëè ïðÿìî è ñåé÷àñ íå íà ëåñòíèöå èëè íà âåðøèíå ëèôòà, òî îòõîäèì â ñòîðîíó:
+// Если летели прямо и сейчас не на лестнице или на вершине лифта, то отходим в сторону:
   if not (KeyPressed(KEY_LEFT) or KeyPressed(KEY_RIGHT)) then
     if GetAIFlag('FALLINHOLE') = '' then
       if (not OnLadder()) or (FObj.Vel.Y >= 0) or (OnTopLift()) then
@@ -7644,40 +6897,40 @@ begin
         else
           GoRight(360);
 
-// Åñëè íà çåìëå è ìîæíî ïîäïðûãíóòü, òî, âîçìîæíî, ïðûãàåì:
+// Если на земле и можно подпрыгнуть, то, возможно, прыгаем:
   if OnGround() and
       CanJumpUp(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*32) and
       Rnd(8) then
     Jump();
 
-// Åñëè íà çåìëå è âîçëå äûðû (ãëóáèíà > 2 ðîñòîâ èãðîêà):
+// Если на земле и возле дыры (глубина > 2 ростов игрока):
   if OnGround() and NearHole() then
-    if NearDeepHole() then // Åñëè ýòî áåçäíà
+    if NearDeepHole() then // Если это бездна
       case Random(6) of
-        0..3: Turn(); // Áåæèì îáðàòíî
-        4: Jump(); // Ïðûãàåì
-        5: begin // Ïðûãàåì îáðàòíî
+        0..3: Turn(); // Бежим обратно
+        4: Jump(); // Прыгаем
+        5: begin // Прыгаем обратно
              Turn();
              Jump();
            end;
       end
-    else // Ýòî íå áåçäíà è ìû åùå íå ëåòèì òóäà
+    else // Это не бездна и мы еще не летим туда
       if GetAIFlag('GOINHOLE') = '' then
         case Random(6) of
-          0: Turn(); // Íå íóæíî òóäà
-          1: Jump(); // Âäðóã ïîâåçåò - ïðûãàåì
-          else // Åñëè ÿìà ñ ãðàíèöåé, òî ïðè ñëó÷àå ìîæíî òóäà ïðûãíóòü
+          0: Turn(); // Не нужно туда
+          1: Jump(); // Вдруг повезет - прыгаем
+          else // Если яма с границей, то при случае можно туда прыгнуть
             if BorderHole() then
               SetAIFlag('GOINHOLE', '1');
    end;
 
-// Åñëè íà çåìëå, íî íåêóäà èäòè:
+// Если на земле, но некуда идти:
   if (not CanRun()) and OnGround() then
     begin
-    // Åñëè ìû íà ëåñòíèöå èëè ìîæíî ïåðåïðûãíóòü, òî ïðûãàåì:
+    // Если мы на лестнице или можно перепрыгнуть, то прыгаем:
       if CanJumpOver() or OnLadder() then
         Jump()
-      else // èíà÷å ïîïûòàåìñÿ â äðóãóþ ñòîðîíó
+      else // иначе попытаемся в другую сторону
         if Random(2) = 0 then
         begin
           if IsSafeTrigger() then
@@ -7686,11 +6939,11 @@ begin
           Turn();
     end;
 
-// Îñòàëîñü ìàëî âîçäóõà:
+// Осталось мало воздуха:
   if FAir < 36 * 2 then
     Jump(20);
 
-// Âûáèðàåìñÿ èç êèñëîòû, åñëè íåò êîñòþìà, îáîæãëèñü, èëè ìàëî çäîðîâüÿ:
+// Выбираемся из кислоты, если нет костюма, обожглись, или мало здоровья:
   if (FMegaRulez[MR_SUIT] < gTime) and ((FLastHit = HIT_ACID) or (Healthy() <= 1)) then
     if BodyInAcid(0, 0) then
       Jump();
@@ -7731,7 +6984,7 @@ begin
   if Dist = -1 then Dist := BOT_LONGDIST;
 
   if Dist > BOT_LONGDIST then
-  begin // Äàëüíèé áîé
+  begin // Дальний бой
     for a := 0 to 9 do
       if FWeapon[FDifficult.WeaponPrior[a]] and HaveAmmo(FDifficult.WeaponPrior[a]) then
       begin
@@ -7740,7 +6993,7 @@ begin
       end;
   end
   else //if Dist > BOT_UNSAFEDIST then
-  begin // Áëèæíèé áîé
+  begin // Ближний бой
     for a := 0 to 9 do
       if FWeapon[FDifficult.CloseWeaponPrior[a]] and HaveAmmo(FDifficult.CloseWeaponPrior[a]) then
       begin
@@ -7799,7 +7052,7 @@ begin
       ok := False;
       if (g_GetUIDType(FLastSpawnerUID) = UID_PLAYER) and
           LongBool(gGameSettings.Options and GAME_OPTION_BOTVSPLAYER) then
-        begin // Èãðîê
+        begin // Игрок
           pla := g_Player_Get(FLastSpawnerUID);
           ok := not TargetOnScreen(pla.FObj.X + PLAYER_RECT.X,
                                    pla.FObj.Y + PLAYER_RECT.Y);
@@ -7807,7 +7060,7 @@ begin
       else
         if (g_GetUIDType(FLastSpawnerUID) = UID_MONSTER) and
            LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER) then
-        begin // Ìîíñòð
+        begin // Монстр
           mon := g_Monsters_ByUID(FLastSpawnerUID);
           ok := not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
                                    mon.Obj.Y + mon.Obj.Rect.Y);
@@ -7875,22 +7128,22 @@ var
 begin
   inherited SaveState(st);
   utils.writeSign(st, 'BOT0');
-  // Âûáðàííîå îðóæèå
+  // Выбранное оружие
   utils.writeInt(st, Byte(FSelectedWeapon));
-  // UID öåëè
+  // UID цели
   utils.writeInt(st, Word(FTargetUID));
-  // Âðåìÿ ïîòåðè öåëè
+  // Время потери цели
   utils.writeInt(st, LongWord(FLastVisible));
-  // Êîëè÷åñòâî ôëàãîâ ÈÈ
+  // Количество флагов ИИ
   dw := Length(FAIFlags);
   utils.writeInt(st, LongInt(dw));
-  // Ôëàãè ÈÈ
+  // Флаги ИИ
   for i := 0 to dw-1 do
   begin
     utils.writeStr(st, FAIFlags[i].Name, 20);
     utils.writeStr(st, FAIFlags[i].Value, 20);
   end;
-  // Íàñòðîéêè ñëîæíîñòè
+  // Настройки сложности
   FDifficult.save(st);
 end;
 
@@ -7902,27 +7155,29 @@ var
 begin
   inherited LoadState(st);
   if not utils.checkSign(st, 'BOT0') then raise XStreamError.Create('invalid bot signature');
-  // Âûáðàííîå îðóæèå
+  // Выбранное оружие
   FSelectedWeapon := utils.readByte(st);
-  // UID öåëè
+  // UID цели
   FTargetUID := utils.readWord(st);
-  // Âðåìÿ ïîòåðè öåëè
+  // Время потери цели
   FLastVisible := utils.readLongWord(st);
-  // Êîëè÷åñòâî ôëàãîâ ÈÈ
+  // Количество флагов ИИ
   dw := utils.readLongInt(st);
   if (dw < 0) or (dw > 16384) then raise XStreamError.Create('invalid number of bot AI flags');
   SetLength(FAIFlags, dw);
-  // Ôëàãè ÈÈ
+  // Флаги ИИ
   for i := 0 to dw-1 do
   begin
     FAIFlags[i].Name := utils.readStr(st, 20);
     FAIFlags[i].Value := utils.readStr(st, 20);
   end;
-  // Íàñòðîéêè ñëîæíîñòè
+  // Настройки сложности
   FDifficult.load(st);
 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.