DEADSOFTWARE

game: remove unneded render imports
[d2df-sdl.git] / src / game / g_player.pas
index 28c28b15f9e118a74f9c3940d6da46da8bb42656..92afc636f73aa29a5a0922fbc8493cb8b8b7bf28 100644 (file)
@@ -1,11 +1,29 @@
+(* Copyright (C)  Doom 2D: Forever Developers
+ *
+ * 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, 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
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *)
+{$INCLUDE ../shared/a_modes.inc}
+{$M+}
 unit g_player;
 
 interface
 
 uses
-  e_graphics, g_playermodel, g_basic, g_textures,
-  g_weapons, g_phys, g_sound, g_saveload, MAPSTRUCT,
-  BinEditor, g_panel;
+  SysUtils, Classes,
+  {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
+  g_base, g_playermodel, g_basic, g_textures,
+  g_weapons, g_phys, g_sound, g_saveload, MAPDEF,
+  g_panel;
 
 const
   KEY_LEFT       = 1;
@@ -34,6 +52,12 @@ const
   A_SHELLS          = 1;
   A_ROCKETS         = 2;
   A_CELLS           = 3;
+  A_FUEL            = 4;
+  A_HIGH            = 4;
+
+  AmmoLimits: Array [0..1] of Array [A_BULLETS..A_HIGH] of Word =
+  ((200,  50,  50, 300, 100),
+   (400, 100, 100, 600, 200));
 
   K_SIMPLEKILL      = 0;
   K_HARDKILL        = 1;
@@ -70,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;
@@ -86,6 +125,7 @@ type
     Kills: Word;
     Color: TRGB;
     Spectator: Boolean;
+    UID: Word;
   end;
 
   TPlayerStatArray = Array of TPlayerStat;
@@ -96,11 +136,13 @@ type
     Air:        Integer;
     JetFuel:    Integer;
     CurrWeap:   Byte;
-    Ammo:       Array [A_BULLETS..A_CELLS] of Word;
-    MaxAmmo:    Array [A_BULLETS..A_CELLS] of Word;
-    Weapon:     Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Boolean;
+    NextWeap:   WORD;
+    NextWeapDelay: Byte;
+    Ammo:       Array [A_BULLETS..A_HIGH] of Word;
+    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
@@ -108,13 +150,13 @@ type
     Time: Word;
   end;
 
-  TPlayer = class (TObject)
+  TPlayer = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
   private
     FIamBot:    Boolean;
     FUID:       Word;
     FName:      String;
     FTeam:      Byte;
-    FLive:      Boolean;
+    FAlive:     Boolean;
     FSpawned:   Boolean;
     FDirection: TDirection;
     FHealth:    Integer;
@@ -135,29 +177,39 @@ type
     FFlag:      Byte;
     FSecrets:   Integer;
     FCurrWeap:  Byte;
+    FNextWeap:  WORD;
+    FNextWeapDelay: Byte; // frames
     FBFGFireCounter: SmallInt;
     FLastSpawnerUID: Word;
     FLastHit:   Byte;
     FObj:       TObj;
     FXTo, FYTo: Integer;
     FSpectatePlayer: Integer;
+    FFirePainTime:   Integer;
+    FFireAttacker:   Word;
 
-    FSavedState: TPlayerSavedState;
+    FSavedStateNum:   Integer;
 
     FModel:     TPlayerModel;
+    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;
@@ -166,39 +218,50 @@ type
     FNoReload:  Boolean;
     FJustTeleported: Boolean;
     FNetTime: LongWord;
-
-    function    CollideLevel(XInc, YInc: Integer): Boolean;
-    function    StayOnStep(XInc, YInc: Integer): Boolean;
-    function    HeadInLiquid(XInc, YInc: Integer): Boolean;
-    function    BodyInLiquid(XInc, YInc: Integer): Boolean;
-    function    BodyInAcid(XInc, YInc: Integer): Boolean;
-    function    FullInLift(XInc, YInc: Integer): Integer;
-    {procedure   CollideItem();}
-    procedure   FlySmoke(Times: DWORD = 1);
-    function    GetAmmoByWeapon(Weapon: Byte): Word;
-    procedure   SetAction(Action: Byte; Force: Boolean = False);
-    procedure   OnDamage(Angle: SmallInt); virtual;
-    function    firediry(): Integer;
-
-    procedure   Run(Direction: TDirection);
-    procedure   NextWeapon();
-    procedure   PrevWeapon();
-    procedure   SeeUp();
-    procedure   SeeDown();
-    procedure   Fire();
-    procedure   Jump();
-    procedure   Use();
+    mEDamageType: Integer;
+
+
+    function CollideLevel(XInc, YInc: Integer): Boolean;
+    function StayOnStep(XInc, YInc: Integer): Boolean;
+    function HeadInLiquid(XInc, YInc: Integer): Boolean;
+    function BodyInLiquid(XInc, YInc: Integer): Boolean;
+    function BodyInAcid(XInc, YInc: Integer): Boolean;
+    function FullInLift(XInc, YInc: Integer): Integer;
+    {procedure CollideItem();}
+    procedure FlySmoke(Times: DWORD = 1);
+    procedure OnFireFlame(Times: DWORD = 1);
+    procedure SetAction(Action: Byte; Force: Boolean = False);
+    procedure OnDamage(Angle: SmallInt); virtual;
+    function firediry(): Integer;
+    procedure DoPunch();
+
+    procedure Run(Direction: TDirection);
+    procedure NextWeapon();
+    procedure PrevWeapon();
+    procedure SeeUp();
+    procedure SeeDown();
+    procedure Fire();
+    procedure Jump();
+    procedure Use();
+
+    function getNextWeaponIndex (): Byte; // return 255 for "no switch"
+    procedure resetWeaponQueue ();
+    function hasAmmoForWeapon (weapon: Byte): Boolean;
+
+    procedure doDamage (v: Integer);
+
+    function refreshCorpse(): Boolean;
 
   public
     FDamageBuffer:   Integer;
 
-    FAmmo:      Array [A_BULLETS..A_CELLS] of Word;
-    FMaxAmmo:   Array [A_BULLETS..A_CELLS] of Word;
-    FWeapon:    Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Boolean;
+    FAmmo:      Array [A_BULLETS..A_HIGH] of Word;
+    FMaxAmmo:   Array [A_BULLETS..A_HIGH] of Word;
+    FWeapon:    Array [WP_FIRST..WP_LAST] of Boolean;
     FRulez:     Set of R_ITEM_BACKPACK..R_BERSERK;
     FBerserk:   Integer;
     FMegaRulez: Array [MR_SUIT..MR_MAX] of DWORD;
-    FReloading: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Word;
+    FReloading: Array [WP_FIRST..WP_LAST] of Word;
     FTime:      Array [T_RESPAWN..T_FLAGCAP] of DWORD;
     FKeys:      Array [KEY_LEFT..KEY_CHAT] of TKeyState;
     FColor:     TRGB;
@@ -208,12 +271,24 @@ 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;
+
+    function isValidViewPort (): Boolean; inline;
 
     constructor Create(); virtual;
     destructor  Destroy(); override;
@@ -223,10 +298,11 @@ type
     procedure   ReleaseKeys();
     procedure   SetModel(ModelName: String);
     procedure   SetColor(Color: TRGB);
+    function    GetColor(): TRGB;
     procedure   SetWeapon(W: Byte);
     function    IsKeyPressed(K: Byte): Boolean;
     function    GetKeys(): Byte;
-    function    PickItem(ItemType: Byte; respawn: Boolean; var remove: Boolean): Boolean; virtual;
+    function    PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean; virtual;
     function    Collide(X, Y: Integer; Width, Height: Word): Boolean; overload;
     function    Collide(Panel: TPanel): Boolean; overload;
     function    Collide(X, Y: Integer): Boolean; overload;
@@ -241,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();
@@ -255,24 +331,37 @@ type
     procedure   Spectate(NoMove: Boolean = False);
     procedure   SwitchNoClip;
     procedure   SoftReset();
-    procedure   Draw(); virtual;
-    procedure   DrawPain();
-    procedure   DrawPickup();
-    procedure   DrawRulez();
-    procedure   DrawAim();
-    procedure   DrawBubble();
-    procedure   DrawGUI();
+    procedure   PreUpdate();
     procedure   Update(); virtual;
     procedure   RememberState();
     procedure   RecallState();
-    procedure   SaveState(var Mem: TBinMemoryWriter); virtual;
-    procedure   LoadState(var Mem: TBinMemoryReader); virtual;
+    procedure   SaveState (st: TStream); virtual;
+    procedure   LoadState (st: TStream); virtual;
     procedure   PauseSounds(Enable: Boolean);
     procedure   NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
     procedure   DoLerp(Level: Integer = 2);
     procedure   SetLerp(XTo, YTo: Integer);
+    procedure   QueueWeaponSwitch(Weapon: Byte);
+    procedure   RealizeCurrentWeapon();
+    procedure   FlamerOn;
+    procedure   FlamerOff;
     procedure   JetpackOn;
     procedure   JetpackOff;
+    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!
+
+    procedure getMapBox (out x, y, w, h: Integer); inline;
+    procedure moveBy (dx, dy: Integer); inline;
+
+    function getCameraObj(): TObj;
+
+    function GetAmmoByWeapon(Weapon: Byte): Word; // private state
+
+  public
+    property    Vel: TPoint2i read FObj.Vel;
+    property    Obj: TObj read FObj;
 
     property    Name: String read FName write FName;
     property    Model: TPlayerModel read FModel;
@@ -290,7 +379,7 @@ type
     property    GodMode: Boolean read FGodMode write FGodMode;
     property    NoTarget: Boolean read FNoTarget write FNoTarget;
     property    NoReload: Boolean read FNoReload write FNoReload;
-    property    Live: Boolean read FLive write FLive;
+    property    alive: Boolean read FAlive write FAlive;
     property    Flag: Byte read FFlag;
     property    Team: Byte read FTeam write FTeam;
     property    Direction: TDirection read FDirection;
@@ -300,24 +389,75 @@ type
     property    GameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
     property    GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
     property    GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
-    property    Vel: TPoint2i read FObj.Vel;
-    property    Obj: TObj read FObj;
     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;
+    property eLives: Byte read FLives write FLives;
+    property eArmor: Integer read FArmor write FArmor;
+    property eAir:   Integer read FAir write FAir;
+    property eJetFuel: Integer read FJetFuel write FJetFuel;
+    property eFrags: Integer read FFrags write FFrags;
+    property eDeath: Integer read FDeath write FDeath;
+    property eKills: Integer read FKills write FKills;
+    property eCurrWeap: Byte read FCurrWeap write FCurrWeap;
+    property eMonsterKills: Integer read FMonsterKills write FMonsterKills;
+    property eSecrets: Integer read FSecrets write FSecrets;
+    property eGodMode: Boolean read FGodMode write FGodMode;
+    property eNoTarget: Boolean read FNoTarget write FNoTarget;
+    property eNoReload: Boolean read FNoReload write FNoReload;
+    property eAlive: Boolean read FAlive write FAlive;
+    property eFlag: Byte read FFlag;
+    property eTeam: Byte read FTeam write FTeam;
+    property eDirection: TDirection read FDirection;
+    property eGameX: Integer read FObj.X write FObj.X;
+    property eGameY: Integer read FObj.Y write FObj.Y;
+    property eGameVelX: Integer read FObj.Vel.X write FObj.Vel.X;
+    property eGameVelY: Integer read FObj.Vel.Y write FObj.Vel.Y;
+    property eGameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
+    property eGameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
+    property eIncCam: Integer read FIncCam write FIncCam;
+    property eUID: Word read FUID;
+    property eJustTeleported: Boolean read FJustTeleported;
+    property eNetTime: LongWord read FNetTime;
+
+    // set this before assigning something to `eDamage`
+    property eDamageType: Integer read mEDamageType write mEDamageType;
+    property eDamage: Integer write doDamage;
   end;
 
   TDifficult = record
+  public
     DiagFire: Byte;
     InvisFire: Byte;
     DiagPrecision: Byte;
     FlyPrecision: Byte;
     Cover: Byte;
     CloseJump: Byte;
-    WeaponPrior: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
-    CloseWeaponPrior: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
-    //SafeWeaponPrior: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
+    WeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
+    CloseWeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
+    //SafeWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
+
+  public
+    procedure save (st: TStream);
+    procedure load (st: TStream);
   end;
 
   TAIFlag = record
@@ -325,7 +465,7 @@ type
     Value: String;
   end;
 
-  TBot = class (TPlayer)
+  TBot = class(TPlayer)
   private
     FSelectedWeapon:  Byte;
     FTargetUID:       Word;
@@ -339,9 +479,9 @@ type
     function    FullInStep(XInc, YInc: Integer): Boolean;
     //function    NeedItem(Item: Byte): Byte;
     procedure   SelectWeapon(Dist: Integer);
-    procedure   SetAIFlag(fName, fValue: String20);
-    function    GetAIFlag(fName: String20): String20;
-    procedure   RemoveAIFlag(fName: String20);
+    procedure   SetAIFlag(aName, fValue: String20);
+    function    GetAIFlag(aName: String20): String20;
+    procedure   RemoveAIFlag(aName: String20);
     function    Healthy(): Byte;
     procedure   UpdateMove();
     procedure   UpdateCombat();
@@ -354,56 +494,72 @@ 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;
-    procedure   SaveState(var Mem: TBinMemoryWriter); override;
-    procedure   LoadState(var Mem: TBinMemoryReader); override;
+    procedure   SaveState (st: TStream); override;
+    procedure   LoadState (st: TStream); override;
   end;
 
+  PGib = ^TGib;
   TGib = record
-    Live:     Boolean;
-    ID:       DWORD;
-    MaskID:   DWORD;
+    alive:    Boolean;
     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;
+
+    procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
   end;
 
+
+  PShell = ^TShell;
   TShell = record
-    SpriteID: DWORD;
-    Live:     Boolean;
+    alive:    Boolean;
     SType:    Byte;
     RAngle:   Integer;
     Timeout:  Cardinal;
-    CX, CY:   Integer;
     Obj:      TObj;
+
+    procedure getMapBox (out x, y, w, h: Integer); inline;
+    procedure moveBy (dx, dy: Integer); inline;
+
+    procedure positionChanged ();  inline; //WARNING! call this after entity position was changed, or coldet will not work right!
   end;
 
-  TCorpse = class (TObject)
+  TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
   private
-    FModelName:     String;
     FMess:          Boolean;
     FState:         Byte;
     FDamage:        Byte;
-    FColor:         TRGB;
     FObj:           TObj;
-    FAnimation:     TAnimation;
-    FAnimationMask: TAnimation;
+    FPlayerUID:     Word;
+    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(var Mem: TBinMemoryWriter);
-    procedure   LoadState(var Mem: TBinMemoryReader);
+    procedure   SaveState (st: TStream);
+    procedure   LoadState (st: TStream);
 
-    property    Obj: TObj read FObj;
+    procedure getMapBox (out x, y, w, h: Integer); inline;
+    procedure moveBy (dx, dy: Integer); inline;
+
+    procedure positionChanged ();  inline; //WARNING! call this after entity position was changed, or coldet will not work right!
+
+    function ObjPtr (): PObj; inline;
+
+    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
@@ -419,10 +575,12 @@ var
   gTeamStat: TTeamStat;
   gFly: Boolean = False;
   gAimLine: Boolean = False;
-  gChatBubble: Byte = 0;
+  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;
@@ -439,39 +597,43 @@ function  g_Shells_GetMax(): Word;
 procedure g_Player_Init();
 procedure g_Player_Free();
 function  g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
-function  g_Player_CreateFromState(var Mem: TBinMemoryReader): Word;
+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(var Mem: TBinMemoryWriter);
-procedure g_Player_Corpses_LoadState(var Mem: TBinMemoryReader);
-procedure g_Bot_Add(Team, Difficult: Byte);
-procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1);
+procedure g_Player_Corpses_SaveState (st: TStream);
+procedure g_Player_Corpses_LoadState (st: TStream);
+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
-  e_log, g_map, g_items, g_console, SysUtils, g_gfx, Math,
-  g_options, g_triggers, g_menu, MAPDEF, g_game,
-  wadreader, g_main, g_monsters, CONFIG, g_language, g_net, g_netmsg;
+{$IFDEF ENABLE_HOLMES}
+  g_holmes,
+{$ENDIF}
+  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;
 
 type
   TBotProfile = record
@@ -485,58 +647,51 @@ type
     fly_precision: Byte;
     cover: Byte;
     close_jump: Byte;
-    w_prior1: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
-    w_prior2: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
-    w_prior3: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte;
+    w_prior1: Array [WP_FIRST..WP_LAST] of Byte;
+    w_prior2: Array [WP_FIRST..WP_LAST] of Byte;
+    w_prior3: Array [WP_FIRST..WP_LAST] of Byte;
   end;
 
 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 TPoint = ((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); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0));
+                                WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
   DIFFICULT_MEDIUM: TDifficult = (DiagFire: 127; InvisFire: 127; DiagPrecision: 127;
                                   FlyPrecision: 127; Cover: 127; CloseJump: 127;
-                                WeaponPrior:(0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0));
+                                WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
   DIFFICULT_HARD: TDifficult = (DiagFire: 255; InvisFire: 255; DiagPrecision: 255;
                                 FlyPrecision: 255; Cover: 255; CloseJump: 255;
-                                WeaponPrior:(0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0));
-  WEAPON_PRIOR1: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte =
-                                (WEAPON_SUPERPULEMET, WEAPON_SHOTGUN2, WEAPON_SHOTGUN1,
+                                WeaponPrior:(0,0,0,0,0,0,0,0,0,0,0); CloseWeaponPrior:(0,0,0,0,0,0,0,0,0,0,0));
+  WEAPON_PRIOR1: Array [WP_FIRST..WP_LAST] of Byte =
+                                (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
+                                 WEAPON_SHOTGUN2, WEAPON_SHOTGUN1,
                                  WEAPON_CHAINGUN, WEAPON_PLASMA, WEAPON_ROCKETLAUNCHER,
                                  WEAPON_BFG, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
-  WEAPON_PRIOR2: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte =
-                                (WEAPON_SUPERPULEMET, WEAPON_BFG, WEAPON_ROCKETLAUNCHER,
+  WEAPON_PRIOR2: Array [WP_FIRST..WP_LAST] of Byte =
+                                (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
+                                 WEAPON_BFG, WEAPON_ROCKETLAUNCHER,
                                  WEAPON_SHOTGUN2, WEAPON_PLASMA, WEAPON_SHOTGUN1,
                                  WEAPON_CHAINGUN, WEAPON_PISTOL, WEAPON_SAW, WEAPON_KASTET);
-  //WEAPON_PRIOR3: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte =
-  //                              (WEAPON_SUPERPULEMET, WEAPON_BFG, WEAPON_PLASMA,
-  //                               WEAPON_SHOTGUN2, WEAPON_CHAINGUN, WEAPON_SHOTGUN1,
-  //                               WEAPON_SAW, WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_KASTET);
-  WEAPON_RELOAD: Array [WEAPON_KASTET..WEAPON_SUPERPULEMET] of Byte =
-                                (5, 2, 6, 18, 36, 2, 12, 2, 14, 2);
+  //WEAPON_PRIOR3: Array [WP_FIRST..WP_LAST] of Byte =
+  //                              (WEAPON_FLAMETHROWER, WEAPON_SUPERPULEMET,
+  //                               WEAPON_BFG, WEAPON_PLASMA, WEAPON_SHOTGUN2,
+  //                               WEAPON_CHAINGUN, WEAPON_SHOTGUN1, WEAPON_SAW,
+  //                               WEAPON_ROCKETLAUNCHER, WEAPON_PISTOL, WEAPON_KASTET);
+  WEAPON_RELOAD: Array [WP_FIRST..WP_LAST] of Byte =
+                                (5, 2, 6, 18, 36, 2, 12, 2, 14, 2, 2);
 
   PLAYER_SIGNATURE = $52594C50; // 'PLYR'
   CORPSE_SIGNATURE = $50524F43; // 'CORP'
@@ -552,6 +707,8 @@ var
   CurrentShell: Integer = 0;
   BotNames: Array of String;
   BotList: Array of TBotProfile;
+  SavedStates: Array of TPlayerSavedState;
+
 
 function Lerp(X, Y, Factor: Integer): Integer;
 begin
@@ -623,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
@@ -632,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
@@ -649,7 +806,7 @@ begin
   gPlayers[a].FActualModelName := ModelName;
   gPlayers[a].SetModel(ModelName);
 
-// Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî:
+// Нет модели - создание не возможно:
   if gPlayers[a].FModel = nil then
   begin
     gPlayers[a].Free();
@@ -673,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]
@@ -681,52 +838,41 @@ begin
     gPlayers[a].FModel.Color := Color;
 
   gPlayers[a].FUID := g_CreateUID(UID_PLAYER);
-  gPlayers[a].FLive := False;
+  gPlayers[a].FAlive := False;
 
   Result := gPlayers[a].FUID;
 end;
 
-function g_Player_CreateFromState(var Mem: TBinMemoryReader): Word;
+function g_Player_CreateFromState (st: TStream): Word;
 var
   a, i: Integer;
   ok, Bot: Boolean;
-  sig: DWORD;
   b: Byte;
 begin
-  Result := 0;
-  if Mem = nil then
-    Exit;
+  result := 0;
+  if (st = nil) then exit; //???
 
-// Ñèãíàòóðà èãðîêà:
-  Mem.ReadDWORD(sig);
-  if sig <> PLAYER_SIGNATURE then // 'PLYR'
-  begin
-    raise EBinSizeError.Create('g_Player_CreateFromState: Wrong Player Signature');
-  end;
+  // Сигнатура игрока
+  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');
 
-// Áîò èëè ÷åëîâåê:
-  Mem.ReadBoolean(Bot);
+  // Бот или человек:
+  Bot := utils.readBool(st);
 
-  ok := False;
+  ok := false;
   a := 0;
 
-// Åñòü ëè ìåñòî â gPlayers:
-  if gPlayers <> nil then
-    for a := 0 to High(gPlayers) do
-      if gPlayers[a] = nil then
-      begin
-        ok := True;
-        Break;
-      end;
+  // Есть ли место в 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
@@ -734,129 +880,117 @@ begin
   gPlayers[a].FIamBot := Bot;
   gPlayers[a].FPhysics := True;
 
-// UID èãðîêà:
-  Mem.ReadWord(gPlayers[a].FUID);
-// Èìÿ èãðîêà:
-  Mem.ReadString(gPlayers[a].FName);
-// Êîìàíäà:
-  Mem.ReadByte(gPlayers[a].FTeam);
+  // 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;
-// Æèâ ëè:
-  Mem.ReadBoolean(gPlayers[a].FLive);
-// Èçðàñõîäîâàë ëè âñå æèçíè:
-  Mem.ReadBoolean(gPlayers[a].FNoRespawn);
-// Íàïðàâëåíèå:
-  Mem.ReadByte(b);
-  if b = 1 then
-    gPlayers[a].FDirection := D_LEFT
-  else // b = 2
-    gPlayers[a].FDirection := D_RIGHT;
-// Çäîðîâüå:
-  Mem.ReadInt(gPlayers[a].FHealth);
-// Æèçíè:
-  Mem.ReadByte(gPlayers[a].FLives);
-// Áðîíÿ:
-  Mem.ReadInt(gPlayers[a].FArmor);
-// Çàïàñ âîçäóõà:
-  Mem.ReadInt(gPlayers[a].FAir);
-// Çàïàñ ãîðþ÷åãî:
-  Mem.ReadInt(gPlayers[a].FJetFuel);
-// Áîëü:
-  Mem.ReadInt(gPlayers[a].FPain);
-// Óáèë:
-  Mem.ReadInt(gPlayers[a].FKills);
-// Óáèë ìîíñòðîâ:
-  Mem.ReadInt(gPlayers[a].FMonsterKills);
-// Ôðàãîâ:
-  Mem.ReadInt(gPlayers[a].FFrags);
-// Ôðàãîâ ïîäðÿä:
-  Mem.ReadByte(gPlayers[a].FFragCombo);
-// Âðåìÿ ïîñëåäíåãî ôðàãà:
-  Mem.ReadDWORD(gPlayers[a].FLastFrag);
-// Ñìåðòåé:
-  Mem.ReadInt(gPlayers[a].FDeath);
-// Êàêîé ôëàã íåñåò:
-  Mem.ReadByte(gPlayers[a].FFlag);
-// Íàøåë ñåêðåòîâ:
-  Mem.ReadInt(gPlayers[a].FSecrets);
-// Òåêóùåå îðóæèå:
-  Mem.ReadByte(gPlayers[a].FCurrWeap);
-// Âðåìÿ çàðÿäêè BFG:
-  Mem.ReadSmallInt(gPlayers[a].FBFGFireCounter);
-// Áóôåð óðîíà:
-  Mem.ReadInt(gPlayers[a].FDamageBuffer);
-// Ïîñëåäíèé óäàðèâøèé:
-  Mem.ReadWord(gPlayers[a].FLastSpawnerUID);
-// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
-  Mem.ReadByte(gPlayers[a].FLastHit);
-// Îáúåêò èãðîêà:
-  Obj_LoadState(@gPlayers[a].FObj, Mem);
-// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_CELLS do
-    Mem.ReadWord(gPlayers[a].FAmmo[i]);
-// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_CELLS do
-    Mem.ReadWord(gPlayers[a].FMaxAmmo[i]);
-// Íàëè÷èå îðóæèÿ:
-  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
-    Mem.ReadBoolean(gPlayers[a].FWeapon[i]);
-// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
-  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
-    Mem.ReadWord(gPlayers[a].FReloading[i]);
-// Íàëè÷èå ðþêçàêà:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(gPlayers[a].FRulez, R_ITEM_BACKPACK);
-// Íàëè÷èå êðàñíîãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(gPlayers[a].FRulez, R_KEY_RED);
-// Íàëè÷èå çåëåíîãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(gPlayers[a].FRulez, R_KEY_GREEN);
-// Íàëè÷èå ñèíåãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(gPlayers[a].FRulez, R_KEY_BLUE);
-// Íàëè÷èå áåðñåðêà:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(gPlayers[a].FRulez, R_BERSERK);
-// Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ:
-  for i := MR_SUIT to MR_MAX do
-    Mem.ReadDWORD(gPlayers[a].FMegaRulez[i]);
-// Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà:
-  for i := T_RESPAWN to T_FLAGCAP do
-    Mem.ReadDWORD(gPlayers[a].FTime[i]);
-
-// Íàçâàíèå ìîäåëè:
-  Mem.ReadString(gPlayers[a].FActualModelName);
-// Öâåò ìîäåëè:
-  Mem.ReadByte(gPlayers[a].FColor.R);
-  Mem.ReadByte(gPlayers[a].FColor.G);
-  Mem.ReadByte(gPlayers[a].FColor.B);
-// Îáíîâëÿåì ìîäåëü èãðîêà:
+  // Жив ли
+  gPlayers[a].FAlive := utils.readBool(st);
+  // Израсходовал ли все жизни
+  gPlayers[a].FNoRespawn := utils.readBool(st);
+  // Направление
+  b := utils.readByte(st);
+  if b = 1 then gPlayers[a].FDirection := TDirection.D_LEFT else gPlayers[a].FDirection := TDirection.D_RIGHT; // b = 2
+  // Здоровье
+  gPlayers[a].FHealth := utils.readLongInt(st);
+  // Фора
+  gPlayers[a].FHandicap := utils.readLongInt(st);
+  // Жизни
+  gPlayers[a].FLives := utils.readByte(st);
+  // Броня
+  gPlayers[a].FArmor := utils.readLongInt(st);
+  // Запас воздуха
+  gPlayers[a].FAir := utils.readLongInt(st);
+  // Запас горючего
+  gPlayers[a].FJetFuel := utils.readLongInt(st);
+  // Боль
+  gPlayers[a].FPain := utils.readLongInt(st);
+  // Убил
+  gPlayers[a].FKills := utils.readLongInt(st);
+  // Убил монстров
+  gPlayers[a].FMonsterKills := utils.readLongInt(st);
+  // Фрагов
+  gPlayers[a].FFrags := utils.readLongInt(st);
+  // Фрагов подряд
+  gPlayers[a].FFragCombo := utils.readByte(st);
+  // Время последнего фрага
+  gPlayers[a].FLastFrag := utils.readLongWord(st);
+  // Смертей
+  gPlayers[a].FDeath := utils.readLongInt(st);
+  // Какой флаг несет
+  gPlayers[a].FFlag := utils.readByte(st);
+  // Нашел секретов
+  gPlayers[a].FSecrets := utils.readLongInt(st);
+  // Текущее оружие
+  gPlayers[a].FCurrWeap := utils.readByte(st);
+  // Следующее желаемое оружие
+  gPlayers[a].FNextWeap := utils.readWord(st);
+  // ...и пауза
+  gPlayers[a].FNextWeapDelay := utils.readByte(st);
+  // Время зарядки BFG
+  gPlayers[a].FBFGFireCounter := utils.readSmallInt(st);
+  // Буфер урона
+  gPlayers[a].FDamageBuffer := utils.readLongInt(st);
+  // Последний ударивший
+  gPlayers[a].FLastSpawnerUID := utils.readWord(st);
+  // Тип последнего полученного урона
+  gPlayers[a].FLastHit := utils.readByte(st);
+  // Объект игрока:
+  Obj_LoadState(@gPlayers[a].FObj, st);
+  // Текущее количество патронов
+  for i := A_BULLETS to A_HIGH do gPlayers[a].FAmmo[i] := utils.readWord(st);
+  // Максимальное количество патронов
+  for i := A_BULLETS to A_HIGH do gPlayers[a].FMaxAmmo[i] := utils.readWord(st);
+  // Наличие оружия
+  for i := WP_FIRST to WP_LAST do gPlayers[a].FWeapon[i] := utils.readBool(st);
+  // Время перезарядки оружия
+  for i := WP_FIRST to WP_LAST do gPlayers[a].FReloading[i] := utils.readWord(st);
+  // Наличие рюкзака
+  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_ITEM_BACKPACK);
+  // Наличие красного ключа
+  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_RED);
+  // Наличие зеленого ключа
+  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_GREEN);
+  // Наличие синего ключа
+  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_BLUE);
+  // Наличие берсерка
+  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_BERSERK);
+  // Время действия специальных предметов
+  for i := MR_SUIT to MR_MAX do gPlayers[a].FMegaRulez[i] := utils.readLongWord(st);
+  // Время до повторного респауна, смены оружия, исользования, захвата флага
+  for i := T_RESPAWN to T_FLAGCAP do gPlayers[a].FTime[i] := utils.readLongWord(st);
+
+  // Название модели:
+  gPlayers[a].FActualModelName := utils.readStr(st);
+  // Цвет модели
+  gPlayers[a].FColor.R := utils.readByte(st);
+  gPlayers[a].FColor.G := utils.readByte(st);
+  gPlayers[a].FColor.B := utils.readByte(st);
+  // Обновляем модель игрока
   gPlayers[a].SetModel(gPlayers[a].FActualModelName);
 
-// Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî:
-  if gPlayers[a].FModel = nil then
+  // Нет модели - создание невозможно
+  if (gPlayers[a].FModel = nil) then
   begin
     gPlayers[a].Free();
     gPlayers[a] := nil;
     g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [gPlayers[a].FActualModelName]));
-    Exit;
+    exit;
   end;
 
-// Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû:
+  // Если командная игра - красим модель в цвет команды
   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
     gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
   else
     gPlayers[a].FModel.Color := gPlayers[a].FColor;
 
-  Result := gPlayers[a].FUID;
+  result := gPlayers[a].FUID;
 end;
 
+
 procedure g_Player_ResetTeams();
 var
   a: Integer;
@@ -885,20 +1019,20 @@ begin
       end;
 end;
 
-procedure g_Bot_Add(Team, Difficult: Byte);
+procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
 var
-  m: SArray;
+  m: SSArray;
   _name, _model: String;
   a, tr, tb: Integer;
 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
@@ -907,7 +1041,7 @@ begin
     else
       if Team = TEAM_NONE then // CTF / TDM
       begin
-       // Àâòîáàëàíñ êîìàíä:
+       // Автобаланс команд:
         tr := 0;
         tb := 0;
 
@@ -933,7 +1067,7 @@ begin
               Team := TEAM_BLUE;
       end;
 
-// Âûáèðàåì áîòó èìÿ:
+// Выбираем боту имя:
   _name := '';
   if BotNames <> nil then
     for a := 0 to High(BotNames) do
@@ -943,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;
@@ -967,13 +1099,15 @@ begin
       else FDifficult := DIFFICULT_HARD;
     end;
 
-    for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do
+    for a := WP_FIRST to WP_LAST do
     begin
       FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
       FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
       //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);
@@ -982,20 +1116,20 @@ 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: SArray;
+  m: SSArray;
   _name, _model: String;
   a: Integer;
 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
@@ -1005,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;
@@ -1019,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;
@@ -1045,7 +1179,9 @@ begin
     FDifficult.Cover := BotList[num].cover;
     FDifficult.CloseJump := BotList[num].close_jump;
 
-    for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do
+    FHandicap := Handicap;
+
+    for a := WP_FIRST to WP_LAST do
     begin
       FDifficult.WeaponPrior[a] := BotList[num].w_prior1[a];
       FDifficult.CloseWeaponPrior[a] := BotList[num].w_prior2[a];
@@ -1121,15 +1257,17 @@ var
   s: String;
   a, b: Integer;
   config: TConfig;
-  sa: SArray;
+  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
@@ -1146,11 +1284,11 @@ begin
 
   CloseFile(F);
 
-// Ïåðåìåøèâàåì èõ:
+// Перемешиваем их:
   g_Bot_MixNames();
 
-// ×èòàåì ôàéë ñ ïàðàìåòðàìè áîòîâ:
-  config := TConfig.CreateFile(DataDir + BOTLIST_FILENAME);
+// Читаем файл с параметрами ботов:
+  config := TConfig.CreateFile(path);
   BotList := nil;
   a := 0;
 
@@ -1160,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
@@ -1207,6 +1345,7 @@ begin
   end;
 
   config.Free();
+  SetLength(SavedStates, 0);
 end;
 
 procedure g_Player_Free();
@@ -1230,67 +1369,43 @@ begin
 
   gPlayer1 := nil;
   gPlayer2 := nil;
+  SetLength(SavedStates, 0);
 end;
 
-procedure g_Player_UpdateAll();
-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].Update()
-      else TBot(gPlayers[i]).Update();
-end;
-
-procedure g_Player_DrawAll();
+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
-      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);
+      gPlayers[i].PreUpdate();
 end;
 
-procedure g_Player_DrawHealth();
+procedure g_Player_UpdateAll();
 var
   i: Integer;
-  fW, fH: Byte;
 begin
   if gPlayers = nil then Exit;
-  e_TextureFontGetSize(gStdFont, fW, fH);
 
+  //e_WriteLog('***g_Player_UpdateAll: ENTER', MSG_WARNING);
   for i := 0 to High(gPlayers) do
+  begin
     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);
+      if gPlayers[i] is TPlayer then
+      begin
+        gPlayers[i].Update();
+        gPlayers[i].RealizeCurrentWeapon(); // WARNING! DO NOT MOVE THIS INTO `Update()`!
+      end
+      else
+      begin
+        // bot updates weapons in `UpdateCombat()`
+        TBot(gPlayers[i]).Update();
+      end;
     end;
+  end;
+  //e_WriteLog('***g_Player_UpdateAll: EXIT', MSG_WARNING);
 end;
 
 function g_Player_Get(UID: Word): TPlayer;
@@ -1339,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;
@@ -1349,16 +1465,33 @@ 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;
 begin
   for i := Low(gPlayers) to High(gPlayers) do
-    if (gPlayers[i] <> nil) and gPlayers[i].Live then
+    if (gPlayers[i] <> nil) and gPlayers[i].alive then
       gPlayers[i].RememberState;
 end;
 
@@ -1387,13 +1520,25 @@ 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
-  if Player.Live then
+  Result := -1;
+
+  if Player.alive then
     Exit;
+
+// Разрываем связь с прежним трупом:
+  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;
 
@@ -1415,54 +1560,45 @@ 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;
     SType := T;
-    Live := True;
+    alive := True;
     Obj.X := fX;
     Obj.Y := fY;
     g_Obj_Push(@Obj, dX + Random(4)-Random(4), dY-Random(4));
+    positionChanged(); // this updates spatial accelerators
     RAngle := Random(360);
     Timeout := gTime + SHELL_TIMEOUT;
 
@@ -1473,33 +1609,38 @@ 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 := 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;
-      Live := True;
+      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);
 
       if gBloodCount > 0 then
         g_GFX_Blood(fX, fY, 16*gBloodCount+Random(5*gBloodCount), -16+Random(33), -16+Random(33),
-                    Random(48), Random(48), 150, 0, 0);
+                    Random(48), Random(48), Blood.R, Blood.G, Blood.B, Blood.Kind);
 
       if CurrentGib >= High(gGibs) then
         CurrentGib := 0
@@ -1526,22 +1667,26 @@ var
   end;
 
 begin
-// Êóñêè ìÿñà:
+// Куски мяса:
   if gGibs <> nil then
     for i := 0 to High(gGibs) do
-      if gGibs[i].Live then
+      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
 
           if WordBool(mr and MOVE_FALLOUT) then
           begin
-            Live := False;
+            alive := False;
             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
@@ -1558,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
@@ -1575,22 +1720,26 @@ begin
         else
           gCorpses[i].Update();
 
-// Ãèëüçû:
+// Гильзы:
   if gShells <> nil then
     for i := 0 to High(gShells) do
-      if gShells[i].Live then
+      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
 
           if WordBool(mr and MOVE_FALLOUT) or (gShells[i].Timeout < gTime) then
           begin
-            Live := False;
+            alive := False;
             Continue;
           end;
 
-        // Îòëåòàåò îò óäàðà î ñòåíó/ïîòîëîê/ïîë:
+        // Отлетает от удара о стену/потолок/пол:
           if WordBool(mr and MOVE_HITWALL) then
           begin
             Obj.Vel.X := -(vel.X div 2);
@@ -1623,56 +1772,48 @@ begin
         end;
 end;
 
-procedure g_Player_DrawCorpses();
-var
-  i: Integer;
-  a: TPoint;
-begin
-  if gGibs <> nil then
-    for i := 0 to High(gGibs) do
-      if gGibs[i].Live 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);
+procedure TGib.getMapBox (out x, y, w, h: Integer); inline;
+begin
+  x := Obj.X+Obj.Rect.X;
+  y := Obj.Y+Obj.Rect.Y;
+  w := Obj.Rect.Width;
+  h := Obj.Rect.Height;
+end;
 
-          e_DrawAdv(ID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE);
+procedure TGib.moveBy (dx, dy: Integer); inline;
+begin
+  if (dx <> 0) or (dy <> 0) then
+  begin
+    Obj.X += dx;
+    Obj.Y += dy;
+    positionChanged();
+  end;
+end;
 
-          e_Colors := Color;
-          e_DrawAdv(MaskID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_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();
+procedure TShell.getMapBox (out x, y, w, h: Integer); inline;
+begin
+  x := Obj.X;
+  y := Obj.Y;
+  w := Obj.Rect.Width;
+  h := Obj.Rect.Height;
 end;
 
-procedure g_Player_DrawShells();
-var
-  i: Integer;
-  a: TPoint;
+procedure TShell.moveBy (dx, dy: Integer); inline;
 begin
-  if gShells <> nil then
-    for i := 0 to High(gShells) do
-      if gShells[i].Live then
-        with gShells[i] do
-        begin
-          if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
-            Continue;
+  if (dx <> 0) or (dy <> 0) then
+  begin
+    Obj.X += dx;
+    Obj.Y += dy;
+    positionChanged();
+  end;
+end;
 
-          a.X := CX;
-          a.Y := CY;
 
-          e_DrawAdv(SpriteID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE);
-        end;
-end;
+procedure TGib.positionChanged (); inline; begin end;
+procedure TShell.positionChanged (); inline; begin end;
+
 
 procedure g_Player_RemoveAllCorpses();
 var
@@ -1693,78 +1834,70 @@ begin
   SetLength(gCorpses, MaxCorpses);
 end;
 
-procedure g_Player_Corpses_SaveState(var Mem: TBinMemoryWriter);
+procedure g_Player_Corpses_SaveState (st: TStream);
 var
   count, i: Integer;
-  b: Boolean;
 begin
-// Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðóïîâ:
+  // Считаем количество существующих трупов
   count := 0;
-  if gCorpses <> nil then
-    for i := 0 to High(gCorpses) do
-      if gCorpses[i] <> nil then
-        count := count + 1;
+  for i := 0 to High(gCorpses) do if (gCorpses[i] <> nil) then Inc(count);
 
-  Mem := TBinMemoryWriter.Create((count+1) * 128);
+  // Количество трупов
+  utils.writeInt(st, LongInt(count));
 
-// Êîëè÷åñòâî òðóïîâ:
-  Mem.WriteInt(count);
-
-  if count = 0 then
-    Exit;
+  if (count = 0) then exit;
 
-// Ñîõðàíÿåì òðóïû:
+  // Сохраняем трупы
   for i := 0 to High(gCorpses) do
+  begin
     if gCorpses[i] <> nil then
     begin
-    // Íàçâàíèå ìîäåëè:
-      Mem.WriteString(gCorpses[i].FModelName);
-    // Òèï ñìåðòè:
-      b := gCorpses[i].Mess;
-      Mem.WriteBoolean(b);
-    // Ñîõðàíÿåì äàííûå òðóïà:
-      gCorpses[i].SaveState(Mem);
+      // Название модели
+      utils.writeStr(st, gCorpses[i].FModel.GetName());
+      // Тип смерти
+      utils.writeBool(st, gCorpses[i].Mess);
+      // Сохраняем данные трупа:
+      gCorpses[i].SaveState(st);
     end;
+  end;
 end;
 
-procedure g_Player_Corpses_LoadState(var Mem: TBinMemoryReader);
+
+procedure g_Player_Corpses_LoadState (st: TStream);
 var
   count, i: Integer;
   str: String;
   b: Boolean;
 begin
-  if Mem = nil then
-    Exit;
+  assert(st <> nil);
 
   g_Player_RemoveAllCorpses();
 
-// Êîëè÷åñòâî òðóïîâ:
-  Mem.ReadInt(count);
+  // Количество трупов:
+  count := utils.readLongInt(st);
+  if (count < 0) or (count > Length(gCorpses)) then raise XStreamError.Create('invalid number of corpses');
 
-  if count > Length(gCorpses) then
-  begin
-    raise EBinSizeError.Create('g_Player_Corpses_LoadState: Too Many Corpses');
-  end;
-
-  if count = 0 then
-    Exit;
+  if (count = 0) then exit;
 
-// Çàãðóæàåì òðóïû:
+  // Загружаем трупы
   for i := 0 to count-1 do
   begin
-  // Íàçâàíèå ìîäåëè:
-    Mem.ReadString(str);
-  // Òèï ñìåðòè:
-    Mem.ReadBoolean(b);
-  // Ñîçäàåì òðóï:
+    // Название модели:
+    str := utils.readStr(st);
+    // Тип смерти
+    b := utils.readBool(st);
+    // Создаем труп
     gCorpses[i] := TCorpse.Create(0, 0, str, b);
-  // Çàãðóæàåì äàííûå òðóïà:
-    gCorpses[i].LoadState(Mem);
+    // Загружаем данные трупа
+    gCorpses[i].LoadState(st);
   end;
 end;
 
+
 { T P l a y e r : }
 
+function TPlayer.isValidViewPort (): Boolean; inline; begin result := (viewPortW > 0) and (viewPortH > 0); end;
+
 procedure TPlayer.BFGHit();
 begin
   g_Weapon_BFGHit(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
@@ -1777,13 +1910,13 @@ end;
 
 procedure TPlayer.ChangeModel(ModelName: string);
 var
-  Model: TPlayerModel;
+  locModel: TPlayerModel;
 begin
-  Model := g_PlayerModel_Get(ModelName);
-  if Model = nil then Exit;
+  locModel := g_PlayerModel_Get(ModelName);
+  if locModel = nil then Exit;
 
   FModel.Free();
-  FModel := Model;
+  FModel := locModel;
 end;
 
 procedure TPlayer.SetModel(ModelName: string);
@@ -1823,13 +1956,18 @@ 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
     Exit;
   if not (gGameSettings.GameMode in [GM_TDM, GM_CTF]) then Exit;
 
-  if gGameOn and FLive then
+  if gGameOn and FAlive then
     Kill(K_SIMPLEKILL, FUID, HIT_SELF);
 
   if FTeam = TEAM_RED then
@@ -1872,12 +2010,12 @@ var
   r: Boolean;
 begin
  if gItems = nil then Exit;
- if not FLive then Exit;
+ if not FAlive then Exit;
 
  for i := 0 to High(gItems) do
   with gItems[i] do
   begin
-   if (ItemType <> ITEM_NONE) and Live then
+   if (ItemType <> ITEM_NONE) and alive then
     if g_Obj_Collide(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width,
                      PLAYER_RECT.Height, @Obj) then
    begin
@@ -1889,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
@@ -1908,6 +2046,12 @@ end;
 
 constructor TPlayer.Create();
 begin
+  viewPortX := 0;
+  viewPortY := 0;
+  viewPortW := 0;
+  viewPortH := 0;
+  mEDamageType := HIT_SOME;
+
   FIamBot := False;
   FDummy := False;
   FSpawned := False;
@@ -1916,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();
@@ -1924,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');
@@ -1932,8 +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';
 
@@ -1943,18 +2098,35 @@ begin
   FBFGFireCounter := -1;
   FJustTeleported := False;
   FNetTime := 0;
+
+  FWaitForFirstSpawn := false;
+  FPunchAnim := TAnimationState.Create(False, 1, 4);
+  FPunchAnim.Disable;
+
+  resetWeaponQueue();
+end;
+
+procedure TPlayer.positionChanged (); inline;
+begin
+end;
+
+procedure TPlayer.doDamage (v: Integer);
+begin
+  if (v <= 0) then exit;
+  if (v > 32767) then v := 32767;
+  Damage(v, 0, 0, 0, mEDamageType);
 end;
 
 procedure TPlayer.Damage(value: Word; SpawnerUID: Word; vx, vy: Integer; t: Byte);
 var
   c: Word;
 begin
-  if (not g_Game_IsClient) and (not FLive) then
+  if (not g_Game_IsClient) and (not FAlive) then
     Exit;
 
   FLastHit := t;
 
-// Íåóÿçâèìîñòü íå ñïàñàåò îò ëîâóøåê:
+// Неуязвимость не спасает от ловушек:
   if ((t = HIT_TRAP) or (t = HIT_SELF)) and (not FGodMode) then
   begin
     if not g_Game_IsClient then
@@ -1962,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);
@@ -2017,11 +2190,11 @@ begin
                       FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
     end;
 
-  // Áóôåð óðîíà:
-    if FLive then
+  // Буфер урона:
+    if FAlive then
       Inc(FDamageBuffer, value);
 
-  // Âñïûøêà áîëè:
+  // Вспышка боли:
     if gFlash <> 0 then
       FPain := FPain + value;
   end;
@@ -2039,7 +2212,7 @@ begin
   Result := False;
   if g_Game_IsClient then
     Exit;
-  if not FLive then
+  if not FAlive then
     Exit;
 
   if Soft and (FHealth < PLAYER_HP_SOFT) then
@@ -2067,572 +2240,99 @@ begin
   FSawSound.Free();
   FSawSoundIdle.Free();
   FSawSoundHit.Free();
+  FSawSoundSelect.Free();
+  FFlameSoundOn.Free();
+  FFlameSoundOff.Free();
+  FFlameSoundWork.Free();
   FJetSoundFly.Free();
   FJetSoundOn.Free();
   FJetSoundOff.Free();
   FModel.Free();
+  FPunchAnim.Free();
 
   inherited;
 end;
 
-procedure TPlayer.DrawBubble();
+procedure TPlayer.DoPunch();
+begin
+  FPunchAnim.Reset;
+  FPunchAnim.Enable;
+end;
+
+procedure TPlayer.Fire();
 var
-  bubX, bubY: Integer;
-  ID: LongWord;
-  Rb, Gb, Bb,
-  Rw, Gw, Bw: SmallInt;
-  Dot: Byte;
-begin
-  bubX := FObj.X+FObj.Rect.X + IfThen(FDirection = 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 = D_RIGHT then
-          e_Draw(ID, bubX - 6, bubY - 7, 0, True, False)
-        else
-          e_Draw(ID, bubX - 6, bubY - 7, 0, True, False, M_HORIZONTAL);
-      Exit;
-    end;
+  f, DidFire: Boolean;
+  wx, wy, xd, yd: Integer;
+  locobj: TObj;
+begin
+  if g_Game_IsClient then Exit;
+// FBFGFireCounter - время перед выстрелом (для BFG)
+// FReloading - время после выстрела (для всего)
+
+  if FSpectator then
+  begin
+    Respawn(False);
+    Exit;
   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);
+  if FReloading[FCurrWeap] <> 0 then Exit;
 
-  // Tail
-  Dot := IfThen(FDirection = D_LEFT, 14, 5);
-  e_DrawLine(1, bubX + Dot, bubY + 14, bubX + Dot, bubY + 16, Rb, Gb, Bb);
-  e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 1, Dot + 1), bubY + 13, bubX + IfThen(FDirection = D_LEFT, Dot - 1, Dot + 1), bubY + 15, Rw, Gw, Bw);
-  e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 2, Dot + 2), bubY + 13, bubX + IfThen(FDirection = D_LEFT, Dot - 2, Dot + 2), bubY + 14, Rw, Gw, Bw);
-  e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 3, Dot + 3), bubY + 13, bubX + IfThen(FDirection = D_LEFT, Dot - 3, Dot + 3), bubY + 13, Rw, Gw, Bw);
-  e_DrawLine(1, bubX + IfThen(FDirection = D_LEFT, Dot - 3, Dot + 3), bubY + 14, bubX + IfThen(FDirection = D_LEFT, Dot - 1, Dot + 1), bubY + 16, Rb, Gb, Bb);
+  DidFire := False;
 
-  // 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;
+  f := False;
+  wx := FObj.X+WEAPONPOINT[FDirection].X;
+  wy := FObj.Y+WEAPONPOINT[FDirection].Y;
+  xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
+  yd := wy+firediry();
 
-procedure TPlayer.Draw();
-var
-  ID: DWORD;
-  w, h: Word;
-begin
-  if FLive then
-  begin
-    if (FMegaRulez[MR_INVUL] > gTime) and (gPlayerDrawn <> Self) then
-      if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
+  case FCurrWeap of
+    WEAPON_KASTET:
+    begin
+      DoPunch();
+      if R_BERSERK in FRulez then
       begin
-        e_GetTextureSize(ID, @w, @h);
-        if FDirection = 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, 0, True, False)
+        //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
+        locobj.X := FObj.X+FObj.Rect.X;
+        locobj.Y := FObj.Y+FObj.Rect.Y;
+        locobj.rect.X := 0;
+        locobj.rect.Y := 0;
+        locobj.rect.Width := 39;
+        locobj.rect.Height := 52;
+        locobj.Vel.X := (xd-wx) div 2;
+        locobj.Vel.Y := (yd-wy) div 2;
+        locobj.Accel.X := xd-wx;
+        locobj.Accel.y := yd-wy;
+
+        if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
+          g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
         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, 0, True, False);
-      end;
+          g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
 
-    if FMegaRulez[MR_INVIS] > gTime then
-    begin
-      if (gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or
-         ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM))) then
-        FModel.Draw(FObj.X, FObj.Y, 200)
+        if (gFlash = 1) and (FPain < 50) then FPain := min(FPain + 25, 50);
+      end
       else
-        FModel.Draw(FObj.X, FObj.Y, 254);
-    end
-    else
-      FModel.Draw(FObj.X, FObj.Y);
-  end;
+      begin
+        g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
+      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;
+      DidFire := True;
+      FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
+    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 Live and
-  ((Self = gPlayer1) or (Self = gPlayer2)) then
-    DrawAim();
-end;
-
-procedure TPlayer.DrawAim();
-var
-  wx, wy, xx, yy: Integer;
-  angle: SmallInt;
-  sz, len: Word;
-begin
-  wx := FObj.X + WEAPONPOINT[FDirection].X + IfThen(FDirection = 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;
-  e_DrawLine(sz, wx, wy, xx, yy, 255, 0, 0, 96);
-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];
-  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, B_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, B_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, B_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.Fire();
-var
-  f, DidFire: Boolean;
-  wx, wy, xd, yd: Integer;
-  obj: TObj;
-begin
-  if g_Game_IsClient then Exit;
-// FBFGFireCounter - âðåìÿ ïåðåä âûñòðåëîì (äëÿ BFG)
-// FReloading - âðåìÿ ïîñëå âûñòðåëà (äëÿ âñåãî)
-
-  if FSpectator then
-  begin
-    Respawn(False);
-    Exit;
-  end;
-
-  if FReloading[FCurrWeap] <> 0 then Exit;
-
-  DidFire := False;
-
-  f := False;
-  wx := FObj.X+WEAPONPOINT[FDirection].X;
-  wy := FObj.Y+WEAPONPOINT[FDirection].Y;
-  xd := wx+IfThen(FDirection = D_LEFT, -30, 30);
-  yd := wy+firediry();
-
-  case FCurrWeap of
-    WEAPON_KASTET:
-    begin
-      if R_BERSERK in FRulez then
-      begin
-        //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
-        obj.X := FObj.X+FObj.Rect.X;
-        obj.Y := FObj.Y+FObj.Rect.Y;
-        obj.rect.X := 0;
-        obj.rect.Y := 0;
-        obj.rect.Width := 39;
-        obj.rect.Height := 52;
-        obj.Vel.X := (xd-wx) div 2;
-        obj.Vel.Y := (yd-wy) div 2;
-        obj.Accel.X := xd-wx;
-        obj.Accel.y := yd-wy;
-
-        if g_Weapon_Hit(@obj, 50, FUID, HIT_SOME) <> 0 then
-          g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
-        else
-          g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
-
-        if gFlash = 1 then
-          if FPain < 50 then
-            FPain := min(FPain + 25, 50);
-      end else g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
-
-      DidFire := True;
-      FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
-    end;
-
-    WEAPON_SAW:
-    begin
-      if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
-                           IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
-      begin
-        FSawSoundSelect.Stop();
-        FSawSound.Stop();
-        FSawSoundHit.PlayAt(FObj.X, FObj.Y);
-      end
-      else if not FSawSoundHit.IsPlaying() then
-      begin
-        FSawSoundSelect.Stop();
-        FSawSound.PlayAt(FObj.X, FObj.Y);
-      end;
+    WEAPON_SAW:
+    begin
+      if g_Weapon_chainsaw(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
+                           IfThen(gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF], 9, 3), FUID) <> 0 then
+      begin
+        FSawSoundSelect.Stop();
+        FSawSound.Stop();
+        FSawSoundHit.PlayAt(FObj.X, FObj.Y);
+      end
+      else if not FSawSoundHit.IsPlaying() then
+      begin
+        FSawSoundSelect.Stop();
+        FSawSound.PlayAt(FObj.X, FObj.Y);
+      end;
 
       FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
       DidFire := True;
@@ -2738,6 +2438,23 @@ begin
         g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
                              GameVelX, GameVelY-2, SHELL_SHELL);
       end;
+
+    WEAPON_FLAMETHROWER:
+      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;
 
   if g_Game_IsNet then
@@ -2768,6 +2485,7 @@ begin
     WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS];
     WEAPON_ROCKETLAUNCHER: Result := FAmmo[A_ROCKETS];
     WEAPON_PLASMA, WEAPON_BFG: Result := FAmmo[A_CELLS];
+    WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL];
     else Result := 0;
   end;
 end;
@@ -2779,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;
@@ -2796,11 +2543,27 @@ begin
   FJetSoundOff.PlayAt(FObj.X, FObj.Y);
 end;
 
+procedure TPlayer.CatchFire(Attacker: Word; Timeout: Integer = PLAYER_BURN_TIME);
+begin
+  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);
+end;
+
 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
@@ -2818,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;
@@ -2856,22 +2619,34 @@ var
   DoFrags: Boolean;
   OldLR: Byte;
   KP: TPlayer;
+  it: PItem;
 
   procedure PushItem(t: Byte);
   var
     id: DWORD;
   begin
     id := g_Items_Create(FObj.X, FObj.Y, t, True, False);
+    it := g_Items_ByIdx(id);
     if KillType = K_EXTRAHARDKILL then // -7..+7; -8..0
-      g_Obj_Push(@gItems[id].Obj, (FObj.Vel.X div 2)-7+Random(15),
-                                  (FObj.Vel.Y div 2)-Random(9))
+    begin
+      g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-7+Random(15),
+                          (FObj.Vel.Y div 2)-Random(9));
+      it.positionChanged(); // this updates spatial accelerators
+    end
     else
+    begin
       if KillType = K_HARDKILL then // -5..+5; -5..0
-        g_Obj_Push(@gItems[id].Obj, (FObj.Vel.X div 2)-5+Random(11),
-                                    (FObj.Vel.Y div 2)-Random(6))
+      begin
+        g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-5+Random(11),
+                            (FObj.Vel.Y div 2)-Random(6));
+      end
       else // -3..+3; -3..0
-        g_Obj_Push(@gItems[id].Obj, (FObj.Vel.X div 2)-3+Random(7),
-                                    (FObj.Vel.Y div 2)-Random(4));
+      begin
+        g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-3+Random(7),
+                            (FObj.Vel.Y div 2)-Random(4));
+      end;
+      it.positionChanged(); // this updates spatial accelerators
+    end;
 
     if g_Game_IsNet and g_Game_IsServer then
       MH_SEND_ItemSpawn(True, id);
@@ -2882,13 +2657,13 @@ begin
   Srv := g_Game_IsServer;
   Netsrv := g_Game_IsServer and g_Game_IsNet;
   if Srv then FDeath := FDeath + 1;
-  if FLive then
+  if FAlive then
   begin
     if FGhost then
       FGhost := False;
     if not FPhysics then
       FPhysics := True;
-    FLive := False;
+    FAlive := False;
   end;
   FShellTimer := -1;
 
@@ -2898,7 +2673,7 @@ begin
     if FLives = 0 then FNoRespawn := True;
   end;
 
-// Íîìåð òèïà ñìåðòè:
+// Номер типа смерти:
   a := 1;
   case KillType of
     K_SIMPLEKILL:    a := 1;
@@ -2907,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:
@@ -2924,7 +2699,7 @@ begin
         FTime[T_RESPAWN] := gTime + TIME_RESPAWN3;
     end;
 
-// Ïåðåêëþ÷àåì ñîñòîÿíèå:
+// Переключаем состояние:
   case KillType of
     K_SIMPLEKILL:
       SetAction(A_DIE1);
@@ -2932,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);
@@ -2947,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
@@ -2991,12 +2766,12 @@ begin
         end;
       end
     else if g_GetUIDType(SpawnerUID) = UID_MONSTER then
-      begin // Óáèò ìîíñòðîì
-        mon := g_Monsters_Get(SpawnerUID);
+      begin // Убит монстром
+        mon := g_Monsters_ByUID(SpawnerUID);
         if mon = nil then
           s := '?'
         else
-          s := g_Monsters_GetKilledBy(mon.MonsterType);
+          s := g_Mons_GetKilledByTypeId(mon.MonsterType);
 
         case KillType of
           K_HARDKILL:
@@ -3013,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);
@@ -3026,8 +2801,8 @@ begin
 
   if Srv then
   begin
-// Âûáðîñ îðóæèÿ:
-    for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do
+// Выброс оружия:
+    for a := WP_FIRST to WP_LAST do
       if FWeapon[a] then
       begin
         case a of
@@ -3039,6 +2814,7 @@ begin
           WEAPON_PLASMA: i := ITEM_WEAPON_PLASMA;
           WEAPON_BFG: i := ITEM_WEAPON_BFG;
           WEAPON_SUPERPULEMET: i := ITEM_WEAPON_SUPERPULEMET;
+          WEAPON_FLAMETHROWER: i := ITEM_WEAPON_FLAMETHROWER;
           else i := 0;
         end;
 
@@ -3046,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);
@@ -3067,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
@@ -3175,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;
@@ -3205,106 +2982,202 @@ 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),
-              150, 0, 0);
+              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,
-              150, 0, 0);
+              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,
-              150, 0, 0);
+              Blood.R, Blood.G, Blood.B, Blood.Kind);
 end;
 
-procedure TPlayer.NextWeapon();
-var
-  i: Byte;
-  ok: Boolean;
+procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
 begin
   if g_Game_IsClient then Exit;
-  if FBFGFireCounter <> -1 then Exit;
-
-  if FTime[T_SWITCH] > gTime then Exit;
+  if Weapon > High(FWeapon) then Exit;
+  FNextWeap := FNextWeap or (1 shl Weapon);
+end;
 
-  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
-    if FReloading[i] > 0 then Exit;
+procedure TPlayer.resetWeaponQueue ();
+begin
+  FNextWeap := 0;
+  FNextWeapDelay := 0;
+end;
 
-  ok := False;
+function TPlayer.hasAmmoForWeapon (weapon: Byte): Boolean;
+begin
+  result := false;
+  case weapon of
+    WEAPON_KASTET, WEAPON_SAW: result := true;
+    WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
+    WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
+    WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
+    WEAPON_PLASMA, WEAPON_BFG: result := (FAmmo[A_CELLS] > 0);
+    WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
+    else result := (weapon < length(FWeapon));
+  end;
+end;
 
-  for i := FCurrWeap+1 to WEAPON_SUPERPULEMET do
-    if FWeapon[i] then
+// return 255 for "no switch"
+function TPlayer.getNextWeaponIndex (): Byte;
+var
+  i: Word;
+  wantThisWeapon: array[0..64] of Boolean;
+  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;
+  // cycling has priority
+  if (FNextWeap and $C000) <> 0 then
+  begin
+    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 := FCurrWeap;
+    for i := 0 to High(FWeapon) do
     begin
-      FCurrWeap := i;
-      ok := True;
-      Break;
-    end;
-
-  if not ok then
-    for i := WEAPON_KASTET to FCurrWeap-1 do
-      if FWeapon[i] then
+      cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
+      if FWeapon[cwi] then
       begin
-        FCurrWeap := i;
-        Break;
+        //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING);
+        result := Byte(cwi);
+        FNextWeapDelay := WEAPON_DELAY;
+        exit;
       end;
-
-  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);
+    end;
+    resetWeaponQueue();
+    exit;
+  end;
+  // no cycling
+  for i := 0 to High(wantThisWeapon) do
+    wantThisWeapon[i] := false;
+  for i := 0 to High(FWeapon) do
+    if (FNextWeap and (1 shl i)) <> 0 then
+    begin
+      wantThisWeapon[i] := true;
+      Inc(wwc);
+    end;
+  // exclude currently selected weapon from the set
+  wantThisWeapon[FCurrWeap] := false;
+  // slow down alterations a little
+  if wwc > 1 then
+  begin
+    //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
+      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
+  begin
+    resetWeaponQueue();
+    exit;
+  end;
+  //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
+  // try weapons in descending order
+  for i := High(FWeapon) downto 0 do
+  begin
+    if wantThisWeapon[i] and FWeapon[i] and ((wwc = 1) or hasAmmoForWeapon(i)) then
+    begin
+      // i found her!
+      result := Byte(i);
+      resetWeaponQueue();
+      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;
 
-procedure TPlayer.PrevWeapon();
+procedure TPlayer.RealizeCurrentWeapon();
+  function switchAllowed (): Boolean;
+  var
+    i: Byte;
+  begin
+    result := false;
+    if FBFGFireCounter <> -1 then
+      exit;
+    if FTime[T_SWITCH] > gTime then
+      exit;
+    for i := WP_FIRST to WP_LAST do
+      if FReloading[i] > 0 then
+        exit;
+    result := true;
+  end;
+
 var
-  i: Byte;
-  ok: Boolean;
+  nw: Byte;
 begin
-  if g_Game_IsClient then Exit;
-  if FBFGFireCounter <> -1 then Exit;
+  //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
+  //FNextWeap := FNextWeap and $1FFF;
+  if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
 
-  if FTime[T_SWITCH] > gTime then Exit;
-
-  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
-    if FReloading[i] > 0 then Exit;
-
-  ok := False;
-
-  if FCurrWeap > 0 then
-    for i := FCurrWeap-1 downto WEAPON_KASTET do
-      if FWeapon[i] then
-      begin
-        FCurrWeap := i;
-        ok := True;
-        Break;
-      end;
-
-  if not ok then
-    for i := WEAPON_SUPERPULEMET downto FCurrWeap+1 do
-      if FWeapon[i] then
-      begin
-        FCurrWeap := i;
-        Break;
-      end;
+  if not switchAllowed then
+  begin
+    //HACK for weapon cycling
+    if (FNextWeap and $E000) <> 0 then FNextWeap := 0;
+    exit;
+  end;
 
-  FTime[T_SWITCH] := gTime+156;
+  nw := getNextWeaponIndex();
+  if nw = 255 then exit; // don't reset anything here
+  if nw > High(FWeapon) then
+  begin
+    // don't forget to reset queue here!
+    //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
+    resetWeaponQueue();
+    exit;
+  end;
 
-  if FCurrWeap = WEAPON_SAW then
-    FSawSoundSelect.PlayAt(FObj.X, FObj.Y);
+  if FWeapon[nw] then
+  begin
+    FCurrWeap := 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);
+  end;
+end;
 
-  FModel.SetWeapon(FCurrWeap);
+procedure TPlayer.NextWeapon();
+begin
+  if g_Game_IsClient then Exit;
+  FNextWeap := $8000;
+end;
 
-  if g_Game_IsNet then MH_SEND_PlayerStats(FUID);
+procedure TPlayer.PrevWeapon();
+begin
+  if g_Game_IsClient then Exit;
+  FNextWeap := $4000;
 end;
 
 procedure TPlayer.SetWeapon(W: Byte);
@@ -3315,35 +3188,48 @@ begin
 
   FCurrWeap := W;
   FModel.SetWeapon(CurrWeap);
+  resetWeaponQueue();
 end;
 
-function TPlayer.PickItem(ItemType: Byte; respawn: Boolean; var remove: Boolean): Boolean;
+function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean;
+
+  function allowBerserkSwitching (): Boolean;
+  begin
+    if (FBFGFireCounter <> -1) then begin result := false; exit; end;
+    result := true;
+    if gBerserkAutoswitch then exit;
+    if not conIsCheatsEnabled then exit;
+    result := false;
+  end;
+
 var
   a: Boolean;
 begin
   Result := False;
   if g_Game_IsClient then Exit;
 
-  // a = true - ìåñòî ñïàâíà ïðåäìåòà:
-  a := LongBool(gGameSettings.Options and GAME_OPTION_WEAPONSTAY) and respawn;
+  // 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;
         if gFlash = 2 then Inc(FPickup, 5);
       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;
         if gFlash = 2 then Inc(FPickup, 5);
       end;
 
@@ -3366,16 +3252,17 @@ 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;
         if gFlash = 2 then Inc(FPickup, 5);
       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;
@@ -3383,11 +3270,12 @@ begin
           FArmor := PLAYER_AP_LIMIT;
         Result := True;
         remove := True;
+        FFireTime := 0;
         if gFlash = 2 then Inc(FPickup, 5);
       end;
 
     ITEM_WEAPON_SAW:
-      if (not FWeapon[WEAPON_SAW]) or ((not respawn) and (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) then
+      if (not FWeapon[WEAPON_SAW]) or ((not arespawn) and (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) then
       begin
         FWeapon[WEAPON_SAW] := True;
         Result := True;
@@ -3398,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]);
@@ -3480,6 +3368,18 @@ begin
         if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
       end;
 
+    ITEM_WEAPON_FLAMETHROWER:
+      if (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) or not FWeapon[WEAPON_FLAMETHROWER] then
+      begin
+        if a and FWeapon[WEAPON_FLAMETHROWER] then Exit;
+
+        IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
+        FWeapon[WEAPON_FLAMETHROWER] := True;
+        Result := True;
+        if gFlash = 2 then Inc(FPickup, 5);
+        if a and g_Game_IsNet then MH_SEND_Sound(GameX, GameY, 'SOUND_ITEM_GETWEAPON');
+      end;
+
     ITEM_AMMO_BULLETS:
       if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
       begin
@@ -3552,17 +3452,28 @@ begin
         if gFlash = 2 then Inc(FPickup, 5);
       end;
 
+    ITEM_AMMO_FUELCAN:
+      if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then
+      begin
+        IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
+        Result := True;
+        remove := True;
+        if gFlash = 2 then Inc(FPickup, 5);
+      end;
+
     ITEM_AMMO_BACKPACK:
       if not(R_ITEM_BACKPACK in FRulez) or
             (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
             (FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS]) or
             (FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS]) or
-            (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) then
+            (FAmmo[A_CELLS] < FMaxAmmo[A_CELLS]) or
+            (FAmmo[A_FUEL] < FMaxAmmo[A_FUEL]) then
       begin
-        FMaxAmmo[A_BULLETS] := 400;
-        FMaxAmmo[A_SHELLS] := 100;
-        FMaxAmmo[A_ROCKETS] := 100;
-        FMaxAmmo[A_CELLS] := 600;
+        FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
+        FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
+        FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
+        FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
+        FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
 
         if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then
           IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
@@ -3572,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;
@@ -3615,6 +3528,7 @@ begin
         FMegaRulez[MR_SUIT] := gTime+PLAYER_SUIT_TIME;
         Result := True;
         remove := True;
+        FFireTime := 0;
         if gFlash = 2 then Inc(FPickup, 5);
       end;
 
@@ -3632,24 +3546,29 @@ begin
         if not (R_BERSERK in FRulez) then
         begin
           Include(FRulez, R_BERSERK);
-          if FBFGFireCounter = -1 then
+          if allowBerserkSwitching then
           begin
             FCurrWeap := WEAPON_KASTET;
+            resetWeaponQueue();
             FModel.SetWeapon(WEAPON_KASTET);
           end;
           if gFlash <> 0 then
+          begin
             Inc(FPain, 100);
             if gFlash = 2 then Inc(FPickup, 5);
+          end;
           FBerserk := gTime+30000;
           Result := True;
           remove := True;
+          FFireTime := 0;
         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;
+          FFireTime := 0;
         end;
       end;
 
@@ -3657,17 +3576,19 @@ 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;
         if gFlash = 2 then Inc(FPickup, 5);
       end;
 
@@ -3702,12 +3623,12 @@ end;
 
 procedure TPlayer.Touch();
 begin
-  if not FLive then
+  if not FAlive then
     Exit;
   //FModel.PlaySound(MODELSOUND_PAIN, 1, FObj.X, FObj.Y);
   if FIamBot then
   begin
-  // Áðîñèòü ôëàã òîâàðèùó:
+  // Бросить флаг товарищу:
     if gGameSettings.GameMode = GM_CTF then
       DropFlag();
   end;
@@ -3726,7 +3647,7 @@ end;
 procedure TPlayer.Reset(Force: Boolean);
 begin
   if Force then
-    FLive := False;
+    FAlive := False;
 
   FSpawned := False;
   FTime[T_RESPAWN] := 0;
@@ -3741,6 +3662,9 @@ begin
   FMonsterKills := 0;
   FDeath := 0;
   FSecrets := 0;
+  FSpawnInvul := 0;
+  FCorpse := -1;
+  FReady := False;
   if FNoRespawn then
   begin
     FSpectator := False;
@@ -3759,6 +3683,8 @@ begin
   ReleaseKeys();
 
   FDamageBuffer := 0;
+  FSlopeOld := 0;
+  FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
   FShellTimer := -1;
@@ -3776,157 +3702,80 @@ 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;
+  FSpawnInvul := 0;
+  FCorpse := -1;
+
   if not g_Game_IsServer then
     Exit;
   if FDummy then
@@ -3936,17 +3785,17 @@ begin
   if Force then
   begin
     FTime[T_RESPAWN] := 0;
-    FLive := False;
+    FAlive := False;
   end;
   FNetTime := 0;
   // 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);
@@ -3955,32 +3804,32 @@ 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 FLive then
+// Воскрешение без оружия:
+  if not FAlive then
   begin
-    FHealth := PLAYER_HP_SOFT;
+    FHealth := Round(PLAYER_HP_SOFT * (FHandicap / 100));
     FArmor := 0;
-    FLive := True;
+    FAlive := True;
     FAir := AIR_DEF;
     FJetFuel := 0;
 
-    for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do
+    for a := WP_FIRST to WP_LAST do
     begin
       FWeapon[a] := False;
       FReloading[a] := 0;
@@ -3989,52 +3838,51 @@ begin
     FWeapon[WEAPON_PISTOL] := True;
     FWeapon[WEAPON_KASTET] := True;
     FCurrWeap := WEAPON_PISTOL;
+    resetWeaponQueue();
 
     FModel.SetWeapon(FCurrWeap);
 
-    for b := A_BULLETS to A_CELLS do
+    for b := A_BULLETS to A_HIGH do
       FAmmo[b] := 0;
 
     FAmmo[A_BULLETS] := 50;
 
-    FMaxAmmo[A_BULLETS] := 200;
-    FMaxAmmo[A_SHELLS] := 50;
-    FMaxAmmo[A_ROCKETS] := 50;
-    FMaxAmmo[A_CELLS] := 300;
+    FMaxAmmo[A_BULLETS] := AmmoLimits[0, A_BULLETS];
+    FMaxAmmo[A_SHELLS] := AmmoLimits[0, A_SHELLS];
+    FMaxAmmo[A_ROCKETS] := AmmoLimits[0, A_SHELLS];
+    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;
   FObj.Accel.Y := 0;
 
   FDirection := RespawnPoint.Direction;
-  if FDirection = D_LEFT then
+  if FDirection = TDirection.D_LEFT then
     FAngle := 180
   else
     FAngle := 0;
 
-  FIncCam := 0;
-  FBFGFireCounter := -1;
-  FShellTimer := -1;
-  FPain := 0;
-  FLastHit := 0;
-
   SetAction(A_STAND, True);
   FModel.Direction := FDirection;
 
@@ -4044,19 +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;
@@ -4064,6 +3921,11 @@ begin
   FSpectatePlayer := -1;
   FSpawned := True;
 
+  if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then
+    gPlayer1 := self;
+  if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then
+    gPlayer2 := self;
+
   if g_Game_IsNet then
   begin
     MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
@@ -4077,7 +3939,7 @@ end;
 
 procedure TPlayer.Spectate(NoMove: Boolean = False);
 begin
-  if FLive then
+  if FAlive then
     Kill(K_EXTRAHARDKILL, FUID, HIT_SOME)
   else if (not NoMove) then
   begin
@@ -4087,23 +3949,24 @@ begin
   FXTo := GameX;
   FYTo := GameY;
 
-  FLive := False;
+  FAlive := False;
   FSpectator := True;
   FGhost := True;
   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;
@@ -4114,7 +3977,7 @@ end;
 
 procedure TPlayer.SwitchNoClip;
 begin
-  if not FLive then
+  if not FAlive then
     Exit;
   FGhost := not FGhost;
   FPhysics := not FGhost;
@@ -4136,8 +3999,8 @@ begin
   if MAX_RUNVEL > 8 then
     FlySmoke();
 
-// Áåæèì:
-  if Direction = D_LEFT then
+// Бежим:
+  if Direction = TDirection.D_LEFT then
     begin
       if FObj.Vel.X > -MAX_RUNVEL then
         FObj.Vel.X := FObj.Vel.X - (MAX_RUNVEL shr 3);
@@ -4146,20 +4009,29 @@ 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);
     if b > 1 then b := b * (Random(8 div b) + 1);
     for a := 0 to High(gGibs) do
-      if gGibs[a].Live and
+    begin
+      if gGibs[a].alive and
          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
-          g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // íàëåâî
+        begin
+          g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // налево
+        end
         else
-          g_Obj_PushA(@gGibs[a].Obj, b, Random(61));    // íàïðàâî
+        begin
+          g_Obj_PushA(@gGibs[a].Obj, b, Random(61));    // направо
+        end;
+        gGibs[a].positionChanged(); // this updates spatial accelerators
+      end;
+    end;
   end;
 
   SetAction(A_WALK);
@@ -4169,7 +4041,7 @@ procedure TPlayer.SeeDown();
 begin
   SetAction(A_SEEDOWN);
 
-  if FDirection = D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN;
+  if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTDOWN else FAngle := ANGLE_RIGHTDOWN;
 
   if FIncCam > -120 then DecMin(FIncCam, 5, -120);
 end;
@@ -4178,7 +4050,7 @@ procedure TPlayer.SeeUp();
 begin
   SetAction(A_SEEUP);
 
-  if FDirection = D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP;
+  if FDirection = TDirection.D_LEFT then FAngle := ANGLE_LEFTUP else FAngle := ANGLE_RIGHTUP;
 
   if FIncCam < 120 then IncMax(FIncCam, 5, 120);
 end;
@@ -4221,9 +4093,6 @@ begin
 end;
 
 function TPlayer.TeleportTo(X, Y: Integer; silent: Boolean; dir: Byte): Boolean;
-var
-  Anim: TAnimation;
-  ID: DWORD;
 begin
   Result := False;
 
@@ -4237,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,
@@ -4256,7 +4122,9 @@ begin
 
   FObj.X := X-PLAYER_RECT.X;
   FObj.Y := Y-PLAYER_RECT.Y;
-  if FLive and FGhost then
+  FObj.oldX := FObj.X; // don't interpolate after respawn
+  FObj.oldY := FObj.Y;
+  if FAlive and FGhost then
   begin
     FXTo := FObj.X;
     FYTo := FObj.Y;
@@ -4266,37 +4134,38 @@ begin
   begin
     if dir = 1 then
     begin
-      SetDirection(D_LEFT);
+      SetDirection(TDirection.D_LEFT);
       FAngle := 180;
     end
     else
       if dir = 2 then
       begin
-        SetDirection(D_RIGHT);
+        SetDirection(TDirection.D_RIGHT);
         FAngle := 0;
       end
       else
         if dir = 3 then
-        begin // îáðàòíîå
-          if FDirection = D_RIGHT then
+        begin // обратное
+          if FDirection = TDirection.D_RIGHT then
           begin
-            SetDirection(D_LEFT);
+            SetDirection(TDirection.D_LEFT);
             FAngle := 180;
           end
           else
           begin
-            SetDirection(D_RIGHT);
+            SetDirection(TDirection.D_RIGHT);
             FAngle := 0;
           end;
         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,
@@ -4314,6 +4183,49 @@ begin
     Result := 1;
 end;
 
+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
+    Exit;
+  for i := 0 to High(gCorpses) do
+    if gCorpses[i] <> nil then
+      if gCorpses[i].FPlayerUID = FUID then
+      begin
+        Result := True;
+        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;
@@ -4346,15 +4258,20 @@ begin
       FLoss := 0;
     end;
 
-  if FLive and (gFly or FJetpack) then
+  if FAlive then
+    FPunchAnim.Update;
+  if FPunchAnim.played then
+    FPunchAnim.Disable;
+
+  if FAlive and (gFly or FJetpack) then
     FlySmoke();
 
-  if FDirection = D_LEFT then
+  if FDirection = TDirection.D_LEFT then
     FAngle := 180
   else
     FAngle := 0;
 
-  if FLive and (not FGhost) then
+  if FAlive and (not FGhost) then
   begin
     if FKeys[KEY_UP].Pressed then
       SeeUp();
@@ -4371,32 +4288,47 @@ begin
     FIncCam := FIncCam*i;
   end;
 
+  // no need to do that each second frame, weapon queue will take care of it
+  if FAlive and FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
+  if FAlive and FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
+
   if gTime mod (GAME_TICK*2) <> 0 then
   begin
-    if (FObj.Vel.X = 0) and FLive then
+    if (FObj.Vel.X = 0) and FAlive then
     begin
       if FKeys[KEY_LEFT].Pressed then
-        Run(D_LEFT);
+        Run(TDirection.D_LEFT);
       if FKeys[KEY_RIGHT].Pressed then
-        Run(D_RIGHT);
+        Run(TDirection.D_RIGHT);
     end;
 
     if FPhysics then
+    begin
       g_Obj_Move(@FObj, True, True, True);
+      positionChanged(); // this updates spatial accelerators
+    end;
 
     Exit;
   end;
 
   FActionChanged := False;
 
-  if FLive then
+  if FAlive then
   begin
     // Let alive player do some actions
-    if FKeys[KEY_LEFT].Pressed then Run(D_LEFT);
-    if FKeys[KEY_RIGHT].Pressed then Run(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_LEFT].Pressed then Run(TDirection.D_LEFT);
+    if FKeys[KEY_RIGHT].Pressed then Run(TDirection.D_RIGHT);
+    //if FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
+    //if FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
+    if FKeys[KEY_FIRE].Pressed and AnyServer then Fire()
+    else
+    begin
+      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
@@ -4428,7 +4360,7 @@ begin
         Respawn(False)
       else // Single
         if (FTime[T_RESPAWN] <= gTime) and
-          gGameOn and (not FLive) then
+          gGameOn and (not FAlive) then
         begin
           if (g_Player_GetCount() > 1) then
             Respawn(False)
@@ -4454,7 +4386,7 @@ begin
             SetSpect := False;
             for I := FSpectatePlayer + 1 to High(gPlayers) do
               if gPlayers[I] <> nil then
-                if gPlayers[I].Live then
+                if gPlayers[I].alive then
                   if gPlayers[I].UID <> FUID then
                   begin
                     FSpectatePlayer := I;
@@ -4505,7 +4437,10 @@ begin
   end;
 
   if FPhysics then
-    g_Obj_Move(@FObj, True, True, True)
+  begin
+    g_Obj_Move(@FObj, True, True, True);
+    positionChanged(); // this updates spatial accelerators
+  end
   else
   begin
     FObj.Vel.X := 0;
@@ -4513,7 +4448,7 @@ begin
     if FSpectator then
       if (FSpectatePlayer <= High(gPlayers)) and (FSpectatePlayer >= 0) then
         if gPlayers[FSpectatePlayer] <> nil then
-          if gPlayers[FSpectatePlayer].Live then
+          if gPlayers[FSpectatePlayer].alive then
           begin
             FXTo := gPlayers[FSpectatePlayer].GameX;
             FYTo := gPlayers[FSpectatePlayer].GameY;
@@ -4525,8 +4460,8 @@ begin
                                  PANEL_BLOCKMON, True);
   headwater := HeadInLiquid(0, 0);
 
-// Ñîïðîòèâëåíèå âîçäóõà:
-  if (not FLive) or not (FKeys[KEY_LEFT].Pressed or FKeys[KEY_RIGHT].Pressed) then
+// Сопротивление воздуха:
+  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);
 
@@ -4534,9 +4469,9 @@ begin
   DecMin(FPain, 5, 0);
   DecMin(FPickup, 1, 0);
 
-  if FLive and (FObj.Y > gMapInfo.Height+128) and AnyServer then
+  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;
@@ -4545,7 +4480,7 @@ begin
 
   i := 9;
 
-  if FLive then
+  if FAlive then
   begin
     if FCurrWeap = WEAPON_SAW then
       if not (FSawSound.IsPlaying() or FSawSoundHit.IsPlaying() or
@@ -4560,7 +4495,7 @@ begin
         FJetSoundFly.PlayAt(FObj.X, FObj.Y);
       end;
 
-    for b := WEAPON_KASTET to WEAPON_SUPERPULEMET do
+    for b := WP_FIRST to WP_LAST do
       if FReloading[b] > 0 then
         if FNoReload then
           FReloading[b] := 0
@@ -4590,7 +4525,7 @@ begin
         begin
           wx := FObj.X+WEAPONPOINT[FDirection].X;
           wy := FObj.Y+WEAPONPOINT[FDirection].Y;
-          xd := wx+IfThen(FDirection = D_LEFT, -30, 30);
+          xd := wx+IfThen(FDirection = TDirection.D_LEFT, -30, 30);
           yd := wy+firediry();
           g_Weapon_bfgshot(wx, wy, xd, yd, FUID);
           if NetServer then MH_SEND_PlayerFire(FUID, WEAPON_BFG, wx, wy, xd, yd);
@@ -4634,6 +4569,37 @@ begin
     end else if FAir < AIR_DEF then
       FAir := AIR_DEF;
 
+    if FFireTime > 0 then
+    begin
+      if BodyInLiquid(0, 0) then
+      begin
+        FFireTime := 0;
+        FFirePainTime := 0;
+      end
+      else if FMegaRulez[MR_SUIT] >= gTime then
+      begin
+        if FMegaRulez[MR_SUIT] = gTime then
+          FFireTime := 1;
+        FFirePainTime := 0;
+      end
+      else
+      begin
+        OnFireFlame(1);
+        if FFirePainTime <= 0 then
+        begin
+          if g_Game_IsServer then
+            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;
+    end;
+
     if FDamageBuffer > 0 then
     begin
       if FDamageBuffer >= 9 then
@@ -4660,7 +4626,7 @@ begin
             else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
               else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
 
-      if FLive 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)
@@ -4672,24 +4638,44 @@ begin
     end;
 
     {CollideItem();}
-  end; // if FLive then ...
+  end; // if FAlive then ...
 
   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);
 end;
 
+
+procedure TPlayer.getMapBox (out x, y, w, h: Integer); inline;
+begin
+  x := FObj.X+PLAYER_RECT.X;
+  y := FObj.Y+PLAYER_RECT.Y;
+  w := PLAYER_RECT.Width;
+  h := PLAYER_RECT.Height;
+end;
+
+
+procedure TPlayer.moveBy (dx, dy: Integer); inline;
+begin
+  if (dx <> 0) or (dy <> 0) then
+  begin
+    FObj.X += dx;
+    FObj.Y += dy;
+    positionChanged();
+  end;
+end;
+
+
 function TPlayer.Collide(X, Y: Integer; Width, Height: Word): Boolean;
 begin
   Result := g_Collide(FObj.X+PLAYER_RECT.X,
@@ -4770,7 +4756,7 @@ begin
 
   for a := 0 to High(gPlayers) do
     if (gPlayers[a] <> nil) and (gPlayers[a] <> Self) and
-       gPlayers[a].Live and SameTeam(FUID, gPlayers[a].FUID) and
+       gPlayers[a].alive and SameTeam(FUID, gPlayers[a].FUID) and
        g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y,
                      FObj.Rect.Width, FObj.Rect.Height, @gPlayers[a].FObj) then
     begin
@@ -4784,7 +4770,7 @@ end;
 
 procedure TPlayer.NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
 var
-  Obj: TObj;
+  locObj: TObj;
   F: Boolean;
   WX, WY, XD, YD: Integer;
 begin
@@ -4797,21 +4783,22 @@ begin
   case FCurrWeap of
     WEAPON_KASTET:
     begin
+      DoPunch();
       if R_BERSERK in FRulez then
       begin
         //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
-        obj.X := FObj.X+FObj.Rect.X;
-        obj.Y := FObj.Y+FObj.Rect.Y;
-        obj.rect.X := 0;
-        obj.rect.Y := 0;
-        obj.rect.Width := 39;
-        obj.rect.Height := 52;
-        obj.Vel.X := (xd-wx) div 2;
-        obj.Vel.Y := (yd-wy) div 2;
-        obj.Accel.X := xd-wx;
-        obj.Accel.y := yd-wy;
-
-        if g_Weapon_Hit(@obj, 50, FUID, HIT_SOME) <> 0 then
+        locobj.X := FObj.X+FObj.Rect.X;
+        locobj.Y := FObj.Y+FObj.Rect.Y;
+        locobj.rect.X := 0;
+        locobj.rect.Y := 0;
+        locobj.rect.Width := 39;
+        locobj.rect.Height := 52;
+        locobj.Vel.X := (xd-wx) div 2;
+        locobj.Vel.Y := (yd-wy) div 2;
+        locobj.Accel.X := xd-wx;
+        locobj.Accel.y := yd-wy;
+
+        if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
           g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
         else
           g_Sound_PlayExAt('SOUND_WEAPON_MISSBERSERK', FObj.X, FObj.Y);
@@ -4905,6 +4892,14 @@ begin
       g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
                              GameVelX, GameVelY-2, SHELL_SHELL);
     end;
+
+    WEAPON_FLAMETHROWER:
+    begin
+      g_Weapon_flame(wx, wy, xd, yd, FUID, WID);
+      FlamerOn;
+      FFireAngle := FAngle;
+      f := True;
+    end;
   end;
 
   if not f then Exit;
@@ -4924,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
@@ -4958,7 +4957,7 @@ end;
 function TPlayer.GetFlag(Flag: Byte): Boolean;
 var
   s, ts: String;
-  evtype: Byte;
+  evtype, a: Byte;
 begin
   Result := False;
 
@@ -4967,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
@@ -4986,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;
@@ -5000,7 +5009,7 @@ begin
     Exit;
   end;
 
-// Ïîäîáðàë ñâîé ôëàã - âåðíóë åãî íà áàçó:
+// Подобрал свой флаг - вернул его на базу:
   if (Flag = FTeam) and
      (gFlags[Flag].State = FLAG_STATE_DROPPED) then
   begin
@@ -5017,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
@@ -5026,7 +5045,7 @@ begin
     Exit;
   end;
 
-// Ïîäîáðàë ÷óæîé ôëàã:
+// Подобрал чужой флаг:
   if (Flag <> FTeam) and (FTime[T_FLAGCAP] <= gTime) then
   begin
     SetFlag(Flag);
@@ -5044,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
@@ -5060,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
@@ -5077,6 +5107,7 @@ begin
     Count := FLAG_TIME;
     g_Obj_Push(@Obj, (FObj.Vel.X div 2)-2+Random(5),
                      (FObj.Vel.Y div 2)-2+Random(5));
+    positionChanged(); // this updates spatial accelerators
 
     if FFlag = FLAG_RED then
       s := _lc[I_PLAYER_FLAG_RED]
@@ -5086,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;
@@ -5095,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;
 
@@ -5136,312 +5182,283 @@ end;
 procedure TPlayer.RememberState();
 var
   i: Integer;
-begin
-  FSavedState.Health := FHealth;
-  FSavedState.Armor := FArmor;
-  FSavedState.Air := FAir;
-  FSavedState.JetFuel := FJetFuel;
-  FSavedState.CurrWeap := FCurrWeap;
-
-  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;
-
-  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);
 end;
 
-procedure TPlayer.SaveState(var Mem: TBinMemoryWriter);
+procedure TPlayer.SaveState (st: TStream);
 var
   i: Integer;
-  sig: DWORD;
-  str: String;
   b: Byte;
 begin
-  if FIamBot then
-    i := 512
-  else
-    i := 256;
-
-  Mem := TBinMemoryWriter.Create(i);
-
-// Ñèãíàòóðà èãðîêà:
-  sig := PLAYER_SIGNATURE; // 'PLYR'
-  Mem.WriteDWORD(sig);
-// Áîò èëè ÷åëîâåê:
-  Mem.WriteBoolean(FIamBot);
-// UID èãðîêà:
-  Mem.WriteWord(FUID);
-// Èìÿ èãðîêà:
-  Mem.WriteString(FName, 32);
-// Êîìàíäà:
-  Mem.WriteByte(FTeam);
-// Æèâ ëè:
-  Mem.WriteBoolean(FLive);
-// Èçðàñõîäîâàë ëè âñå æèçíè:
-  Mem.WriteBoolean(FNoRespawn);
-// Íàïðàâëåíèå:
-  if FDirection = D_LEFT then
-    b := 1
-  else // D_RIGHT
-    b := 2;
-  Mem.WriteByte(b);
-// Çäîðîâüå:
-  Mem.WriteInt(FHealth);
-// Æèçíè:
-  Mem.WriteByte(FLives);
-// Áðîíÿ:
-  Mem.WriteInt(FArmor);
-// Çàïàñ âîçäóõà:
-  Mem.WriteInt(FAir);
-// Çàïàñ ãîðþ÷åãî:
-  Mem.WriteInt(FJetFuel);
-// Áîëü:
-  Mem.WriteInt(FPain);
-// Óáèë:
-  Mem.WriteInt(FKills);
-// Óáèë ìîíñòðîâ:
-  Mem.WriteInt(FMonsterKills);
-// Ôðàãîâ:
-  Mem.WriteInt(FFrags);
-// Ôðàãîâ ïîäðÿä:
-  Mem.WriteByte(FFragCombo);
-// Âðåìÿ ïîñëåäíåãî ôðàãà:
-  Mem.WriteDWORD(FLastFrag);
-// Ñìåðòåé:
-  Mem.WriteInt(FDeath);
-// Êàêîé ôëàã íåñåò:
-  Mem.WriteByte(FFlag);
-// Íàøåë ñåêðåòîâ:
-  Mem.WriteInt(FSecrets);
-// Òåêóùåå îðóæèå:
-  Mem.WriteByte(FCurrWeap);
-// Âðåìÿ çàðÿäêè BFG:
-  Mem.WriteSmallInt(FBFGFireCounter);
-// Áóôåð óðîíà:
-  Mem.WriteInt(FDamageBuffer);
-// Ïîñëåäíèé óäàðèâøèé:
-  Mem.WriteWord(FLastSpawnerUID);
-// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
-  Mem.WriteByte(FLastHit);
-// Îáúåêò èãðîêà:
-  Obj_SaveState(@FObj, Mem);
-// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_CELLS do
-    Mem.WriteWord(FAmmo[i]);
-// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_CELLS do
-    Mem.WriteWord(FMaxAmmo[i]);
-// Íàëè÷èå îðóæèÿ:
-  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
-    Mem.WriteBoolean(FWeapon[i]);
-// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
-  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
-    Mem.WriteWord(FReloading[i]);
-// Íàëè÷èå ðþêçàêà:
-  if R_ITEM_BACKPACK in FRulez then
-    b := 1
-  else
-    b := 0;
-  Mem.WriteByte(b);
-// Íàëè÷èå êðàñíîãî êëþ÷à:
-  if R_KEY_RED in FRulez then
-    b := 1
-  else
-    b := 0;
-  Mem.WriteByte(b);
-// Íàëè÷èå çåëåíîãî êëþ÷à:
-  if R_KEY_GREEN in FRulez then
-    b := 1
-  else
-    b := 0;
-  Mem.WriteByte(b);
-// Íàëè÷èå ñèíåãî êëþ÷à:
-  if R_KEY_BLUE in FRulez then
-    b := 1
-  else
-    b := 0;
-  Mem.WriteByte(b);
-// Íàëè÷èå áåðñåðêà:
-  if R_BERSERK in FRulez then
-    b := 1
-  else
-    b := 0;
-  Mem.WriteByte(b);
-// Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ:
-  for i := MR_SUIT to MR_MAX do
-    Mem.WriteDWORD(FMegaRulez[i]);
-// Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà:
-  for i := T_RESPAWN to T_FLAGCAP do
-    Mem.WriteDWORD(FTime[i]);
-// Íàçâàíèå ìîäåëè:
-  str := FModel.Name;
-  Mem.WriteString(str);
-// Öâåò ìîäåëè:
-  b := FColor.R;
-  Mem.WriteByte(b);
-  b := FColor.G;
-  Mem.WriteByte(b);
-  b := FColor.B;
-  Mem.WriteByte(b);
-end;
-
-procedure TPlayer.LoadState(var Mem: TBinMemoryReader);
+  // Сигнатура игрока
+  utils.writeSign(st, 'PLYR');
+  utils.writeInt(st, Byte(PLR_SAVE_VERSION)); // version
+  // Бот или человек
+  utils.writeBool(st, FIamBot);
+  // 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
+  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.GetName());
+  // Цвет модели
+  utils.writeInt(st, Byte(FColor.R));
+  utils.writeInt(st, Byte(FColor.G));
+  utils.writeInt(st, Byte(FColor.B));
+end;
+
+
+procedure TPlayer.LoadState (st: TStream);
 var
   i: Integer;
-  sig: DWORD;
   str: String;
   b: Byte;
 begin
-  if Mem = nil then
-    Exit;
-
-// Ñèãíàòóðà èãðîêà:
-  Mem.ReadDWORD(sig);
-  if sig <> PLAYER_SIGNATURE then // 'PLYR'
-  begin
-    raise EBinSizeError.Create('TPlayer.LoadState: Wrong Player Signature');
-  end;
-// Áîò èëè ÷åëîâåê:
-  Mem.ReadBoolean(FIamBot);
-// UID èãðîêà:
-  Mem.ReadWord(FUID);
-// Èìÿ èãðîêà:
-  Mem.ReadString(str);
-  if (Self <> gPlayer1) and (Self <> gPlayer2) then
-    FName := str;
-// Êîìàíäà:
-  Mem.ReadByte(FTeam);
-// Æèâ ëè:
-  Mem.ReadBoolean(FLive);
-// Èçðàñõîäîâàë ëè âñå æèçíè:
-  Mem.ReadBoolean(FNoRespawn);
-// Íàïðàâëåíèå:
-  Mem.ReadByte(b);
-  if b = 1 then
-    FDirection := D_LEFT
-  else // b = 2
-    FDirection := D_RIGHT;
-// Çäîðîâüå:
-  Mem.ReadInt(FHealth);
-// Æèçíè:
-  Mem.ReadByte(FLives);
-// Áðîíÿ:
-  Mem.ReadInt(FArmor);
-// Çàïàñ âîçäóõà:
-  Mem.ReadInt(FAir);
-// Çàïàñ ãîðþ÷åãî:
-  Mem.ReadInt(FJetFuel);
-// Áîëü:
-  Mem.ReadInt(FPain);
-// Óáèë:
-  Mem.ReadInt(FKills);
-// Óáèë ìîíñòðîâ:
-  Mem.ReadInt(FMonsterKills);
-// Ôðàãîâ:
-  Mem.ReadInt(FFrags);
-// Ôðàãîâ ïîäðÿä:
-  Mem.ReadByte(FFragCombo);
-// Âðåìÿ ïîñëåäíåãî ôðàãà:
-  Mem.ReadDWORD(FLastFrag);
-// Ñìåðòåé:
-  Mem.ReadInt(FDeath);
-// Êàêîé ôëàã íåñåò:
-  Mem.ReadByte(FFlag);
-// Íàøåë ñåêðåòîâ:
-  Mem.ReadInt(FSecrets);
-// Òåêóùåå îðóæèå:
-  Mem.ReadByte(FCurrWeap);
-// Âðåìÿ çàðÿäêè BFG:
-  Mem.ReadSmallInt(FBFGFireCounter);
-// Áóôåð óðîíà:
-  Mem.ReadInt(FDamageBuffer);
-// Ïîñëåäíèé óäàðèâøèé:
-  Mem.ReadWord(FLastSpawnerUID);
-// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
-  Mem.ReadByte(FLastHit);
-// Îáúåêò èãðîêà:
-  Obj_LoadState(@FObj, Mem);
-// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_CELLS do
-    Mem.ReadWord(FAmmo[i]);
-// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_CELLS do
-    Mem.ReadWord(FMaxAmmo[i]);
-// Íàëè÷èå îðóæèÿ:
-  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
-    Mem.ReadBoolean(FWeapon[i]);
-// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
-  for i := WEAPON_KASTET to WEAPON_SUPERPULEMET do
-    Mem.ReadWord(FReloading[i]);
-// Íàëè÷èå ðþêçàêà:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(FRulez, R_ITEM_BACKPACK);
-// Íàëè÷èå êðàñíîãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(FRulez, R_KEY_RED);
-// Íàëè÷èå çåëåíîãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(FRulez, R_KEY_GREEN);
-// Íàëè÷èå ñèíåãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(FRulez, R_KEY_BLUE);
-// Íàëè÷èå áåðñåðêà:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(FRulez, R_BERSERK);
-// Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ:
-  for i := MR_SUIT to MR_MAX do
-    Mem.ReadDWORD(FMegaRulez[i]);
-// Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà:
-  for i := T_RESPAWN to T_FLAGCAP do
-    Mem.ReadDWORD(FTime[i]);
-// Íàçâàíèå ìîäåëè:
-  Mem.ReadString(str);
-// Öâåò ìîäåëè:
-  Mem.ReadByte(FColor.R);
-  Mem.ReadByte(FColor.G);
-  Mem.ReadByte(FColor.B);
-  if Self = gPlayer1 then
+  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 игрока
+  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);
+  // Желаемое оружие
+  FNextWeap := utils.readWord(st);
+  // ...и пауза
+  FNextWeapDelay := utils.readByte(st);
+  // Время зарядки 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);
+  if (self = gPlayer1) then
   begin
     str := gPlayer1Settings.Model;
     FColor := gPlayer1Settings.Color;
-  end;
-  if Self = gPlayer2 then
+  end
+  else if (self = gPlayer2) then
   begin
     str := gPlayer2Settings.Model;
     FColor := gPlayer2Settings.Color;
   end;
-// Îáíîâëÿåì ìîäåëü èãðîêà:
+  // Обновляем модель игрока
   SetModel(str);
   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
     FModel.Color := TEAMCOLOR[FTeam]
@@ -5449,6 +5466,7 @@ begin
     FModel.Color := FColor;
 end;
 
+
 procedure TPlayer.AllRulez(Health: Boolean);
 var
   a: Integer;
@@ -5460,8 +5478,8 @@ begin
     Exit;
   end;
 
-  for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do FWeapon[a] := True;
-  for a := A_BULLETS to A_CELLS do FAmmo[a] := 30000;
+  for a := WP_FIRST to WP_LAST do FWeapon[a] := True;
+  for a := A_BULLETS to A_HIGH do FAmmo[a] := 30000;
   FRulez := FRulez+[R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
 end;
 
@@ -5523,6 +5541,7 @@ begin
           if FBFGFireCounter < 1 then
           begin
             FCurrWeap := WEAPON_KASTET;
+            resetWeaponQueue();
             FModel.SetWeapon(WEAPON_KASTET);
           end;
           if gFlash <> 0 then
@@ -5540,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:
@@ -5554,6 +5574,68 @@ begin
         FJetFuel := JET_MAX;
       end;
 
+    ITEM_MEDKIT_SMALL: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 10, PLAYER_HP_SOFT);
+    ITEM_MEDKIT_LARGE: if FHealth < PLAYER_HP_SOFT then IncMax(FHealth, 25, PLAYER_HP_SOFT);
+
+    ITEM_ARMOR_GREEN: if FArmor < PLAYER_AP_SOFT then FArmor := PLAYER_AP_SOFT;
+    ITEM_ARMOR_BLUE: if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
+
+    ITEM_SPHERE_BLUE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 100, PLAYER_HP_LIMIT);
+    ITEM_SPHERE_WHITE:
+      if (FHealth < PLAYER_HP_LIMIT) or (FArmor < PLAYER_AP_LIMIT) then
+      begin
+        if FHealth < PLAYER_HP_LIMIT then FHealth := PLAYER_HP_LIMIT;
+        if FArmor < PLAYER_AP_LIMIT then FArmor := PLAYER_AP_LIMIT;
+      end;
+
+    ITEM_WEAPON_SAW: FWeapon[WEAPON_SAW] := True;
+    ITEM_WEAPON_SHOTGUN1: FWeapon[WEAPON_SHOTGUN1] := True;
+    ITEM_WEAPON_SHOTGUN2: FWeapon[WEAPON_SHOTGUN2] := True;
+    ITEM_WEAPON_CHAINGUN: FWeapon[WEAPON_CHAINGUN] := True;
+    ITEM_WEAPON_ROCKETLAUNCHER: FWeapon[WEAPON_ROCKETLAUNCHER] := True;
+    ITEM_WEAPON_PLASMA: FWeapon[WEAPON_PLASMA] := True;
+    ITEM_WEAPON_BFG: FWeapon[WEAPON_BFG] := True;
+    ITEM_WEAPON_SUPERPULEMET: FWeapon[WEAPON_SUPERPULEMET] := True;
+    ITEM_WEAPON_FLAMETHROWER: FWeapon[WEAPON_FLAMETHROWER] := True;
+
+    ITEM_AMMO_BULLETS: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
+    ITEM_AMMO_BULLETS_BOX: if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 50, FMaxAmmo[A_BULLETS]);
+    ITEM_AMMO_SHELLS: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
+    ITEM_AMMO_SHELLS_BOX: if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 25, FMaxAmmo[A_SHELLS]);
+    ITEM_AMMO_ROCKET: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
+    ITEM_AMMO_ROCKET_BOX: if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 5, FMaxAmmo[A_ROCKETS]);
+    ITEM_AMMO_CELL: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
+    ITEM_AMMO_CELL_BIG: if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 100, FMaxAmmo[A_CELLS]);
+    ITEM_AMMO_FUELCAN: if FAmmo[A_FUEL] < FMaxAmmo[A_FUEL] then IncMax(FAmmo[A_FUEL], 100, FMaxAmmo[A_FUEL]);
+
+    ITEM_AMMO_BACKPACK:
+      if (FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS]) or
+         (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
+      begin
+        FMaxAmmo[A_BULLETS] := AmmoLimits[1, A_BULLETS];
+        FMaxAmmo[A_SHELLS] := AmmoLimits[1, A_SHELLS];
+        FMaxAmmo[A_ROCKETS] := AmmoLimits[1, A_ROCKETS];
+        FMaxAmmo[A_CELLS] := AmmoLimits[1, A_CELLS];
+        FMaxAmmo[A_FUEL] := AmmoLimits[1, A_FUEL];
+
+        if FAmmo[A_BULLETS] < FMaxAmmo[A_BULLETS] then IncMax(FAmmo[A_BULLETS], 10, FMaxAmmo[A_BULLETS]);
+        if FAmmo[A_SHELLS] < FMaxAmmo[A_SHELLS] then IncMax(FAmmo[A_SHELLS], 4, FMaxAmmo[A_SHELLS]);
+        if FAmmo[A_ROCKETS] < FMaxAmmo[A_ROCKETS] then IncMax(FAmmo[A_ROCKETS], 1, FMaxAmmo[A_ROCKETS]);
+        if FAmmo[A_CELLS] < FMaxAmmo[A_CELLS] then IncMax(FAmmo[A_CELLS], 40, FMaxAmmo[A_CELLS]);
+
+        FRulez := FRulez + [R_ITEM_BACKPACK];
+      end;
+
+    ITEM_KEY_RED: if not (R_KEY_RED in FRulez) then Include(FRulez, R_KEY_RED);
+    ITEM_KEY_GREEN: if not (R_KEY_GREEN in FRulez) then Include(FRulez, R_KEY_GREEN);
+    ITEM_KEY_BLUE: if not (R_KEY_BLUE in FRulez) then Include(FRulez, R_KEY_BLUE);
+
+    ITEM_BOTTLE: if FHealth < PLAYER_HP_LIMIT then IncMax(FHealth, 4, PLAYER_HP_LIMIT);
+    ITEM_HELMET: if FArmor < PLAYER_AP_LIMIT then IncMax(FArmor, 5, PLAYER_AP_LIMIT);
+
     else
       Exit;
   end;
@@ -5562,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;
@@ -5580,16 +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 i: DWORD;
+begin
+  if (Random(10) = 1) and (Times = 1) then
+    Exit;
+
+  for i := 1 to Times do
+  begin
+    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;
 
@@ -5599,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 : }
@@ -5609,31 +5708,53 @@ 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;
 
-procedure TCorpse.Damage(Value: Word; vx, vy: Integer);
-var
-  pm: TPlayerModel;
+function TCorpse.ObjPtr (): PObj; inline; begin result := @FObj; end;
+
+procedure TCorpse.positionChanged (); inline; begin end;
+
+procedure TCorpse.moveBy (dx, dy: Integer); inline;
+begin
+  if (dx <> 0) or (dy <> 0) then
+  begin
+    FObj.X += dx;
+    FObj.Y += dy;
+    positionChanged();
+  end;
+end;
+
+
+procedure TCorpse.getMapBox (out x, y, w, h: Integer); inline;
+begin
+  x := FObj.X+PLAYER_CORPSERECT.X;
+  y := FObj.Y+PLAYER_CORPSERECT.Y;
+  w := PLAYER_CORPSERECT.Width;
+  h := PLAYER_CORPSERECT.Height;
+end;
+
+
+procedure TCorpse.Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer);
+  var Blood: TModelBlood;
 begin
   if FState = CORPSE_STATE_REMOVEME then
     Exit;
@@ -5641,51 +5762,42 @@ 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;
 
-        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
+      );
 
-        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, 3, FObj.X, FObj.Y);
-        pm.Free;
-      end;
+      // Звук мяса от трупа:
+      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 := 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),
                   FObj.Y+PLAYER_CORPSERECT.Y+(PLAYER_CORPSERECT.Height div 2),
                   Value, vx, vy, 16, (PLAYER_CORPSERECT.Height*2) div 3,
-                  150, 0, 0);
+                  Blood.R, Blood.G, Blood.B, Blood.Kind);
     end;
 end;
 
-procedure TCorpse.Draw();
-begin
-  if FState = CORPSE_STATE_REMOVEME then
-    Exit;
-
-  if FAnimation <> nil then
-    FAnimation.Draw(FObj.X, FObj.Y, M_NONE);
-
-  if FAnimationMask <> nil then
-  begin
-    e_Colors := FColor;
-    FAnimationMask.Draw(FObj.X, FObj.Y, M_NONE);
-    e_Colors.R := 255;
-    e_Colors.G := 255;
-    e_Colors.B := 255;
-  end;
-end;
-
 procedure TCorpse.Update();
 var
   st: Word;
@@ -5693,17 +5805,21 @@ 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);
-
+    positionChanged(); // this updates spatial accelerators
     Exit;
   end;
 
-// Ñîïðîòèâëåíèå âîçäóõà äëÿ òðóïà:
+// Сопротивление воздуха для трупа:
   FObj.Vel.X := z_dec(FObj.Vel.X, 1);
 
   st := g_Obj_Move(@FObj, True, True, True);
+  positionChanged(); // this updates spatial accelerators
 
   if WordBool(st and MOVE_FALLOUT) then
   begin
@@ -5711,87 +5827,78 @@ begin
     Exit;
   end;
 
-  if FAnimation <> nil then
-    FAnimation.Update();
-  if FAnimationMask <> nil then
-    FAnimationMask.Update();
-end;
-
-procedure TCorpse.SaveState(var Mem: TBinMemoryWriter);
-var
-  sig: DWORD;
-  anim: Boolean;
-begin
-  if Mem = nil then
-    Exit;
-
-// Ñèãíàòóðà òðóïà:
-  sig := CORPSE_SIGNATURE; // 'CORP'
-  Mem.WriteDWORD(sig);
-// Ñîñòîÿíèå:
-  Mem.WriteByte(FState);
-// Íàêîïëåííûé óðîí:
-  Mem.WriteByte(FDamage);
-// Öâåò:
-  Mem.WriteByte(FColor.R);
-  Mem.WriteByte(FColor.G);
-  Mem.WriteByte(FColor.B);
-// Îáúåêò òðóïà:
-  Obj_SaveState(@FObj, Mem);
-// Åñòü ëè àíèìàöèÿ:
-  anim := FAnimation <> nil;
-  Mem.WriteBoolean(anim);
-// Åñëè åñòü - ñîõðàíÿåì:
-  if anim then
-    FAnimation.SaveState(Mem);
-// Åñòü ëè ìàñêà àíèìàöèè:
-  anim := FAnimationMask <> nil;
-  Mem.WriteBoolean(anim);
-// Åñëè åñòü - ñîõðàíÿåì:
-  if anim then
-    FAnimationMask.SaveState(Mem);
-end;
-
-procedure TCorpse.LoadState(var Mem: TBinMemoryReader);
-var
-  sig: DWORD;
-  anim: Boolean;
-begin
-  if Mem = nil then
-    Exit;
-
-// Ñèãíàòóðà òðóïà:
-  Mem.ReadDWORD(sig);
-  if sig <> CORPSE_SIGNATURE then // 'CORP'
-  begin
-    raise EBinSizeError.Create('TCorpse.LoadState: Wrong Corpse Signature');
-  end;
-// Ñîñòîÿíèå:
-  Mem.ReadByte(FState);
-// Íàêîïëåííûé óðîí:
-  Mem.ReadByte(FDamage);
-// Öâåò:
-  Mem.ReadByte(FColor.R);
-  Mem.ReadByte(FColor.G);
-  Mem.ReadByte(FColor.B);
-// Îáúåêò òðóïà:
-  Obj_LoadState(@FObj, Mem);
-// Åñòü ëè àíèìàöèÿ:
-  Mem.ReadBoolean(anim);
-// Åñëè åñòü - çàãðóæàåì:
+  if FModel <> nil then
+    FModel.Update;
+end;
+
+
+procedure TCorpse.SaveState (st: TStream);
+  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(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));
+  // animation
+  anim := (FModel <> nil);
+  utils.writeBool(st, anim);
+  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 FModel.AnimState.SaveState(st);
+end;
+
+
+procedure TCorpse.LoadState (st: TStream);
+  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);
+  // Цвет
+  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(Mem);
-  end;
-// Åñòü ëè ìàñêà àíèìàöèè:
-  Mem.ReadBoolean(anim);
-// Åñëè åñòü - çàãðóæàåì:
-  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(Mem);
+    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 : }
@@ -5810,7 +5917,7 @@ begin
 
   Inc(gNumBots);
 
-  for a := WEAPON_KASTET to WEAPON_SUPERPULEMET do
+  for a := WP_FIRST to WP_LAST do
   begin
     FDifficult.WeaponPrior[a] := WEAPON_PRIOR1[a];
     FDifficult.CloseWeaponPrior[a] := WEAPON_PRIOR2[a];
@@ -5824,20 +5931,13 @@ 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);
 
   FAIFlags := nil;
   FSelectedWeapon := FCurrWeap;
+  resetWeaponQueue();
   FTargetUID := 0;
 end;
 
@@ -5858,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;
 
@@ -5883,29 +5983,64 @@ var
   firew, fireh: Integer;
   angle: SmallInt;
   mon: TMonster;
-  pla: TPlayer;
+  pla, tpla: TPlayer;
   vsPlayer, vsMonster, ok: Boolean;
+
+
+  function monsUpdate (mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    if mon.alive and (mon.MonsterType <> MONSTER_BARREL) then
+    begin
+      if not TargetOnScreen(mon.Obj.X+mon.Obj.Rect.X, mon.Obj.Y+mon.Obj.Rect.Y) then exit;
+
+      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
+          UID := mon.UID;
+          X := mon.Obj.X;
+          Y := mon.Obj.Y;
+          cX := x2;
+          cY := y2;
+          Rect := mon.Obj.Rect;
+          Dist := g_PatchLength(x1, y1, x2, y2);
+          Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
+                  (y1-4 > Target.Y + mon.Obj.Rect.Y);
+          Visible := True;
+          IsPlayer := False;
+        end;
+      end;
+    end;
+  end;
+
 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');
 
       case FCurrWeap of
         WEAPON_PLASMA, WEAPON_SUPERPULEMET, WEAPON_CHAINGUN: PressKey(KEY_FIRE, 20);
-        WEAPON_SAW, WEAPON_KASTET, WEAPON_MEGAKASTET: PressKey(KEY_FIRE, 40);
+        WEAPON_SAW, WEAPON_KASTET, WEAPON_FLAMETHROWER: PressKey(KEY_FIRE, 40);
         else PressKey(KEY_FIRE);
       end;
     end;
 
-// Êîîðäèíàòû ñòâîëà:
+// Координаты ствола:
   x1 := FObj.X + WEAPONPOINT[FDirection].X;
   y1 := FObj.Y + WEAPONPOINT[FDirection].Y;
 
@@ -5913,18 +6048,20 @@ begin
 
   ok := False;
   if Target.UID <> 0 then
-    begin // Öåëü åñòü - íàñòðàèâàåì
+    begin // Цель есть - настраиваем
       if (g_GetUIDType(Target.UID) = UID_PLAYER) and
           vsPlayer then
-        begin // Èãðîê
-          with g_Player_Get(Target.UID) do
-            begin
-              if (@FObj) <> nil then
+        begin // Игрок
+          tpla := g_Player_Get(Target.UID);
+          if tpla <> nil then
+            with tpla do
               begin
-                Target.X := FObj.X;
-                Target.Y := FObj.Y;
+                if (@FObj) <> nil then
+                begin
+                  Target.X := FObj.X;
+                  Target.Y := FObj.Y;
+                end;
               end;
-            end;
 
           Target.cX := Target.X + PLAYER_RECT_CX;
           Target.cY := Target.Y + PLAYER_RECT_CY;
@@ -5938,8 +6075,8 @@ begin
       else
         if (g_GetUIDType(Target.UID) = UID_MONSTER) and
             vsMonster then
-          begin // Ìîíñòð
-            mon := g_Monsters_Get(Target.UID);
+          begin // Монстр
+            mon := g_Monsters_ByUID(Target.UID);
             if mon <> nil then
               begin
                 Target.X := mon.Obj.X;
@@ -5958,7 +6095,7 @@ begin
     end;
 
   if not ok then
-    begin // Öåëè íåò - îáíóëÿåì
+    begin // Цели нет - обнуляем
       Target.X := 0;
       Target.Y := 0;
       Target.cX := 0;
@@ -5970,13 +6107,13 @@ 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].Live) and
+        if (gPlayers[a] <> nil) and (gPlayers[a].alive) and
            (gPlayers[a].FUID <> FUID) and
            (not SameTeam(FUID, gPlayers[a].FUID)) and
            (not gPlayers[a].NoTarget) and
@@ -5989,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
@@ -6011,122 +6148,88 @@ begin
               end;
           end;
 
-  // Ìîíñòðû:
-    if vsMonster and (gMonsters <> nil) then
-      for a := 0 to High(gMonsters) do
-        if (gMonsters[a] <> nil) and (gMonsters[a].Live) and
-           (gMonsters[a].MonsterType <> MONSTER_BARREL) then
-          begin
-            mon := gMonsters[a];
-
-            if not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
-                                  mon.Obj.Y + mon.Obj.Rect.Y) then
-              Continue;
-
-            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
-                    UID := mon.UID;
-                    X := mon.Obj.X;
-                    Y := mon.Obj.Y;
-                    cX := x2;
-                    cY := y2;
-                    Rect := mon.Obj.Rect;
-                    Dist := g_PatchLength(x1, y1, x2, y2);
-                    Line := (y1+4 < Target.Y + mon.Obj.Rect.Y + mon.Obj.Rect.Height) and
-                            (y1-4 > Target.Y + mon.Obj.Rect.Y);
-                    Visible := True;
-                    IsPlayer := False;
-                  end;
-              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 // Åñëè çäîðîâû - äîãîíÿåì
-            if ((RunDirection() = D_LEFT) and (Target.X > FObj.X)) then
+          begin // Если здоровы - догоняем
+            if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
               SetAIFlag('GORIGHT', '1');
-            if ((RunDirection() = D_RIGHT) and (Target.X < FObj.X)) then
+            if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
               SetAIFlag('GOLEFT', '1');
           end
         else
-          begin // Åñëè ïîáèòû - óáåãàåì
-            if ((RunDirection() = D_LEFT) and (Target.X < FObj.X)) then
+          begin // Если побиты - убегаем
+            if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
               SetAIFlag('GORIGHT', '1');
-            if ((RunDirection() = D_RIGHT) and (Target.X > FObj.X)) then
+            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 // Åñëè çäîðîâû - äîãîíÿåì
-            if ((RunDirection() = D_LEFT) and (Target.X > FObj.X)) then
+          begin // Если здоровы - догоняем
+            if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
               SetAIFlag('GORIGHT', '1');
-            if ((RunDirection() = D_RIGHT) and (Target.X < FObj.X)) then
+            if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
               SetAIFlag('GOLEFT', '1');
           end
         else
-          begin // Åñëè ïîáèòû - çàáûâàåì î öåëè è óáåãàåì
+          begin // Если побиты - забываем о цели и убегаем
             Target.UID := 0;
-            if ((RunDirection() = D_LEFT) and (Target.X < FObj.X)) then
+            if ((RunDirection() = TDirection.D_LEFT) and (Target.X < FObj.X)) then
               SetAIFlag('GORIGHT', '1');
-            if ((RunDirection() = D_RIGHT) and (Target.X > FObj.X)) then
+            if ((RunDirection() = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
               SetAIFlag('GOLEFT', '1');
           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() = D_LEFT) and (Target.X > FObj.X)) then
+            if ((RunDirection() = TDirection.D_LEFT) and (Target.X > FObj.X)) then
               SetAIFlag('GORIGHT', '1');
-            if ((RunDirection() = D_RIGHT) and (Target.X < FObj.X)) then
+            if ((RunDirection() = TDirection.D_RIGHT) and (Target.X < FObj.X)) then
               SetAIFlag('GOLEFT', '1');
           end;
       end;
 
-  // Âûáèðàåì óãîë ââåðõ:
-    if FDirection = D_LEFT then
+  // Выбираем угол вверх:
+    if FDirection = TDirection.D_LEFT then
       angle := ANGLE_LEFTUP
     else
       angle := ANGLE_RIGHTUP;
@@ -6134,19 +6237,19 @@ 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 = D_LEFT then
+  // Выбираем угол вниз:
+    if FDirection = TDirection.D_LEFT then
       angle := ANGLE_LEFTDOWN
     else
       angle := ANGLE_RIGHTDOWN;
@@ -6154,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 = D_LEFT) and (Target.X < FObj.X)) or
-            ((FDirection = D_RIGHT) and (Target.X > FObj.X)) then
-        begin // òî íóæíî ñòðåëÿòü âïåðåä
+      // Если идем в сторону цели, то надо стрелять:
+        if ((FDirection = TDirection.D_LEFT) and (Target.X < FObj.X)) or
+            ((FDirection = TDirection.D_RIGHT) and (Target.X > FObj.X)) then
+        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
@@ -6191,89 +6294,92 @@ 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.Live) or pla.NoTarget or
+              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 // Öåëü - ìîíñòð
-              mon := g_Monsters_Get(Target.UID);
-              if (mon = nil) or (not mon.Live) then
-                Target.UID := 0; // òî çàáûòü öåëü
+            begin // Цель - монстр
+              mon := g_Monsters_ByUID(Target.UID);
+              if (mon = nil) or (not mon.alive) then
+                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() = D_RIGHT then
-          begin // Èäåì íå â òó ñòîðîíó
+        if RunDirection() = TDirection.D_RIGHT then
+          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() = D_LEFT then
-            begin // Èäåì íå â òó ñòîðîíó
+          if RunDirection() = TDirection.D_LEFT then
+            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 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 = D_LEFT then
+          // Ищем цель сверху и стреляем, если есть:
+            if FDirection = TDirection.D_LEFT then
               angle := ANGLE_LEFTUP
             else
               angle := ANGLE_RIGHTUP;
@@ -6291,8 +6397,8 @@ begin
                 SetAIFlag('NEEDSEEUP', '1');
               end;
 
-          // Èùåì öåëü ñíèçó è ñòðåëÿåì, åñëè åñòü:
-            if FDirection = D_LEFT then
+          // Ищем цель снизу и стреляем, если есть:
+            if FDirection = TDirection.D_LEFT then
               angle := ANGLE_LEFTDOWN
             else
               angle := ANGLE_RIGHTDOWN;
@@ -6311,30 +6417,30 @@ begin
               end;
           end;
 
-      // Åñëè öåëü "ïåðåä íîñîì", òî ñòðåëÿåì:
+      // Если цель "перед носом", то стреляем:
         if targets[a].Line and targets[a].Visible and
-            (((FDirection = D_LEFT) and (targets[a].X < FObj.X)) or
-            ((FDirection = D_RIGHT) and (targets[a].X > FObj.X))) then
+            (((FDirection = TDirection.D_LEFT) and (targets[a].X < FObj.X)) or
+            ((FDirection = TDirection.D_RIGHT) and (targets[a].X > FObj.X))) then
         begin
           SetAIFlag('NEEDFIRE', '1');
           Break;
         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);
@@ -6346,7 +6452,7 @@ procedure TBot.Update();
 var
   EnableAI: Boolean;
 begin
-  if not FLive then
+  if not FAlive then
   begin // Respawn
     ReleaseKeys();
     PressKey(KEY_UP);
@@ -6355,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
@@ -6367,6 +6473,10 @@ begin
     begin
       UpdateMove();
       UpdateCombat();
+    end
+    else
+    begin
+      RealizeCurrentWeapon();
     end;
   end;
 
@@ -6387,33 +6497,33 @@ begin
   Result := FKeys[Key].Pressed;
 end;
 
-function TBot.GetAIFlag(fName: String20): String20;
+function TBot.GetAIFlag(aName: String20): String20;
 var
   a: Integer;
 begin
   Result := '';
 
-  fName := LowerCase(fName);
+  aName := LowerCase(aName);
 
   if FAIFlags <> nil then
     for a := 0 to High(FAIFlags) do
-      if LowerCase(FAIFlags[a].Name) = fName then
+      if LowerCase(FAIFlags[a].Name) = aName then
       begin
         Result := FAIFlags[a].Value;
         Break;
       end;
 end;
 
-procedure TBot.RemoveAIFlag(fName: String20);
+procedure TBot.RemoveAIFlag(aName: String20);
 var
   a, b: Integer;
 begin
   if FAIFlags = nil then Exit;
 
-  fName := LowerCase(fName);
+  aName := LowerCase(aName);
 
   for a := 0 to High(FAIFlags) do
-    if LowerCase(FAIFlags[a].Name) = fName then
+    if LowerCase(FAIFlags[a].Name) = aName then
     begin
       if a <> High(FAIFlags) then
         for b := a to High(FAIFlags)-1 do
@@ -6424,7 +6534,7 @@ begin
     end;
 end;
 
-procedure TBot.SetAIFlag(fName, fValue: String20);
+procedure TBot.SetAIFlag(aName, fValue: String20);
 var
   a: Integer;
   ok: Boolean;
@@ -6432,11 +6542,11 @@ begin
   a := 0;
   ok := False;
 
-  fName := LowerCase(fName);
+  aName := LowerCase(aName);
 
   if FAIFlags <> nil then
     for a := 0 to High(FAIFlags) do
-      if LowerCase(FAIFlags[a].Name) = fName then
+      if LowerCase(FAIFlags[a].Name) = aName then
       begin
         ok := True;
         Break;
@@ -6448,7 +6558,7 @@ begin
     SetLength(FAIFlags, Length(FAIFlags)+1);
     with FAIFlags[High(FAIFlags)] do
     begin
-      Name := fName;
+      Name := aName;
       Value := fValue;
     end;
   end;
@@ -6461,7 +6571,7 @@ procedure TBot.UpdateMove;
     ReleaseKey(KEY_LEFT);
     ReleaseKey(KEY_RIGHT);
     PressKey(KEY_LEFT, Time);
-    SetDirection(D_LEFT);
+    SetDirection(TDirection.D_LEFT);
   end;
 
   procedure GoRight(Time: Word = 1);
@@ -6469,7 +6579,7 @@ procedure TBot.UpdateMove;
     ReleaseKey(KEY_LEFT);
     ReleaseKey(KEY_RIGHT);
     PressKey(KEY_RIGHT, Time);
-    SetDirection(D_RIGHT);
+    SetDirection(TDirection.D_RIGHT);
   end;
 
   function Rnd(a: Word): Boolean;
@@ -6479,7 +6589,7 @@ procedure TBot.UpdateMove;
 
   procedure Turn(Time: Word = 1200);
   begin
-    if RunDirection() = D_LEFT then GoRight(Time) else GoLeft(Time);
+    if RunDirection() = TDirection.D_LEFT then GoRight(Time) else GoLeft(Time);
   end;
 
   procedure Stop();
@@ -6500,7 +6610,7 @@ procedure TBot.UpdateMove;
 
   function CanRun(): Boolean;
   begin
-    if RunDirection() = D_LEFT then Result := CanRunLeft() else Result := CanRunRight();
+    if RunDirection() = TDirection.D_LEFT then Result := CanRunLeft() else Result := CanRunRight();
   end;
 
   procedure Jump(Time: Word = 30);
@@ -6512,8 +6622,8 @@ procedure TBot.UpdateMove;
   var
     x, sx: Integer;
   begin
-    { TODO 5 : Ëåñòíèöû }
-    sx := IfThen(RunDirection() = D_LEFT, -1, 1);
+    { 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
          (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
@@ -6530,8 +6640,8 @@ procedure TBot.UpdateMove;
   var
     x, sx, xx: Integer;
   begin
-    { TODO 5 : Ëåñòíèöû }
-    sx := IfThen(RunDirection() = D_LEFT, -1, 1);
+    { 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
          (not CollideLevel(x*sx, PLAYER_RECT.Height)) and
@@ -6554,7 +6664,7 @@ procedure TBot.UpdateMove;
   begin
     Result := False;
 
-    sx := IfThen(RunDirection() = D_LEFT, -1, 1);
+    sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
     y := 3;
 
     for x := 1 to PLAYER_RECT.Width do
@@ -6600,18 +6710,18 @@ procedure TBot.UpdateMove;
 
   function BelowLadder(): Boolean;
   begin
-    Result := (FullInStep(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and
-              not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
-              (FullInStep(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and
-              not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
+    Result := (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) and
+              not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
+              (FullInStep(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) and
+              not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
   end;
 
   function BelowLiftUp(): Boolean;
   begin
-    Result := ((FullInLift(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and
-              not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
-              ((FullInLift(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and
-              not CollideLevel(IfThen(RunDirection() = D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
+    Result := ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height) = -1) and
+              not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -PLAYER_RECT.Height)) or
+              ((FullInLift(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP) = -1) and
+              not CollideLevel(IfThen(RunDirection() = TDirection.D_LEFT, -1, 1)*(PLAYER_RECT.Width div 2), -BOT_MAXJUMP));
   end;
 
   function OnTopLift(): Boolean;
@@ -6623,7 +6733,7 @@ procedure TBot.UpdateMove;
   var
     sx, y: Integer;
   begin
-    sx := IfThen(RunDirection() = D_LEFT, -1, 1);
+    sx := IfThen(RunDirection() = TDirection.D_LEFT, -1, 1);
 
     Result := False;
 
@@ -6703,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);
@@ -6715,7 +6825,7 @@ begin
       Jump();
     end;
 
-// Èäåì âëåâî, åñëè íàäî áûëî:
+// Идем влево, если надо было:
   if GetAIFlag('GOLEFT') <> '' then
     begin
       RemoveAIFlag('GOLEFT');
@@ -6723,7 +6833,7 @@ begin
         GoLeft(360);
     end;
 
-// Èäåì âïðàâî, åñëè íàäî áûëî:
+// Идем вправо, если надо было:
   if GetAIFlag('GORIGHT') <> '' then
     begin
       RemoveAIFlag('GORIGHT');
@@ -6731,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);
@@ -6754,7 +6864,7 @@ begin
       RemoveAIFlag('NEEDSEEUP');
     end;
 
-// Ñìîòðèì âíèç, åñëè íàäî áûëî:
+// Смотрим вниз, если надо было:
   if GetAIFlag('NEEDSEEDOWN') <> '' then
     begin
       ReleaseKey(KEY_UP);
@@ -6763,7 +6873,7 @@ begin
       RemoveAIFlag('NEEDSEEDOWN');
     end;
 
-// Åñëè íóæíî áûëî â äûðó è ìû íå íà çåìëå, òî ïîêîðíî ëåòèì:
+// Если нужно было в дыру и мы не на земле, то покорно летим:
   if GetAIFlag('GOINHOLE') <> '' then
     if not OnGround() then
       begin
@@ -6773,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
@@ -6787,40 +6897,40 @@ begin
         else
           GoRight(360);
 
-// Åñëè íà çåìëå è ìîæíî ïîäïðûãíóòü, òî, âîçìîæíî, ïðûãàåì:
+// Если на земле и можно подпрыгнуть, то, возможно, прыгаем:
   if OnGround() and
-      CanJumpUp(IfThen(RunDirection() = D_LEFT, -1, 1)*32) 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
@@ -6829,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();
@@ -6865,6 +6975,7 @@ var
       WEAPON_PLASMA: Result := FAmmo[A_CELLS] >= 10;
       WEAPON_BFG: Result := FAmmo[A_CELLS] >= 40;
       WEAPON_SUPERPULEMET: Result := FAmmo[A_SHELLS] >= 1;
+      WEAPON_FLAMETHROWER: Result := FAmmo[A_FUEL] >= 1;
       else Result := True;
     end;
   end;
@@ -6873,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
@@ -6882,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
@@ -6941,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);
@@ -6949,8 +7060,8 @@ begin
       else
         if (g_GetUIDType(FLastSpawnerUID) = UID_MONSTER) and
            LongBool(gGameSettings.Options and GAME_OPTION_BOTVSMONSTER) then
-        begin // Ìîíñòð
-          mon := g_Monsters_Get(FLastSpawnerUID);
+        begin // Монстр
+          mon := g_Monsters_ByUID(FLastSpawnerUID);
           ok := not TargetOnScreen(mon.Obj.X + mon.Obj.Rect.X,
                                    mon.Obj.Y + mon.Obj.Rect.Y);
         end;
@@ -6967,7 +7078,7 @@ function TBot.RunDirection(): TDirection;
 begin
   if Abs(Vel.X) >= 1 then
   begin
-    if Vel.X > 0 then Result := D_RIGHT else Result := D_LEFT;
+    if Vel.X > 0 then Result := TDirection.D_RIGHT else Result := TDirection.D_LEFT;
   end else
     Result := FDirection;
 end;
@@ -6984,64 +7095,89 @@ begin
   Result := Round((255-a)/255*radius*(Random(2)-1));
 end;
 
-procedure TBot.SaveState(var Mem: TBinMemoryWriter);
+
+procedure TDifficult.save (st: TStream);
+begin
+  utils.writeInt(st, Byte(DiagFire));
+  utils.writeInt(st, Byte(InvisFire));
+  utils.writeInt(st, Byte(DiagPrecision));
+  utils.writeInt(st, Byte(FlyPrecision));
+  utils.writeInt(st, Byte(Cover));
+  utils.writeInt(st, Byte(CloseJump));
+  st.WriteBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
+  st.WriteBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
+end;
+
+procedure TDifficult.load (st: TStream);
+begin
+  DiagFire := utils.readByte(st);
+  InvisFire := utils.readByte(st);
+  DiagPrecision := utils.readByte(st);
+  FlyPrecision := utils.readByte(st);
+  Cover := utils.readByte(st);
+  CloseJump := utils.readByte(st);
+  st.ReadBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
+  st.ReadBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
+end;
+
+
+procedure TBot.SaveState (st: TStream);
 var
   i: Integer;
-  dw: DWORD;
-  p: Pointer;
-begin
-  inherited SaveState(Mem);
-
-// Âûáðàííîå îðóæèå:
-  Mem.WriteByte(FSelectedWeapon);
-// UID öåëè:
-  Mem.WriteWord(FTargetUID);
-// Âðåìÿ ïîòåðè öåëè:
-  Mem.WriteDWORD(FLastVisible);
-// Êîëè÷åñòâî ôëàãîâ ÈÈ:
+  dw: Integer;
+begin
+  inherited SaveState(st);
+  utils.writeSign(st, 'BOT0');
+  // Выбранное оружие
+  utils.writeInt(st, Byte(FSelectedWeapon));
+  // UID цели
+  utils.writeInt(st, Word(FTargetUID));
+  // Время потери цели
+  utils.writeInt(st, LongWord(FLastVisible));
+  // Количество флагов ИИ
   dw := Length(FAIFlags);
-  Mem.WriteDWORD(dw);
-// Ôëàãè ÈÈ:
-  for i := 0 to Integer(dw)-1 do
+  utils.writeInt(st, LongInt(dw));
+  // Флаги ИИ
+  for i := 0 to dw-1 do
   begin
-    Mem.WriteString(FAIFlags[i].Name, 20);
-    Mem.WriteString(FAIFlags[i].Value, 20);
+    utils.writeStr(st, FAIFlags[i].Name, 20);
+    utils.writeStr(st, FAIFlags[i].Value, 20);
   end;
-// Íàñòðîéêè ñëîæíîñòè:
-  p := @FDifficult;
-  Mem.WriteMemory(p, SizeOf(TDifficult));
+  // Настройки сложности
+  FDifficult.save(st);
 end;
 
-procedure TBot.LoadState(var Mem: TBinMemoryReader);
+
+procedure TBot.LoadState (st: TStream);
 var
   i: Integer;
-  dw: DWORD;
-  p: Pointer;
-begin
-  inherited LoadState(Mem);
-
-// Âûáðàííîå îðóæèå:
-  Mem.ReadByte(FSelectedWeapon);
-// UID öåëè:
-  Mem.ReadWord(FTargetUID);
-// Âðåìÿ ïîòåðè öåëè:
-  Mem.ReadDWORD(FLastVisible);
-// Êîëè÷åñòâî ôëàãîâ ÈÈ:
-  Mem.ReadDWORD(dw);
+  dw: Integer;
+begin
+  inherited LoadState(st);
+  if not utils.checkSign(st, 'BOT0') then raise XStreamError.Create('invalid bot signature');
+  // Выбранное оружие
+  FSelectedWeapon := utils.readByte(st);
+  // 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 Integer(dw)-1 do
+  // Флаги ИИ
+  for i := 0 to dw-1 do
   begin
-    Mem.ReadString(FAIFlags[i].Name);
-    Mem.ReadString(FAIFlags[i].Value);
+    FAIFlags[i].Name := utils.readStr(st, 20);
+    FAIFlags[i].Value := utils.readStr(st, 20);
   end;
-// Íàñòðîéêè ñëîæíîñòè:
-  Mem.ReadMemory(p, dw);
-  if dw <> SizeOf(TDifficult) then
-  begin
-    raise EBinSizeError.Create('TBot.LoadState: Wrong FDifficult Size');
-  end;
-  FDifficult := TDifficult(p^);
+  // Настройки сложности
+  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.