DEADSOFTWARE

Revert "fixed weapon switching over the network; bumped protocol version"
[d2df-sdl.git] / src / game / g_player.pas
index f277a4e8ab74fa5ce9992b49c8af0b215c4e5e27..f770a8c34270ed8d139d41ba8b5784d7af6d7aa9 100644 (file)
@@ -1,4 +1,4 @@
-(* Copyright (C)  DooM 2D:Forever Developers
+(* 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
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *)
 {$INCLUDE ../shared/a_modes.inc}
+{$M+}
 unit g_player;
 
 interface
 
 uses
+  SysUtils, Classes,
+  {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
   e_graphics, g_playermodel, g_basic, g_textures,
-  g_weapons, g_phys, g_sound, g_saveload, MAPSTRUCT,
-  BinEditor, g_panel, z_aabbtree;
+  g_weapons, g_phys, g_sound, g_saveload, MAPDEF,
+  g_panel;
 
 const
   KEY_LEFT       = 1;
@@ -132,13 +135,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;
@@ -160,7 +163,7 @@ type
     FSecrets:   Integer;
     FCurrWeap:  Byte;
     FNextWeap:  WORD;
-    FNextWeapDelay: Byte; // frames
+    FNextWeapDelay: Byte; // frames (unused)
     FBFGFireCounter: SmallInt;
     FLastSpawnerUID: Word;
     FLastHit:   Byte;
@@ -173,6 +176,7 @@ type
     FSavedState: TPlayerSavedState;
 
     FModel:     TPlayerModel;
+    FPunchAnim: TAnimation;
     FActionPrior:    Byte;
     FActionAnim:     Byte;
     FActionForce:    Boolean;
@@ -194,35 +198,43 @@ 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);
-    procedure   OnFireFlame(Times: DWORD = 1);
-    function    GetAmmoByWeapon(Weapon: Byte): Word;
-    procedure   SetAction(Action: Byte; Force: Boolean = False);
-    procedure   OnDamage(Angle: SmallInt); virtual;
-    function    firediry(): Integer;
-
-    procedure   Run(Direction: TDirection);
-    procedure   NextWeapon();
-    procedure   PrevWeapon();
-    procedure   SeeUp();
-    procedure   SeeDown();
-    procedure   Fire();
-    procedure   Jump();
-    procedure   Use();
+    mEDamageType: Integer;
+
+    // client-side only
+    weaponSwitchKeyReleased: array[0..16] of Byte; // bit 0: was released on prev frame; bit 1: new status
+
+
+    function CollideLevel(XInc, YInc: Integer): Boolean;
+    function StayOnStep(XInc, YInc: Integer): Boolean;
+    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);
+    function GetAmmoByWeapon(Weapon: Byte): Word;
+    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;
 
-    function getMapAABB (): AABB2D;
+    procedure doDamage (v: Integer);
+
+    function followCorpse(): Boolean;
 
   public
     FDamageBuffer:   Integer;
@@ -251,6 +263,11 @@ type
     FDummy:     Boolean;
     FFireTime:  Integer;
 
+    // debug: viewport offset
+    viewPortX, viewPortY, viewPortW, viewPortH: Integer;
+
+    function isValidViewPort (): Boolean; inline;
+
     constructor Create(); virtual;
     destructor  Destroy(); override;
     procedure   Respawn(Silent: Boolean; Force: Boolean = False); virtual;
@@ -296,13 +313,14 @@ type
     procedure   DrawPickup();
     procedure   DrawRulez();
     procedure   DrawAim();
+    procedure   DrawIndicator();
     procedure   DrawBubble();
     procedure   DrawGUI();
     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);
@@ -314,7 +332,19 @@ type
     procedure   CatchFire(Attacker: Word);
 
     //WARNING! this does nothing for now, but still call it!
-    procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
+    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;
+
+    procedure releaseAllWeaponSwitchKeys ();
+    procedure weaponSwitchKeysStateChange (index: Integer; pressed: Boolean);
+    function isWeaponSwitchKeyReleased (index: Integer): Boolean;
+    procedure weaponSwitchKeysShiftNewStates ();
+
+  public
+    property    Vel: TPoint2i read FObj.Vel;
+    property    Obj: TObj read FObj;
 
     property    Name: String read FName write FName;
     property    Model: TPlayerModel read FModel;
@@ -332,7 +362,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;
@@ -342,26 +372,62 @@ 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    UID: Word read FUID write FUID;
     property    JustTeleported: Boolean read FJustTeleported write FJustTeleported;
     property    NetTime: LongWord read FNetTime write FNetTime;
 
-    property mapAABB: AABB2D read getMapAABB;
+  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 [WP_FIRST..WP_LAST] of Byte;
-    CloseWeaponPrior: Array [WP_FIRST..WP_LAST] 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
@@ -369,7 +435,7 @@ type
     Value: String;
   end;
 
-  TBot = class (TPlayer)
+  TBot = class(TPlayer)
   private
     FSelectedWeapon:  Byte;
     FTargetUID:       Word;
@@ -402,35 +468,43 @@ type
     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;
+    alive:    Boolean;
     ID:       DWORD;
     MaskID:   DWORD;
     RAngle:   Integer;
     Color:    TRGB;
     Obj:      TObj;
 
-    procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
+    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 positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
+    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;
@@ -438,6 +512,7 @@ type
     FDamage:        Byte;
     FColor:         TRGB;
     FObj:           TObj;
+    FPlayerUID:     Word;
     FAnimation:     TAnimation;
     FAnimationMask: TAnimation;
 
@@ -447,12 +522,17 @@ type
     procedure   Damage(Value: 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);
 
-    procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
+    procedure getMapBox (out x, y, w, h: Integer); inline;
+    procedure moveBy (dx, dy: Integer); inline;
 
-    property    Obj: TObj read FObj;
+    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;
   end;
@@ -471,6 +551,7 @@ var
   gFly: Boolean = False;
   gAimLine: Boolean = False;
   gChatBubble: Byte = 0;
+  gPlayerIndicator: Boolean = True;
   gNumBots: Word = 0;
   gLMSPID1: Word = 0;
   gLMSPID2: Word = 0;
@@ -490,7 +571,7 @@ 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_UpdateAll();
@@ -510,8 +591,8 @@ 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_Player_Corpses_SaveState (st: TStream);
+procedure g_Player_Corpses_LoadState (st: TStream);
 procedure g_Bot_Add(Team, Difficult: Byte);
 procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1);
 procedure g_Bot_MixNames();
@@ -520,9 +601,17 @@ 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;
+{$INCLUDE ../nogl/noGLuses.inc}
+{$IFDEF ENABLE_HOLMES}
+  g_holmes,
+{$ENDIF}
+  e_log, g_map, g_items, g_console, g_gfx, Math,
+  g_options, g_triggers, g_menu, g_game, g_grid,
+  wadreader, g_main, g_monsters, CONFIG, g_language,
+  g_net, g_netmsg, g_window,
+  utils, xstreams;
+
+const PLR_SAVE_VERSION = 0;
 
 type
   TBotProfile = record
@@ -559,7 +648,7 @@ const
   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));
+  WEAPONPOINT: Array [TDirection] of TDFPoint = ((X:16; Y:32), (X:47; Y:32));
   BOT_MAXJUMP = 84;
   BOT_LONGDIST   = 300;
   BOT_UNSAFEDIST = 128;
@@ -608,10 +697,6 @@ var
   BotList: Array of TBotProfile;
 
 
-procedure TGib.positionChanged (); begin end;
-procedure TShell.positionChanged (); begin end;
-
-
 function Lerp(X, Y, Factor: Integer): Integer;
 begin
   Result := X + ((Y - X) div Factor);
@@ -740,52 +825,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
@@ -793,133 +867,115 @@ 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);
-// Ñëåäóþùåå æåëàåìîå îðóæèå:
-  Mem.ReadWord(gPlayers[a].FNextWeap);
-// ...è ïàóçà:
-  Mem.ReadByte(gPlayers[a].FNextWeapDelay);
-// Âðåìÿ çàðÿäêè 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_HIGH do
-    Mem.ReadWord(gPlayers[a].FAmmo[i]);
-// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.ReadWord(gPlayers[a].FMaxAmmo[i]);
-// Íàëè÷èå îðóæèÿ:
-  for i := WP_FIRST to WP_LAST do
-    Mem.ReadBoolean(gPlayers[a].FWeapon[i]);
-// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
-  for i := WP_FIRST to WP_LAST 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].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;
@@ -950,7 +1006,7 @@ end;
 
 procedure g_Bot_Add(Team, Difficult: Byte);
 var
-  m: SArray;
+  m: SSArray;
   _name, _model: String;
   a, tr, tb: Integer;
 begin
@@ -1047,7 +1103,7 @@ end;
 
 procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1);
 var
-  m: SArray;
+  m: SSArray;
   _name, _model: String;
   a: Integer;
 begin
@@ -1184,7 +1240,7 @@ var
   s: String;
   a, b: Integer;
   config: TConfig;
-  sa: SArray;
+  sa: SSArray;
 begin
   BotNames := nil;
 
@@ -1435,7 +1491,7 @@ 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;
 
@@ -1466,11 +1522,20 @@ end;
 
 procedure g_Player_CreateCorpse(Player: TPlayer);
 var
+  i: Integer;
   find_id: DWORD;
   ok: Boolean;
 begin
-  if Player.Live then
+  if Player.alive then
     Exit;
+
+// Ðàçðûâàåì ñâÿçü ñ ïðåæíèì òðóïîì:
+  if gCorpses <> nil then
+    for i := 0 to High(gCorpses) do
+      if gCorpses[i] <> nil then
+        if gCorpses[i].FPlayerUID = Player.FUID then
+          gCorpses[i].FPlayerUID := 0;
+
   if Player.FObj.Y >= gMapInfo.Height+128 then
     Exit;
 
@@ -1496,6 +1561,7 @@ begin
         gCorpses[find_id].FColor := FModel.Color;
         gCorpses[find_id].FObj.Vel := FObj.Vel;
         gCorpses[find_id].FObj.Accel := FObj.Accel;
+        gCorpses[find_id].FPlayerUID := FUID;
       end
     else
       g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX,
@@ -1536,7 +1602,7 @@ begin
       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));
@@ -1555,11 +1621,13 @@ procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: string; fColor: TRGB);
 var
   a: Integer;
   GibsArray: TGibsArray;
+  Blood: TModelBlood;
 begin
   if (gGibs = nil) or (Length(gGibs) = 0) then
     Exit;
   if not g_PlayerModel_GetGibs(ModelName, GibsArray) then
     Exit;
+  Blood := g_PlayerModel_GetBlood(ModelName);
 
   for a := 0 to High(GibsArray) do
     with gGibs[CurrentGib] do
@@ -1567,7 +1635,7 @@ begin
       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);
@@ -1578,7 +1646,7 @@ begin
 
       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
@@ -1608,7 +1676,7 @@ 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
           vel := Obj.Vel;
@@ -1617,7 +1685,7 @@ begin
 
           if WordBool(mr and MOVE_FALLOUT) then
           begin
-            Live := False;
+            alive := False;
             Continue;
           end;
 
@@ -1658,7 +1726,7 @@ begin
 // Ãèëüçû:
   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
           vel := Obj.Vel;
@@ -1667,7 +1735,7 @@ begin
 
           if WordBool(mr and MOVE_FALLOUT) or (gShells[i].Timeout < gTime) then
           begin
-            Live := False;
+            alive := False;
             Continue;
           end;
 
@@ -1704,14 +1772,57 @@ begin
         end;
 end;
 
+
+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;
+
+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;
+
+
+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 TShell.moveBy (dx, dy: Integer); inline;
+begin
+  if (dx <> 0) or (dy <> 0) then
+  begin
+    Obj.X += dx;
+    Obj.Y += dy;
+    positionChanged();
+  end;
+end;
+
+
+procedure TGib.positionChanged (); inline; begin end;
+procedure TShell.positionChanged (); inline; begin end;
+
+
 procedure g_Player_DrawCorpses();
 var
   i: Integer;
-  a: TPoint;
+  a: TDFPoint;
 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
           if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
@@ -1720,10 +1831,10 @@ begin
           a.X := Obj.Rect.X+(Obj.Rect.Width div 2);
           a.y := Obj.Rect.Y+(Obj.Rect.Height div 2);
 
-          e_DrawAdv(ID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE);
+          e_DrawAdv(ID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
 
           e_Colors := Color;
-          e_DrawAdv(MaskID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE);
+          e_DrawAdv(MaskID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
           e_Colors.R := 255;
           e_Colors.G := 255;
           e_Colors.B := 255;
@@ -1738,11 +1849,11 @@ end;
 procedure g_Player_DrawShells();
 var
   i: Integer;
-  a: TPoint;
+  a: TDFPoint;
 begin
   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
           if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
@@ -1751,7 +1862,7 @@ begin
           a.X := CX;
           a.Y := CY;
 
-          e_DrawAdv(SpriteID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, M_NONE);
+          e_DrawAdv(SpriteID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
         end;
 end;
 
@@ -1774,78 +1885,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].FModelName);
+      // Òèï ñìåðòè
+      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),
@@ -1910,7 +2013,7 @@ begin
     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
@@ -1953,12 +2056,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
@@ -1989,6 +2092,12 @@ end;
 
 constructor TPlayer.Create();
 begin
+  viewPortX := 0;
+  viewPortY := 0;
+  viewPortW := 0;
+  viewPortH := 0;
+  mEDamageType := HIT_SOME;
+
   FIamBot := False;
   FDummy := False;
   FSpawned := False;
@@ -2029,17 +2138,68 @@ begin
   FNetTime := 0;
 
   resetWeaponQueue();
+  releaseAllWeaponSwitchKeys();
+end;
+
+
+procedure TPlayer.releaseAllWeaponSwitchKeys ();
+var
+  f: Integer;
+begin
+  for f := 0 to High(weaponSwitchKeyReleased) do weaponSwitchKeyReleased[f] := $03;
+end;
+
+procedure TPlayer.weaponSwitchKeysStateChange (index: Integer; pressed: Boolean);
+begin
+  Inc(index, 2); // -2: prev; -1: next
+  if (index < 0) or (index > High(weaponSwitchKeyReleased)) then exit;
+  weaponSwitchKeyReleased[index] := weaponSwitchKeyReleased[index] or $02;
+  if (pressed) then weaponSwitchKeyReleased[index] := weaponSwitchKeyReleased[index] xor $02;
+end;
+
+function TPlayer.isWeaponSwitchKeyReleased (index: Integer): Boolean;
+begin
+  Inc(index, 2); // -2: prev; -1: next
+  if (index < 0) or (index > High(weaponSwitchKeyReleased)) then
+  begin
+    result := true;
+  end
+  else
+  begin
+    result := (weaponSwitchKeyReleased[index] and $01) <> 0;
+  end;
+end;
+
+procedure TPlayer.weaponSwitchKeysShiftNewStates ();
+var
+  f: Integer;
+begin
+  // copy bit 1 to bit 0
+  for f := 0 to High(weaponSwitchKeyReleased) do
+  begin
+    weaponSwitchKeyReleased[f] :=
+      (weaponSwitchKeyReleased[f] and $02) or
+      ((weaponSwitchKeyReleased[f] shr 1) and $01);
+  end;
 end;
 
-procedure TPlayer.positionChanged ();
+
+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;
@@ -2108,7 +2268,7 @@ begin
     end;
 
   // Áóôåð óðîíà:
-    if FLive then
+    if FAlive then
       Inc(FDamageBuffer, value);
 
   // Âñïûøêà áîëè:
@@ -2129,7 +2289,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
@@ -2161,10 +2321,31 @@ begin
   FJetSoundOn.Free();
   FJetSoundOff.Free();
   FModel.Free();
+  if FPunchAnim <> nil then
+    FPunchAnim.Free();
 
   inherited;
 end;
 
+procedure TPlayer.DrawIndicator();
+var
+  indX, indY: Integer;
+  indW, indH: Word;
+  ID: DWORD;
+begin
+  if FAlive then
+    begin
+      indX := FObj.X+FObj.Rect.X;
+      indY := FObj.Y - 12;
+      if g_Texture_Get('TEXTURE_PLAYER_INDICATOR', ID) then
+        begin
+          e_GetTextureSize(ID, @indW, @indH);
+          e_Draw(ID, indX + indW div 2, indY, 0, True, False);
+        end;
+    end;
+  //e_TextureFontPrint(indX, indY, FName, gStdFont); // Shows player name overhead
+end;
+
 procedure TPlayer.DrawBubble();
 var
   bubX, bubY: Integer;
@@ -2173,7 +2354,7 @@ var
   Rw, Gw, Bw: SmallInt;
   Dot: Byte;
 begin
-  bubX := FObj.X+FObj.Rect.X + IfThen(FDirection = D_LEFT, -4, 18);
+  bubX := FObj.X+FObj.Rect.X + IfThen(FDirection = TDirection.D_LEFT, -4, 18);
   bubY := FObj.Y+FObj.Rect.Y - 18;
   Rb := 64;
   Gb := 64;
@@ -2217,10 +2398,10 @@ begin
     4: // custom textured bubble
     begin
       if g_Texture_Get('TEXTURE_PLAYER_TALKBUBBLE', ID) then
-        if FDirection = D_RIGHT then
+        if FDirection = TDirection.D_RIGHT then
           e_Draw(ID, bubX - 6, bubY - 7, 0, True, False)
         else
-          e_Draw(ID, bubX - 6, bubY - 7, 0, True, False, M_HORIZONTAL);
+          e_Draw(ID, bubX - 6, bubY - 7, 0, True, False, TMirrorType.Horizontal);
       Exit;
     end;
   end;
@@ -2232,12 +2413,12 @@ begin
   e_DrawFillQuad(bubX + 1, bubY + 1, bubX + 18, bubY + 12, Rw, Gw, Bw, 0);
 
   // Tail
-  Dot := IfThen(FDirection = D_LEFT, 14, 5);
+  Dot := IfThen(FDirection = TDirection.D_LEFT, 14, 5);
   e_DrawLine(1, bubX + Dot, bubY + 14, bubX + Dot, bubY + 16, Rb, Gb, Bb);
-  e_DrawLine(1, bubX + IfThen(FDirection = 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);
+  e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 15, Rw, Gw, Bw);
+  e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 2, Dot + 2), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 2, Dot + 2), bubY + 14, Rw, Gw, Bw);
+  e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 13, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 13, Rw, Gw, Bw);
+  e_DrawLine(1, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 3, Dot + 3), bubY + 14, bubX + IfThen(FDirection = TDirection.D_LEFT, Dot - 1, Dot + 1), bubY + 16, Rb, Gb, Bb);
 
   // Dots
   Dot := 6;
@@ -2251,19 +2432,36 @@ var
   ID: DWORD;
   w, h: Word;
   dr: Boolean;
+  Mirror: TMirrorType;
 begin
-  if FLive then
+  if FAlive then
   begin
+    if Direction = TDirection.D_RIGHT then
+      Mirror := TMirrorType.None
+    else
+      Mirror := TMirrorType.Horizontal;
+
+    if FPunchAnim <> nil then
+    begin
+      FPunchAnim.Draw(FObj.X+IfThen(Direction = TDirection.D_LEFT, 15-FObj.Rect.X, FObj.Rect.X-15),
+                      FObj.Y+FObj.Rect.Y-11, Mirror);
+      if FPunchAnim.played then
+      begin
+        FPunchAnim.Free;
+        FPunchAnim := nil;
+      end;
+    end;
+
     if (FMegaRulez[MR_INVUL] > gTime) and (gPlayerDrawn <> Self) then
       if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
       begin
         e_GetTextureSize(ID, @w, @h);
-        if FDirection = D_LEFT then
+        if FDirection = TDirection.D_LEFT then
           e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)+4,
-                     FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7, 0, True, False)
+                     FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+FObj.slopeUpLeft, 0, True, False)
         else
           e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)-2,
-                     FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7, 0, True, False);
+                     FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+FObj.slopeUpLeft, 0, True, False);
       end;
 
     if FMegaRulez[MR_INVIS] > gTime then
@@ -2276,15 +2474,15 @@ begin
         else
           dr := True;
         if dr then
-          FModel.Draw(FObj.X, FObj.Y, 200)
+          FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft, 200)
         else
-          FModel.Draw(FObj.X, FObj.Y);
+          FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft);
       end
       else
-        FModel.Draw(FObj.X, FObj.Y, 254);
+        FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft, 254);
     end
     else
-      FModel.Draw(FObj.X, FObj.Y);
+      FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft);
   end;
 
   if g_debug_Frames then
@@ -2299,18 +2497,42 @@ begin
   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
+  if gAimLine and alive and
   ((Self = gPlayer1) or (Self = gPlayer2)) then
     DrawAim();
 end;
 
+
 procedure TPlayer.DrawAim();
+  procedure drawCast (sz: Integer; ax0, ay0, ax1, ay1: Integer);
+  var
+    ex, ey: Integer;
+  begin
+
+{$IFDEF ENABLE_HOLMES}
+    if isValidViewPort and (self = gPlayer1) then
+    begin
+      g_Holmes_plrLaser(ax0, ay0, ax1, ay1);
+    end;
+{$ENDIF}
+
+    e_DrawLine(sz, ax0, ay0, ax1, ay1, 255, 0, 0, 96);
+    if (g_Map_traceToNearestWall(ax0, ay0, ax1, ay1, @ex, @ey) <> nil) then
+    begin
+      e_DrawLine(sz, ax0, ay0, ex, ey, 0, 255, 0, 96);
+    end
+    else
+    begin
+      e_DrawLine(sz, ax0, ay0, ex, ey, 0, 0, 255, 96);
+    end;
+  end;
+
 var
   wx, wy, xx, yy: Integer;
   angle: SmallInt;
   sz, len: Word;
 begin
-  wx := FObj.X + WEAPONPOINT[FDirection].X + IfThen(FDirection = D_LEFT, 7, -7);
+  wx := FObj.X + WEAPONPOINT[FDirection].X + IfThen(FDirection = TDirection.D_LEFT, 7, -7);
   wy := FObj.Y + WEAPONPOINT[FDirection].Y;
   angle := FAngle;
   len := 1024;
@@ -2391,7 +2613,11 @@ begin
   end;
   xx := Trunc(Cos(-DegToRad(angle)) * len) + wx;
   yy := Trunc(Sin(-DegToRad(angle)) * len) + wy;
+  {$IF DEFINED(D2F_DEBUG)}
+  drawCast(sz, wx, wy, xx, yy);
+  {$ELSE}
   e_DrawLine(sz, wx, wy, xx, yy, 255, 0, 0, 96);
+  {$ENDIF}
 end;
 
 procedure TPlayer.DrawGUI();
@@ -2601,7 +2827,7 @@ begin
 
     if dr then
       e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
-                     191, 191, 191, 0, B_INVERT);
+                     191, 191, 191, 0, TBlending.Invert);
   end;
 
   // Ïðè âçÿòèè çàùèòíîãî êîñòþìà ðèñóåòñÿ çåëåíîâàòûé ôîí
@@ -2614,14 +2840,14 @@ begin
 
     if dr then
       e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
-                     0, 96, 0, 200, B_NONE);
+                     0, 96, 0, 200, TBlending.None);
   end;
 
   // Ïðè âçÿòèè áåðñåðêà ðèñóåòñÿ êðàñíîâàòûé ôîí
   if (FBerserk >= 0) and (LongWord(FBerserk) >= gTime) and (gFlash = 2) then
   begin
     e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1,
-                     255, 0, 0, 200, B_NONE);
+                     255, 0, 0, 200, TBlending.None);
   end;
 end;
 
@@ -2663,6 +2889,27 @@ begin
   e_DrawFillQuad(0, 0, gPlayerScreenSize.X-1, gPlayerScreenSize.Y-1, 150, 200, 150, 255-h*50);
 end;
 
+procedure TPlayer.DoPunch();
+var
+  id: DWORD;
+  st: String;
+begin
+  if FPunchAnim <> nil then begin
+    FPunchAnim.reset();
+    FPunchAnim.Free;
+    FPunchAnim := nil;
+  end;
+  st := 'FRAMES_PUNCH';
+  if R_BERSERK in FRulez then
+    st := st + '_BERSERK';
+  if FKeys[KEY_UP].Pressed then
+    st := st + '_UP'
+  else if FKeys[KEY_DOWN].Pressed then
+    st := st + '_DN';
+  g_Frames_Get(id, st);
+  FPunchAnim := TAnimation.Create(id, False, 1);
+end;
+
 procedure TPlayer.Fire();
 var
   f, DidFire: Boolean;
@@ -2686,12 +2933,13 @@ begin
   f := False;
   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();
 
   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);
@@ -2711,10 +2959,12 @@ begin
         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);
+        if (gFlash = 1) and (FPain < 50) then FPain := min(FPain + 25, 50);
+      end
+      else
+      begin
+        g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 3, FUID);
+      end;
 
       DidFire := True;
       FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
@@ -3015,13 +3265,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;
 
@@ -3129,7 +3379,7 @@ begin
         if mon = nil then
           s := '?'
         else
-          s := g_Monsters_GetKilledBy(mon.MonsterType);
+          s := g_Mons_GetKilledByTypeId(mon.MonsterType);
 
         case KillType of
           K_HARDKILL:
@@ -3343,11 +3593,11 @@ begin
   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);
+              FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.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);
+              FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
 end;
 
 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
@@ -3355,7 +3605,7 @@ begin
   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);
+              FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
 end;
 
 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
@@ -3376,8 +3626,8 @@ begin
   result := false;
   case weapon of
     WEAPON_KASTET, WEAPON_SAW: result := true;
-    WEAPON_SHOTGUN1, WEAPON_SHOTGUN2: result := (FAmmo[A_SHELLS] > 0);
-    WEAPON_PISTOL, WEAPON_CHAINGUN, WEAPON_SUPERPULEMET: result := (FAmmo[A_BULLETS] > 0);
+    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);
@@ -3390,83 +3640,170 @@ function TPlayer.getNextWeaponIndex (): Byte;
 var
   i: Word;
   wantThisWeapon: array[0..64] of Boolean;
-  wwc: Integer = 0; //HACK!
-  dir, cwi: Integer;
+  weaponOrder: array[0..16] of Integer; // value: index in `FWeapon`
+  wwc: Integer;
+  f, dir, cwi, rwidx, curlidx: Integer;
+
+  function real2log (ridx: Integer): Integer;
+  var
+    f: Integer;
+  begin
+    if (ridx >= 0) then
+    begin
+      for f := 0 to High(weaponOrder) do if (weaponOrder[f] = ridx) then begin result := f; exit; end;
+    end;
+    result := -1;
+  end;
+
 begin
   result := 255; // default result: "no switch"
+
   // had weapon cycling on previous frame? remove that flag
   if (FNextWeap and $2000) <> 0 then
   begin
     FNextWeap := FNextWeap and $1FFF;
     FNextWeapDelay := 0;
   end;
+
+  for f := 0 to High(weaponOrder) do weaponOrder[f] := -1;
+
+  // build weapon order (k8: i know, i know, learn how to do constants and such... gtfo, please!)
+  // two knuckles are for "normal" and "berserk" (see `if` below -- it removes the one that is not needed)
+  // priorities:
+  //   bfg, launcher, plasma, flamethrower, ssg, minigun, sg, pistol, berserk, chainsaw, fist
+  weaponOrder[0] := WEAPON_SUPERPULEMET;
+  weaponOrder[1] := WEAPON_BFG;
+  weaponOrder[2] := WEAPON_ROCKETLAUNCHER;
+  weaponOrder[3] := WEAPON_PLASMA;
+  weaponOrder[4] := WEAPON_FLAMETHROWER;
+  weaponOrder[5] := WEAPON_SHOTGUN2;
+  weaponOrder[6] := WEAPON_CHAINGUN;
+  weaponOrder[7] := WEAPON_SHOTGUN1;
+  weaponOrder[8] := WEAPON_PISTOL;
+  weaponOrder[9] := WEAPON_KASTET+666; // berserk fist
+  weaponOrder[10] := WEAPON_SAW;
+  weaponOrder[11] := WEAPON_KASTET; // normal fist
+
+  for f := 0 to High(weaponOrder) do
+  begin
+    if (weaponOrder[f] = WEAPON_KASTET) then
+    begin
+      // normal fist: remove if we have a berserk pack
+      if (R_BERSERK in FRulez) then weaponOrder[f] := -1;
+    end
+    else
+    if (weaponOrder[f] = WEAPON_KASTET+666) then
+    begin
+      // berserk fist: remove if we don't have a berserk pack
+      if (R_BERSERK in FRulez) then weaponOrder[f] := WEAPON_KASTET else weaponOrder[f] := -1;
+    end;
+  end;
+
+  (*
+  WEAPON_KASTET         = 0;
+  WEAPON_SAW            = 1;
+  WEAPON_PISTOL         = 2;
+  WEAPON_SHOTGUN1       = 3;
+  WEAPON_SHOTGUN2       = 4;
+  WEAPON_CHAINGUN       = 5;
+  WEAPON_ROCKETLAUNCHER = 6;
+  WEAPON_PLASMA         = 7;
+  WEAPON_BFG            = 8;
+  WEAPON_SUPERPULEMET   = 9;
+  WEAPON_FLAMETHROWER   = 10;
+  *)
+
   // cycling has priority
   if (FNextWeap and $C000) <> 0 then
   begin
-    if (FNextWeap and $8000) <> 0 then
-      dir := 1
-    else
-      dir := -1;
+    if (FNextWeap and $8000) <> 0 then dir := 1 else dir := -1; // should be reversed if we want "priority-driven cycling"
     FNextWeap := FNextWeap or $2000; // we need this
-    if FNextWeapDelay > 0 then
-      exit; // cooldown time
+    if FNextWeapDelay > 0 then exit; // cooldown time
+    //cwi := real2log(FCurrWeap);
+    //if (cwi < 0) then cwi := 0;
     cwi := FCurrWeap;
     for i := 0 to High(FWeapon) do
     begin
       cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
-      if FWeapon[cwi] then
-      begin
-        //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING);
-        result := Byte(cwi);
-        FNextWeapDelay := 10;
+      //rwidx := weaponOrder[cwi];
+      rwidx := cwi; // sorry
+      if (rwidx < 0) then continue;
+      if FWeapon[rwidx] then
+      begin
+        //e_WriteLog(Format(' SWITCH: cur=%d; new=%d (dir=%d; log=%d)', [FCurrWeap, rwidx, dir, cwi]), TMsgType.Warning);
+        result := Byte(rwidx);
+        //FNextWeapDelay := 10; //k8: not needed anymore
         exit;
       end;
     end;
     resetWeaponQueue();
     exit;
   end;
+
   // no cycling
-  for i := 0 to High(wantThisWeapon) do
-    wantThisWeapon[i] := false;
+  for i := 0 to High(wantThisWeapon) do wantThisWeapon[i] := false;
+  wwc := 0;
+
+  curlidx := -1; // start from a weapon with a highest priority (-1, 'cause loop will immediately increment this index)
+
   for i := 0 to High(FWeapon) do
+  begin
     if (FNextWeap and (1 shl i)) <> 0 then
     begin
-      wantThisWeapon[i] := true;
-      Inc(wwc);
+      cwi := real2log(i);
+      if (cwi >= 0) then
+      begin
+        wantThisWeapon[cwi] := true;
+        Inc(wwc);
+        // if we hit currently selected weapon, start seachring from it, so we'll get "next weaker" weapon
+        if (i = FCurrWeap) then curlidx := cwi; // compare real, start from logical
+      end;
     end;
-  // exclude currently selected weapon from the set
-  wantThisWeapon[FCurrWeap] := false;
+  end;
+
   // slow down alterations a little
-  if wwc > 1 then
+  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
+    // more than one weapon requested, assume "alteration", and check alteration delay
     if FNextWeapDelay > 0 then
     begin
+      //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), TMsgType.Warning);
       FNextWeap := 0;
       exit;
     end; // yeah
   end;
+
   // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
   // but clear all counters if no weapon should be switched
-  if wwc < 1 then
+  if (wwc < 1) then
   begin
     resetWeaponQueue();
     exit;
   end;
-  //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
-  // try weapons in descending order
-  for i := High(FWeapon) downto 0 do
+
+  //e_WriteLog(Format('*** wwc=%d; currweap=%d; logweap=%d', [wwc, FCurrWeap, curlidx]), TMsgType.Warning);
+
+  // find next weapon to switch onto
+  cwi := curlidx;
+  for i := 0 to High(weaponOrder) do
   begin
-    if wantThisWeapon[i] and FWeapon[i] and ((wwc = 1) or hasAmmoForWeapon(i)) then
+    cwi := (cwi+length(weaponOrder)+1) mod length(weaponOrder);
+    if (cwi = curlidx) then continue; // skip current weapon
+    if not wantThisWeapon[cwi] then continue;
+    rwidx := weaponOrder[cwi];
+    if (rwidx < 0) then continue;
+    //e_WriteLog(Format('  trying logical %d (real %d); has=%d, hasammo=%d', [cwi, rwidx, Integer(FWeapon[rwidx]), Integer(hasAmmoForWeapon(rwidx))]), TMsgType.Warning);
+    if FWeapon[rwidx] and ((wwc = 1) or hasAmmoForWeapon(rwidx)) then
     begin
+      //e_WriteLog('    I FOUND HER!', TMsgType.Warning);
       // i found her!
-      result := Byte(i);
+      result := Byte(rwidx);
       resetWeaponQueue();
-      FNextWeapDelay := 10; // anyway, 'cause why not
+      //FNextWeapDelay := 10; // anyway, 'cause why not; k8: not needed anymore
       exit;
     end;
   end;
+
   // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
   resetWeaponQueue();
 end;
@@ -3492,12 +3829,14 @@ var
 begin
   //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
   //FNextWeap := FNextWeap and $1FFF;
-  if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
+  //HACK: alteration delay will be reset when player released any weapon switch key
+  FNextWeapDelay := 0; //k8: just in case
+  //if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
 
   if not switchAllowed then
   begin
     //HACK for weapon cycling
-    if (FNextWeap and $7000) <> 0 then FNextWeap := 0;
+    if (FNextWeap and $E000) <> 0 then FNextWeap := 0;
     exit;
   end;
 
@@ -3506,7 +3845,7 @@ begin
   if nw > High(FWeapon) then
   begin
     // don't forget to reset queue here!
-    //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
+    //e_WriteLog(Format(' RealizeCurrentWeapon: WUTAFUUUU?! (%d)', [nw]), TMsgType.Warning);
     resetWeaponQueue();
     exit;
   end;
@@ -3545,6 +3884,16 @@ begin
 end;
 
 function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean): Boolean;
+
+  function allowBerserkSwitching (): Boolean;
+  begin
+    if (FBFGFireCounter <> -1) then begin result := false; exit; end;
+    result := true;
+    if gBerserkAutoswitch then exit;
+    if not conIsCheatsEnabled then exit;
+    result := false;
+  end;
+
 var
   a: Boolean;
 begin
@@ -3887,15 +4236,17 @@ 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;
@@ -3961,7 +4312,7 @@ 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
@@ -3985,7 +4336,7 @@ end;
 procedure TPlayer.Reset(Force: Boolean);
 begin
   if Force then
-    FLive := False;
+    FAlive := False;
 
   FSpawned := False;
   FTime[T_RESPAWN] := 0;
@@ -4186,6 +4537,12 @@ var
   Anim: TAnimation;
   ID: DWORD;
 begin
+  FIncCam := 0;
+  FBFGFireCounter := -1;
+  FShellTimer := -1;
+  FPain := 0;
+  FLastHit := 0;
+
   if not g_Game_IsServer then
     Exit;
   if FDummy then
@@ -4195,7 +4552,7 @@ begin
   if Force then
   begin
     FTime[T_RESPAWN] := 0;
-    FLive := False;
+    FAlive := False;
   end;
   FNetTime := 0;
   // if server changes MaxLives we gotta be ready
@@ -4231,11 +4588,11 @@ begin
   SetFlag(FLAG_NONE);
 
 // Âîñêðåøåíèå áåç îðóæèÿ:
-  if not FLive then
+  if not FAlive then
   begin
     FHealth := PLAYER_HP_SOFT;
     FArmor := 0;
-    FLive := True;
+    FAlive := True;
     FAir := AIR_DEF;
     FJetFuel := 0;
 
@@ -4285,17 +4642,11 @@ begin
   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;
 
@@ -4328,6 +4679,11 @@ begin
   FSpectatePlayer := -1;
   FSpawned := True;
 
+  if (gPlayer1 = nil) and (gLMSPID1 = FUID) then
+    gPlayer1 := self;
+  if (gPlayer2 = nil) and (gLMSPID2 = FUID) then
+    gPlayer2 := self;
+
   if g_Game_IsNet then
   begin
     MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
@@ -4341,7 +4697,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
@@ -4351,7 +4707,7 @@ begin
   FXTo := GameX;
   FYTo := GameY;
 
-  FLive := False;
+  FAlive := False;
   FSpectator := True;
   FGhost := True;
   FPhysics := False;
@@ -4378,7 +4734,7 @@ end;
 
 procedure TPlayer.SwitchNoClip;
 begin
-  if not FLive then
+  if not FAlive then
     Exit;
   FGhost := not FGhost;
   FPhysics := not FGhost;
@@ -4401,7 +4757,7 @@ begin
     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);
@@ -4417,7 +4773,7 @@ begin
     if b > 1 then b := b * (Random(8 div b) + 1);
     for a := 0 to High(gGibs) do
     begin
-      if gGibs[a].Live and
+      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
@@ -4442,7 +4798,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;
@@ -4451,7 +4807,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;
@@ -4529,7 +4885,7 @@ begin
 
   FObj.X := X-PLAYER_RECT.X;
   FObj.Y := Y-PLAYER_RECT.Y;
-  if FLive and FGhost then
+  if FAlive and FGhost then
   begin
     FXTo := FObj.X;
     FYTo := FObj.Y;
@@ -4539,26 +4895,26 @@ 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
+          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;
@@ -4587,6 +4943,30 @@ begin
     Result := 1;
 end;
 
+function TPlayer.followCorpse(): Boolean;
+var
+  i: Integer;
+begin
+  Result := False;
+  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;
+        FObj.X := gCorpses[i].FObj.X;
+        FObj.Y := gCorpses[i].FObj.Y;
+        FObj.Vel.X := gCorpses[i].FObj.Vel.X;
+        FObj.Vel.Y := gCorpses[i].FObj.Vel.Y;
+        FObj.Accel.X := gCorpses[i].FObj.Accel.X;
+        FObj.Accel.Y := gCorpses[i].FObj.Accel.Y;
+        break;
+      end;
+end;
+
 procedure TPlayer.Update();
 var
   b: Byte;
@@ -4619,15 +4999,18 @@ begin
       FLoss := 0;
     end;
 
-  if FLive and (gFly or FJetpack) then
+  if FAlive and (FPunchAnim <> nil) then
+    FPunchAnim.Update();
+
+  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();
@@ -4645,22 +5028,23 @@ begin
   end;
 
   // no need to do that each second frame, weapon queue will take care of it
-  if FLive and FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
-  if FLive and FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
+  if FAlive and FKeys[KEY_NEXTWEAPON].Pressed and AnyServer then NextWeapon();
+  if FAlive and FKeys[KEY_PREVWEAPON].Pressed and AnyServer then PrevWeapon();
 
   if gTime mod (GAME_TICK*2) <> 0 then
   begin
-    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);
+      if not followCorpse() then
+        g_Obj_Move(@FObj, True, True, True);
       positionChanged(); // this updates spatial accelerators
     end;
 
@@ -4669,11 +5053,11 @@ begin
 
   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_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();
@@ -4708,7 +5092,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)
@@ -4734,7 +5118,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;
@@ -4786,7 +5170,8 @@ begin
 
   if FPhysics then
   begin
-    g_Obj_Move(@FObj, True, True, True);
+    if not followCorpse() then
+      g_Obj_Move(@FObj, True, True, True);
     positionChanged(); // this updates spatial accelerators
   end
   else
@@ -4796,7 +5181,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;
@@ -4809,7 +5194,7 @@ begin
   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);
 
@@ -4817,7 +5202,7 @@ 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;
@@ -4828,7 +5213,7 @@ begin
 
   i := 9;
 
-  if FLive then
+  if FAlive then
   begin
     if FCurrWeap = WEAPON_SAW then
       if not (FSawSound.IsPlaying() or FSawSoundHit.IsPlaying() or
@@ -4873,7 +5258,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);
@@ -4972,7 +5357,7 @@ begin
             else if FHealth > -50 then Kill(K_HARDKILL, FLastSpawnerUID, FLastHit)
               else Kill(K_EXTRAHARDKILL, FLastSpawnerUID, FLastHit);
 
-      if FLive then
+      if FAlive 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)
@@ -4984,7 +5369,7 @@ begin
     end;
 
     {CollideItem();}
-  end; // if FLive then ...
+  end; // if FAlive then ...
 
   if (FActionAnim = A_PAIN) and (FModel.Animation <> A_PAIN) then
   begin
@@ -5002,11 +5387,27 @@ begin
     if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time);
 end;
 
-function TPlayer.getMapAABB (): AABB2D; inline;
+
+procedure TPlayer.getMapBox (out x, y, w, h: Integer); inline;
 begin
-  result := AABB2D.CreateWH(FObj.X+PLAYER_RECT.X, FObj.Y+PLAYER_RECT.Y, PLAYER_RECT.Width, PLAYER_RECT.Height);
+  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,
@@ -5087,7 +5488,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
@@ -5114,6 +5515,7 @@ 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);
@@ -5505,280 +5907,214 @@ begin
     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);
-// Æåëàåìîå îðóæèå:
-  Mem.WriteWord(FNextWeap);
-// ...è ïàóçà
-  Mem.WriteByte(FNextWeapDelay);
-// Âðåìÿ çàðÿäêè BFG:
-  Mem.WriteSmallInt(FBFGFireCounter);
-// Áóôåð óðîíà:
-  Mem.WriteInt(FDamageBuffer);
-// Ïîñëåäíèé óäàðèâøèé:
-  Mem.WriteWord(FLastSpawnerUID);
-// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
-  Mem.WriteByte(FLastHit);
-// Îáúåêò èãðîêà:
-  Obj_SaveState(@FObj, Mem);
-// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.WriteWord(FAmmo[i]);
-// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.WriteWord(FMaxAmmo[i]);
-// Íàëè÷èå îðóæèÿ:
-  for i := WP_FIRST to WP_LAST do
-    Mem.WriteBoolean(FWeapon[i]);
-// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
-  for i := WP_FIRST to WP_LAST 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, 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.Name);
+  // Öâåò ìîäåëè
+  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);
-// Æåëàåìîå îðóæèå:
-  Mem.ReadWord(FNextWeap);
-// ...è ïàóçà
-  Mem.ReadByte(FNextWeapDelay);
-// Âðåìÿ çàðÿäêè BFG:
-  Mem.ReadSmallInt(FBFGFireCounter);
-// Áóôåð óðîíà:
-  Mem.ReadInt(FDamageBuffer);
-// Ïîñëåäíèé óäàðèâøèé:
-  Mem.ReadWord(FLastSpawnerUID);
-// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
-  Mem.ReadByte(FLastHit);
-// Îáúåêò èãðîêà:
-  Obj_LoadState(@FObj, Mem);
-// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.ReadWord(FAmmo[i]);
-// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.ReadWord(FMaxAmmo[i]);
-// Íàëè÷èå îðóæèÿ:
-  for i := WP_FIRST to WP_LAST do
-    Mem.ReadBoolean(FWeapon[i]);
-// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
-  for i := WP_FIRST to WP_LAST 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);
+  // Æèçíè
+  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]
@@ -5786,6 +6122,7 @@ begin
     FModel.Color := FColor;
 end;
 
+
 procedure TPlayer.AllRulez(Health: Boolean);
 var
   a: Integer;
@@ -6052,11 +6389,34 @@ begin
   inherited;
 end;
 
-procedure TCorpse.positionChanged (); begin end;
+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; vx, vy: Integer);
 var
   pm: TPlayerModel;
+  Blood: TModelBlood;
 begin
   if FState = CORPSE_STATE_REMOVEME then
     Exit;
@@ -6079,16 +6439,23 @@ begin
         pm := g_PlayerModel_Get(FModelName);
         pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
         pm.Free;
+
+        // Çëîâåùèé ñìåõ:
+        if (gBodyKillEvent <> -1)
+        and gDelayedEvents[gBodyKillEvent].Pending then
+          gDelayedEvents[gBodyKillEvent].Pending := False;
+        gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, 0);
       end;
     end
   else
     begin
+      Blood := g_PlayerModel_GetBlood(FModelName);
       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;
 
@@ -6098,12 +6465,12 @@ begin
     Exit;
 
   if FAnimation <> nil then
-    FAnimation.Draw(FObj.X, FObj.Y, M_NONE);
+    FAnimation.Draw(FObj.X, FObj.Y, TMirrorType.None);
 
   if FAnimationMask <> nil then
   begin
     e_Colors := FColor;
-    FAnimationMask.Draw(FObj.X, FObj.Y, M_NONE);
+    FAnimationMask.Draw(FObj.X, FObj.Y, TMirrorType.None);
     e_Colors.R := 255;
     e_Colors.G := 255;
     e_Colors.B := 255;
@@ -6142,80 +6509,75 @@ begin
     FAnimationMask.Update();
 end;
 
-procedure TCorpse.SaveState(var Mem: TBinMemoryWriter);
+
+procedure TCorpse.SaveState (st: TStream);
 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);
+  assert(st <> nil);
+
+  // Ñèãíàòóðà òðóïà
+  utils.writeSign(st, 'CORP');
+  utils.writeInt(st, Byte(0));
+  // Ñîñòîÿíèå
+  utils.writeInt(st, Byte(FState));
+  // Íàêîïëåííûé óðîí
+  utils.writeInt(st, Byte(FDamage));
+  // Öâåò
+  utils.writeInt(st, Byte(FColor.R));
+  utils.writeInt(st, Byte(FColor.G));
+  utils.writeInt(st, Byte(FColor.B));
+  // Îáúåêò òðóïà
+  Obj_SaveState(st, @FObj);
+  utils.writeInt(st, Word(FPlayerUID));
+  // Åñòü ëè àíèìàöèÿ
+  anim := (FAnimation <> nil);
+  utils.writeBool(st, anim);
+  // Åñëè åñòü - ñîõðàíÿåì
+  if anim then FAnimation.SaveState(st);
+  // Åñòü ëè ìàñêà àíèìàöèè
+  anim := (FAnimationMask <> nil);
+  utils.writeBool(st, anim);
+  // Åñëè åñòü - ñîõðàíÿåì
+  if anim then FAnimationMask.SaveState(st);
+end;
+
+
+procedure TCorpse.LoadState (st: TStream);
 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);
-// Åñëè åñòü - çàãðóæàåì:
+  assert(st <> nil);
+
+  // Ñèãíàòóðà òðóïà
+  if not utils.checkSign(st, 'CORP') then raise XStreamError.Create('invalid corpse signature');
+  if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid corpse version');
+  // Ñîñòîÿíèå
+  FState := utils.readByte(st);
+  // Íàêîïëåííûé óðîí
+  FDamage := utils.readByte(st);
+  // Öâåò
+  FColor.R := utils.readByte(st);
+  FColor.G := utils.readByte(st);
+  FColor.B := utils.readByte(st);
+  // Îáúåêò òðóïà
+  Obj_LoadState(@FObj, st);
+  FPlayerUID := utils.readWord(st);
+  // Åñòü ëè àíèìàöèÿ
+  anim := utils.readBool(st);
+  // Åñëè åñòü - çàãðóæàåì
   if anim then
   begin
     Assert(FAnimation <> nil, 'TCorpse.LoadState: no FAnimation');
-    FAnimation.LoadState(Mem);
+    FAnimation.LoadState(st);
   end;
-// Åñòü ëè ìàñêà àíèìàöèè:
-  Mem.ReadBoolean(anim);
-// Åñëè åñòü - çàãðóæàåì:
+  // Åñòü ëè ìàñêà àíèìàöèè
+  anim := utils.readBool(st);
+  // Åñëè åñòü - çàãðóæàåì
   if anim then
   begin
     Assert(FAnimationMask <> nil, 'TCorpse.LoadState: no FAnimationMask');
-    FAnimationMask.LoadState(Mem);
+    FAnimationMask.LoadState(st);
   end;
 end;
 
@@ -6316,7 +6678,7 @@ var
   function monsUpdate (mon: TMonster): Boolean;
   begin
     result := false; // don't stop
-    if mon.Live and (mon.MonsterType <> MONSTER_BARREL) then
+    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;
 
@@ -6439,7 +6801,7 @@ 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
@@ -6497,16 +6859,16 @@ begin
 
         if (Healthy() = 3) or ((Healthy() = 2)) 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
         else
           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;
 
@@ -6525,17 +6887,17 @@ begin
       begin // Öåëü ñáåæàëà ñ "ýêðàíà"
         if (Healthy() = 3) or ((Healthy() = 2)) 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
         else
           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
@@ -6547,15 +6909,15 @@ begin
       // Åñëè ðàçíèöà âûñîò íå âåëèêà, òî äîãîíÿåì:
         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;
@@ -6575,7 +6937,7 @@ begin
       end;
 
   // Âûáèðàåì óãîë âíèç:
-    if FDirection = D_LEFT then
+    if FDirection = TDirection.D_LEFT then
       angle := ANGLE_LEFTDOWN
     else
       angle := ANGLE_RIGHTDOWN;
@@ -6600,8 +6962,8 @@ begin
         (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
+        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', '');
@@ -6629,14 +6991,14 @@ begin
           if Target.IsPlayer then
             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; // òî çàáûòü öåëü
             end
           else
             begin // Öåëü - ìîíñòð
               mon := g_Monsters_ByUID(Target.UID);
-              if (mon = nil) or (not mon.Live) then
+              if (mon = nil) or (not mon.alive) then
                 Target.UID := 0; // òî çàáûòü öåëü
             end;
         end;
@@ -6653,7 +7015,7 @@ begin
 
         SetAIFlag('NEEDJUMP', '1');
 
-        if RunDirection() = D_RIGHT then
+        if RunDirection() = TDirection.D_RIGHT then
           begin // Èäåì íå â òó ñòîðîíó
             if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
               begin // Åñëè çäîðîâû, òî, âîçìîæíî, ñòðåëÿåì áåæèì âëåâî è ñòðåëÿåì
@@ -6676,7 +7038,7 @@ begin
 
           SetAIFlag('NEEDJUMP', '1');
 
-          if RunDirection() = D_LEFT then
+          if RunDirection() = TDirection.D_LEFT then
             begin // Èäåì íå â òó ñòîðîíó
               if (Healthy() > 1) and GetRnd(FDifficult.InvisFire) then
                 begin // Åñëè çäîðîâû, òî, âîçìîæíî, áåæèì âïðàâî è ñòðåëÿåì
@@ -6705,7 +7067,7 @@ 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;
@@ -6724,7 +7086,7 @@ begin
               end;
 
           // Èùåì öåëü ñíèçó è ñòðåëÿåì, åñëè åñòü:
-            if FDirection = D_LEFT then
+            if FDirection = TDirection.D_LEFT then
               angle := ANGLE_LEFTDOWN
             else
               angle := ANGLE_RIGHTDOWN;
@@ -6745,8 +7107,8 @@ begin
 
       // Åñëè öåëü "ïåðåä íîñîì", òî ñòðåëÿåì:
         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;
@@ -6778,7 +7140,7 @@ procedure TBot.Update();
 var
   EnableAI: Boolean;
 begin
-  if not FLive then
+  if not FAlive then
   begin // Respawn
     ReleaseKeys();
     PressKey(KEY_UP);
@@ -6897,7 +7259,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);
@@ -6905,7 +7267,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;
@@ -6915,7 +7277,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();
@@ -6936,7 +7298,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);
@@ -6949,7 +7311,7 @@ procedure TBot.UpdateMove;
     x, sx: Integer;
   begin
     { TODO 5 : Ëåñòíèöû }
-    sx := IfThen(RunDirection() = D_LEFT, -1, 1);
+    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
@@ -6967,7 +7329,7 @@ procedure TBot.UpdateMove;
     x, sx, xx: Integer;
   begin
     { TODO 5 : Ëåñòíèöû }
-    sx := IfThen(RunDirection() = D_LEFT, -1, 1);
+    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
@@ -6990,7 +7352,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
@@ -7036,18 +7398,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;
@@ -7059,7 +7421,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;
 
@@ -7225,7 +7587,7 @@ begin
 
 // Åñëè íà çåìëå è ìîæíî ïîäïðûãíóòü, òî, âîçìîæíî, ïðûãàåì:
   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();
 
@@ -7404,7 +7766,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;
@@ -7421,64 +7783,87 @@ 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);
 end.