DEADSOFTWARE

Game: Don't catch fire if protected
[d2df-sdl.git] / src / game / g_player.pas
index e6e0d360227097ccb7a83698395ab0cf4777ca3f..ddf4825b2ee994232eaf81bc22a58ff101935c65 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
@@ -21,7 +21,7 @@ interface
 
 uses
   SysUtils, Classes,
-  mempool,
+  {$IFDEF USE_MEMPOOL}mempool,{$ENDIF}
   e_graphics, g_playermodel, g_basic, g_textures,
   g_weapons, g_phys, g_sound, g_saveload, MAPDEF,
   g_panel;
@@ -95,12 +95,14 @@ const
   PLAYER_AP_SOFT  = 100;
   PLAYER_AP_LIMIT = 200;
   SUICIDE_DAMAGE  = 112;
+  WEAPON_DELAY    = 5;
 
   PLAYER1_DEF_COLOR: TRGB = (R:64; G:175; B:48);
   PLAYER2_DEF_COLOR: TRGB = (R:96; G:96; B:96);
 
 type
   TPlayerStat = record
+    Num: Integer;
     Ping: Word;
     Loss: Byte;
     Name: String;
@@ -135,7 +137,7 @@ type
     Time: Word;
   end;
 
-  TPlayer = class(TPoolObject)
+  TPlayer = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
   private
     FIamBot:    Boolean;
     FUID:       Word;
@@ -176,6 +178,7 @@ type
     FSavedState: TPlayerSavedState;
 
     FModel:     TPlayerModel;
+    FPunchAnim: TAnimation;
     FActionPrior:    Byte;
     FActionAnim:     Byte;
     FActionForce:    Boolean;
@@ -189,6 +192,9 @@ type
     FSawSoundIdle:   TPlayableSound;
     FSawSoundHit:    TPlayableSound;
     FSawSoundSelect: TPlayableSound;
+    FFlameSoundOn:   TPlayableSound;
+    FFlameSoundOff:  TPlayableSound;
+    FFlameSoundWork: TPlayableSound;
     FJetSoundOn:     TPlayableSound;
     FJetSoundOff:    TPlayableSound;
     FJetSoundFly:    TPlayableSound;
@@ -213,6 +219,7 @@ type
     procedure SetAction(Action: Byte; Force: Boolean = False);
     procedure OnDamage(Angle: SmallInt); virtual;
     function firediry(): Integer;
+    procedure DoPunch();
 
     procedure Run(Direction: TDirection);
     procedure NextWeapon();
@@ -229,6 +236,8 @@ type
 
     procedure doDamage (v: Integer);
 
+    function followCorpse(): Boolean;
+
   public
     FDamageBuffer:   Integer;
 
@@ -248,13 +257,16 @@ type
     FWantsInGame: Boolean;
     FGhost:     Boolean;
     FPhysics:   Boolean;
+    FFlaming:   Boolean;
     FJetpack:   Boolean;
     FActualModelName: string;
     FClientID:  SmallInt;
     FPing:      Word;
     FLoss:      Byte;
+    FReady:     Boolean;
     FDummy:     Boolean;
     FFireTime:  Integer;
+    FHandicap:  Integer;
 
     // debug: viewport offset
     viewPortX, viewPortY, viewPortW, viewPortH: Integer;
@@ -287,7 +299,7 @@ type
     procedure   BFGHit();
     function    GetFlag(Flag: Byte): Boolean;
     procedure   SetFlag(Flag: Byte);
-    function    DropFlag(): Boolean;
+    function    DropFlag(Silent: Boolean = True): Boolean;
     procedure   AllRulez(Health: Boolean);
     procedure   RestoreHealthArmor();
     procedure   FragCombo();
@@ -306,6 +318,7 @@ type
     procedure   DrawPickup();
     procedure   DrawRulez();
     procedure   DrawAim();
+    procedure   DrawIndicator();
     procedure   DrawBubble();
     procedure   DrawGUI();
     procedure   Update(); virtual;
@@ -319,6 +332,8 @@ type
     procedure   SetLerp(XTo, YTo: Integer);
     procedure   QueueWeaponSwitch(Weapon: Byte);
     procedure   RealizeCurrentWeapon();
+    procedure   FlamerOn;
+    procedure   FlamerOff;
     procedure   JetpackOn;
     procedure   JetpackOff;
     procedure   CatchFire(Attacker: Word);
@@ -461,7 +476,7 @@ type
 
   PGib = ^TGib;
   TGib = record
-    alive:     Boolean;
+    alive:    Boolean;
     ID:       DWORD;
     MaskID:   DWORD;
     RAngle:   Integer;
@@ -491,7 +506,7 @@ type
     procedure positionChanged ();  inline; //WARNING! call this after entity position was changed, or coldet will not work right!
   end;
 
-  TCorpse = class(TPoolObject)
+  TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
   private
     FModelName:     String;
     FMess:          Boolean;
@@ -499,6 +514,7 @@ type
     FDamage:        Byte;
     FColor:         TRGB;
     FObj:           TObj;
+    FPlayerUID:     Word;
     FAnimation:     TAnimation;
     FAnimationMask: TAnimation;
 
@@ -537,6 +553,7 @@ var
   gFly: Boolean = False;
   gAimLine: Boolean = False;
   gChatBubble: Byte = 0;
+  gPlayerIndicator: Boolean = True;
   gNumBots: Word = 0;
   gLMSPID1: Word = 0;
   gLMSPID2: Word = 0;
@@ -578,18 +595,23 @@ procedure g_Player_DrawShells();
 procedure g_Player_RemoveAllCorpses();
 procedure g_Player_Corpses_SaveState (st: TStream);
 procedure g_Player_Corpses_LoadState (st: TStream);
-procedure g_Bot_Add(Team, Difficult: Byte);
-procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1);
+procedure g_Player_ResetReady();
+procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
+procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1; Handicap: Integer = 100);
 procedure g_Bot_MixNames();
 procedure g_Bot_RemoveAll();
 
 implementation
 
 uses
+{$INCLUDE ../nogl/noGLuses.inc}
+{$IFDEF ENABLE_HOLMES}
+  g_holmes,
+{$ENDIF}
   e_log, g_map, g_items, g_console, g_gfx, Math,
   g_options, g_triggers, g_menu, g_game, g_grid,
   wadreader, g_main, g_monsters, CONFIG, g_language,
-  g_net, g_netmsg, g_window, GL, g_holmes,
+  g_net, g_netmsg, g_window,
   utils, xstreams;
 
 const PLR_SAVE_VERSION = 0;
@@ -861,9 +883,11 @@ begin
   gPlayers[a].FNoRespawn := utils.readBool(st);
   // Íàïðàâëåíèå
   b := utils.readByte(st);
-  if b = 1 then gPlayers[a].FDirection := D_LEFT else gPlayers[a].FDirection := D_RIGHT; // b = 2
+  if b = 1 then gPlayers[a].FDirection := TDirection.D_LEFT else gPlayers[a].FDirection := TDirection.D_RIGHT; // b = 2
   // Çäîðîâüå
   gPlayers[a].FHealth := utils.readLongInt(st);
+  // Ôîðà
+  gPlayers[a].FHandicap := utils.readLongInt(st);
   // Æèçíè
   gPlayers[a].FLives := utils.readByte(st);
   // Áðîíÿ
@@ -985,9 +1009,9 @@ begin
       end;
 end;
 
-procedure g_Bot_Add(Team, Difficult: Byte);
+procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
 var
-  m: SArray;
+  m: SSArray;
   _name, _model: String;
   a, tr, tb: Integer;
 begin
@@ -1074,6 +1098,8 @@ begin
       //FDifficult.SafeWeaponPrior[a] := WEAPON_PRIOR3[a];
     end;
 
+    FHandicap := Handicap;
+
     g_Console_Add(Format(_lc[I_PLAYER_JOIN], [Name]), True);
 
     if g_Game_IsNet then MH_SEND_PlayerCreate(UID);
@@ -1082,9 +1108,9 @@ begin
   end;
 end;
 
-procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1);
+procedure g_Bot_AddList(Team: Byte; lName: ShortString; num: Integer = -1; Handicap: Integer = 100);
 var
-  m: SArray;
+  m: SSArray;
   _name, _model: String;
   a: Integer;
 begin
@@ -1145,6 +1171,8 @@ begin
     FDifficult.Cover := BotList[num].cover;
     FDifficult.CloseJump := BotList[num].close_jump;
 
+    FHandicap := Handicap;
+
     for a := WP_FIRST to WP_LAST do
     begin
       FDifficult.WeaponPrior[a] := BotList[num].w_prior1[a];
@@ -1221,7 +1249,7 @@ var
   s: String;
   a, b: Integer;
   config: TConfig;
-  sa: SArray;
+  sa: SSArray;
 begin
   BotNames := nil;
 
@@ -1453,6 +1481,7 @@ begin
       SetLength(Result, Length(Result)+1);
       with Result[High(Result)] do
       begin
+        Num := a;
         Ping := gPlayers[a].FPing;
         Loss := gPlayers[a].FLoss;
         Name := gPlayers[a].FName;
@@ -1467,6 +1496,22 @@ begin
     end;
 end;
 
+procedure g_Player_ResetReady();
+var
+  a: Integer;
+begin
+  if not g_Game_IsServer then Exit;
+  if gPlayers = nil then Exit;
+
+  for a := 0 to High(gPlayers) do
+    if gPlayers[a] <> nil then
+    begin
+      gPlayers[a].FReady := False;
+      if g_Game_IsNet then
+        MH_SEND_GameEvent(NET_EV_INTER_READY, gPlayers[a].UID, 'N');
+    end;
+end;
+
 procedure g_Player_RememberAll;
 var
   i: Integer;
@@ -1503,11 +1548,20 @@ end;
 
 procedure g_Player_CreateCorpse(Player: TPlayer);
 var
+  i: Integer;
   find_id: DWORD;
   ok: Boolean;
 begin
   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;
 
@@ -1533,6 +1587,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,
@@ -1592,11 +1647,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
@@ -1615,7 +1672,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
@@ -1800,10 +1857,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;
@@ -1831,7 +1888,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;
 
@@ -2075,6 +2132,9 @@ begin
   FSawSoundIdle := TPlayableSound.Create();
   FSawSoundHit := TPlayableSound.Create();
   FSawSoundSelect := TPlayableSound.Create();
+  FFlameSoundOn := TPlayableSound.Create();
+  FFlameSoundOff := TPlayableSound.Create();
+  FFlameSoundWork := TPlayableSound.Create();
   FJetSoundFly := TPlayableSound.Create();
   FJetSoundOn := TPlayableSound.Create();
   FJetSoundOff := TPlayableSound.Create();
@@ -2083,6 +2143,9 @@ begin
   FSawSoundIdle.SetByName('SOUND_WEAPON_IDLESAW');
   FSawSoundHit.SetByName('SOUND_WEAPON_HITSAW');
   FSawSoundSelect.SetByName('SOUND_WEAPON_SELECTSAW');
+  FFlameSoundOn.SetByName('SOUND_WEAPON_FLAMEON');
+  FFlameSoundOff.SetByName('SOUND_WEAPON_FLAMEOFF');
+  FFlameSoundWork.SetByName('SOUND_WEAPON_FLAMEWORK');
   FJetSoundFly.SetByName('SOUND_PLAYER_JETFLY');
   FJetSoundOn.SetByName('SOUND_PLAYER_JETON');
   FJetSoundOff.SetByName('SOUND_PLAYER_JETOFF');
@@ -2096,6 +2159,7 @@ begin
   FFireTime := 0;
   FFirePainTime := 0;
   FFireAttacker := 0;
+  FHandicap := 100;
 
   FActualModelName := 'doomer';
 
@@ -2242,14 +2306,39 @@ begin
   FSawSound.Free();
   FSawSoundIdle.Free();
   FSawSoundHit.Free();
+  FSawSoundSelect.Free();
+  FFlameSoundOn.Free();
+  FFlameSoundOff.Free();
+  FFlameSoundWork.Free();
   FJetSoundFly.Free();
   FJetSoundOn.Free();
   FJetSoundOff.Free();
   FModel.Free();
+  if FPunchAnim <> nil then
+    FPunchAnim.Free();
 
   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;
+      if g_Texture_Get('TEXTURE_PLAYER_INDICATOR', ID) then
+        begin
+          e_GetTextureSize(ID, @indW, @indH);
+          e_Draw(ID, indX + indW div 2, indY - indH, 0, True, False);
+        end;
+    end;
+  //e_TextureFontPrint(indX, indY, FName, gStdFont); // Shows player name overhead
+end;
+
 procedure TPlayer.DrawBubble();
 var
   bubX, bubY: Integer;
@@ -2258,7 +2347,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;
@@ -2302,10 +2391,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;
@@ -2317,12 +2406,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;
@@ -2336,14 +2425,31 @@ var
   ID: DWORD;
   w, h: Word;
   dr: Boolean;
+  Mirror: TMirrorType;
 begin
   if FAlive then
   begin
+    if Direction = TDirection.D_RIGHT then
+      Mirror := TMirrorType.None
+    else
+      Mirror := TMirrorType.Horizontal;
+
+    if FPunchAnim <> nil then
+    begin
+      FPunchAnim.Draw(FObj.X+IfThen(Direction = TDirection.D_LEFT, 15-FObj.Rect.X, FObj.Rect.X-15),
+                      FObj.Y+FObj.Rect.Y-11, Mirror);
+      if FPunchAnim.played then
+      begin
+        FPunchAnim.Free;
+        FPunchAnim := nil;
+      end;
+    end;
+
     if (FMegaRulez[MR_INVUL] > gTime) and (gPlayerDrawn <> Self) then
       if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
       begin
         e_GetTextureSize(ID, @w, @h);
-        if FDirection = 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+FObj.slopeUpLeft, 0, True, False)
         else
@@ -2382,7 +2488,9 @@ begin
   end;
 
   if (gChatBubble > 0) and (FKeys[KEY_CHAT].Pressed) and not FGhost then
-    DrawBubble();
+    if (FMegaRulez[MR_INVIS] <= gTime) or ((gPlayerDrawn <> nil) and ((Self = gPlayerDrawn) or
+       ((FTeam = gPlayerDrawn.Team) and (gGameSettings.GameMode <> GM_DM)))) then
+      DrawBubble();
  // e_DrawPoint(5, 335, 288, 255, 0, 0); // DL, UR, DL, UR
   if gAimLine and alive and
   ((Self = gPlayer1) or (Self = gPlayer2)) then
@@ -2395,10 +2503,13 @@ procedure TPlayer.DrawAim();
   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
@@ -2416,7 +2527,7 @@ var
   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;
@@ -2711,7 +2822,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;
 
   // Ïðè âçÿòèè çàùèòíîãî êîñòþìà ðèñóåòñÿ çåëåíîâàòûé ôîí
@@ -2724,14 +2835,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;
 
@@ -2773,6 +2884,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;
@@ -2796,12 +2928,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);
@@ -2821,10 +2954,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];
@@ -2954,11 +3089,17 @@ begin
       if FAmmo[A_FUEL] > 0 then
       begin
         g_Weapon_flame(wx, wy, xd, yd, FUID);
+        FlamerOn;
         FReloading[FCurrWeap] := WEAPON_RELOAD[FCurrWeap];
         Dec(FAmmo[A_FUEL]);
         FFireAngle := FAngle;
         f := True;
         DidFire := True;
+      end
+      else
+      begin
+        FlamerOff;
+        if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID);
       end;
   end;
 
@@ -3002,6 +3143,35 @@ begin
                                PANEL_WATER or PANEL_ACID1 or PANEL_ACID2, True);
 end;
 
+procedure TPlayer.FlamerOn;
+begin
+  FFlameSoundOff.Stop();
+  FFlameSoundOff.SetPosition(0);
+  if FFlaming then
+  begin
+    if (not FFlameSoundOn.IsPlaying()) and (not FFlameSoundWork.IsPlaying()) then
+      FFlameSoundWork.PlayAt(FObj.X, FObj.Y);
+  end
+  else
+  begin
+    FFlameSoundOn.PlayAt(FObj.X, FObj.Y);
+    FFlaming := True;
+  end;
+end;
+
+procedure TPlayer.FlamerOff;
+begin
+  if FFlaming then
+  begin
+    FFlameSoundOn.Stop();
+    FFlameSoundOn.SetPosition(0);
+    FFlameSoundWork.Stop();
+    FFlameSoundWork.SetPosition(0);
+    FFlameSoundOff.PlayAt(FObj.X, FObj.Y);
+    FFlaming := False;
+  end;
+end;
+
 procedure TPlayer.JetpackOn;
 begin
   FJetSoundFly.Stop;
@@ -3021,6 +3191,10 @@ end;
 
 procedure TPlayer.CatchFire(Attacker: Word);
 begin
+  if (FMegaRulez[MR_SUIT] > gTime) or (FMegaRulez[MR_INVUL] > gTime) then
+    exit; // Íå çàãîðàåìñÿ êîãäà åñòü çàùèòà
+  if FFireTime <= 0 then
+    g_Sound_PlayExAt('SOUND_IGNITE', FObj.X, FObj.Y);
   FFireTime := 100;
   FFireAttacker := Attacker;
   if g_Game_IsNet and g_Game_IsServer then
@@ -3312,7 +3486,7 @@ begin
     end;
 
 // Âûáðîñ ôëàãà:
-    DropFlag();
+    DropFlag(KillType = K_FALLKILL);
   end;
 
   g_Player_CreateCorpse(Self);
@@ -3453,11 +3627,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);
@@ -3465,7 +3639,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);
@@ -3528,7 +3702,7 @@ begin
       begin
         //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING);
         result := Byte(cwi);
-        FNextWeapDelay := 10;
+        FNextWeapDelay := WEAPON_DELAY;
         exit;
       end;
     end;
@@ -3573,7 +3747,7 @@ begin
       // i found her!
       result := Byte(i);
       resetWeaponQueue();
-      FNextWeapDelay := 10; // anyway, 'cause why not
+      FNextWeapDelay := WEAPON_DELAY * 2; // anyway, 'cause why not
       exit;
     end;
   end;
@@ -3607,7 +3781,7 @@ begin
   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;
 
@@ -3661,7 +3835,7 @@ function TPlayer.PickItem(ItemType: Byte; arespawn: Boolean; var remove: Boolean
     if (FBFGFireCounter <> -1) then begin result := false; exit; end;
     result := true;
     if gBerserkAutoswitch then exit;
-    if not (gDebugMode or gCheats) then exit;
+    if not conIsCheatsEnabled then exit;
     result := false;
   end;
 
@@ -4122,6 +4296,7 @@ begin
   FMonsterKills := 0;
   FDeath := 0;
   FSecrets := 0;
+  FReady := False;
   if FNoRespawn then
   begin
     FSpectator := False;
@@ -4308,6 +4483,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
@@ -4355,7 +4536,7 @@ begin
 // Âîñêðåøåíèå áåç îðóæèÿ:
   if not FAlive then
   begin
-    FHealth := PLAYER_HP_SOFT;
+    FHealth := Round(PLAYER_HP_SOFT * (FHandicap / 100));
     FArmor := 0;
     FAlive := True;
     FAir := AIR_DEF;
@@ -4407,17 +4588,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;
 
@@ -4430,6 +4605,7 @@ begin
   FDamageBuffer := 0;
   FJetpack := False;
   FCanJetpack := False;
+  FFlaming := False;
   FFireTime := 0;
   FFirePainTime := 0;
   FFireAttacker := 0;
@@ -4450,6 +4626,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);
@@ -4523,7 +4704,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);
@@ -4564,7 +4745,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;
@@ -4573,7 +4754,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;
@@ -4661,26 +4842,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;
@@ -4709,6 +4890,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;
@@ -4741,10 +4946,13 @@ begin
       FLoss := 0;
     end;
 
+  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;
@@ -4775,14 +4983,15 @@ begin
     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;
 
@@ -4794,11 +5003,19 @@ begin
   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();
+    if FKeys[KEY_FIRE].Pressed and AnyServer then Fire()
+    else
+    begin
+      if AnyServer then
+      begin
+        FlamerOff;
+        if NetServer then MH_SEND_PlayerStats(FUID);
+      end;
+    end;
     if FKeys[KEY_OPEN].Pressed and AnyServer then Use();
     if FKeys[KEY_JUMP].Pressed then Jump()
     else
@@ -4908,7 +5125,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
@@ -4995,7 +5213,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);
@@ -5252,6 +5470,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);
@@ -5364,6 +5583,7 @@ begin
     WEAPON_FLAMETHROWER:
     begin
       g_Weapon_flame(wx, wy, xd, yd, FUID, WID);
+      FlamerOn;
       FFireAngle := FAngle;
       f := True;
     end;
@@ -5420,7 +5640,7 @@ end;
 function TPlayer.GetFlag(Flag: Byte): Boolean;
 var
   s, ts: String;
-  evtype: Byte;
+  evtype, a: Byte;
 begin
   Result := False;
 
@@ -5448,6 +5668,16 @@ begin
     g_Map_ResetFlag(FFlag);
     g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
 
+    if ((Self = gPlayer1) or (Self = gPlayer2)
+    or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
+    or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
+      a := 0
+    else
+      a := 1;
+
+    if not sound_cap_flag[a].IsPlaying() then
+      sound_cap_flag[a].Play();
+
     gTeamStat[FTeam].Goals := gTeamStat[FTeam].Goals + 1;
 
     Result := True;
@@ -5479,6 +5709,16 @@ begin
     g_Map_ResetFlag(Flag);
     g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
 
+    if ((Self = gPlayer1) or (Self = gPlayer2)
+    or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
+    or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
+      a := 0
+    else
+      a := 1;
+
+    if not sound_ret_flag[a].IsPlaying() then
+      sound_ret_flag[a].Play();
+
     Result := True;
     if g_Game_IsNet then
     begin
@@ -5506,6 +5746,16 @@ begin
 
     gFlags[Flag].State := FLAG_STATE_CAPTURED;
 
+    if ((Self = gPlayer1) or (Self = gPlayer2)
+    or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
+    or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
+      a := 0
+    else
+      a := 1;
+
+    if not sound_get_flag[a].IsPlaying() then
+      sound_get_flag[a].Play();
+
     Result := True;
     if g_Game_IsNet then
     begin
@@ -5522,9 +5772,10 @@ begin
     FModel.SetFlag(FFlag);
 end;
 
-function TPlayer.DropFlag(): Boolean;
+function TPlayer.DropFlag(Silent: Boolean = True): Boolean;
 var
   s: String;
+  a: Byte;
 begin
   Result := False;
   if (not g_Game_IsServer) or (FFlag = FLAG_NONE) then
@@ -5549,6 +5800,16 @@ begin
     g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [FName, s]), True);
     g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
 
+    if ((Self = gPlayer1) or (Self = gPlayer2)
+    or ((gPlayer1 <> nil) and (gPlayer1.Team = FTeam))
+    or ((gPlayer2 <> nil) and (gPlayer2.Team = FTeam))) then
+      a := 0
+    else
+      a := 1;
+
+    if (not Silent) and (not sound_lost_flag[a].IsPlaying()) then
+      sound_lost_flag[a].Play();
+
     if g_Game_IsNet then
       MH_SEND_FlagEvent(FLAG_STATE_DROPPED, Flag, FUID, False);
   end;
@@ -5558,6 +5819,11 @@ end;
 
 procedure TPlayer.GetSecret();
 begin
+  if (self = gPlayer1) or (self = gPlayer2) then
+  begin
+    g_Console_Add(Format(_lc[I_PLAYER_SECRET], [FName]), True);
+    g_Sound_PlayEx('SOUND_GAME_SECRET');
+  end;
   Inc(FSecrets);
 end;
 
@@ -5664,10 +5930,12 @@ begin
   // Èçðàñõîäîâàë ëè âñå æèçíè
   utils.writeBool(st, FNoRespawn);
   // Íàïðàâëåíèå
-  if FDirection = D_LEFT then b := 1 else b := 2; // D_RIGHT
+  if FDirection = TDirection.D_LEFT then b := 1 else b := 2; // D_RIGHT
   utils.writeInt(st, Byte(b));
   // Çäîðîâüå
   utils.writeInt(st, LongInt(FHealth));
+  // Êîýôôèöèåíò èíâàëèäíîñòè
+  utils.writeInt(st, LongInt(FHandicap));
   // Æèçíè
   utils.writeInt(st, Byte(FLives));
   // Áðîíÿ
@@ -5767,9 +6035,11 @@ begin
   FNoRespawn := utils.readBool(st);
   // Íàïðàâëåíèå
   b := utils.readByte(st);
-  if b = 1 then FDirection := D_LEFT else FDirection := D_RIGHT; // b = 2
+  if b = 1 then FDirection := TDirection.D_LEFT else FDirection := TDirection.D_RIGHT; // b = 2
   // Çäîðîâüå
   FHealth := utils.readLongInt(st);
+  // Êîýôôèöèåíò èíâàëèäíîñòè
+  FHandicap := utils.readLongInt(st);
   // Æèçíè
   FLives := utils.readByte(st);
   // Áðîíÿ
@@ -6093,6 +6363,12 @@ begin
   FSawSoundIdle.Pause(Enable);
   FSawSoundHit.Pause(Enable);
   FSawSoundSelect.Pause(Enable);
+  FFlameSoundOn.Pause(Enable);
+  FFlameSoundOff.Pause(Enable);
+  FFlameSoundWork.Pause(Enable);
+  FJetSoundFly.Pause(Enable);
+  FJetSoundOn.Pause(Enable);
+  FJetSoundOff.Pause(Enable);
 end;
 
 { T C o r p s e : }
@@ -6152,6 +6428,7 @@ end;
 procedure TCorpse.Damage(Value: Word; vx, vy: Integer);
 var
   pm: TPlayerModel;
+  Blood: TModelBlood;
 begin
   if FState = CORPSE_STATE_REMOVEME then
     Exit;
@@ -6174,16 +6451,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;
 
@@ -6193,12 +6477,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;
@@ -6257,6 +6541,7 @@ begin
   utils.writeInt(st, Byte(FColor.B));
   // Îáúåêò òðóïà
   Obj_SaveState(st, @FObj);
+  utils.writeInt(st, Word(FPlayerUID));
   // Åñòü ëè àíèìàöèÿ
   anim := (FAnimation <> nil);
   utils.writeBool(st, anim);
@@ -6289,6 +6574,7 @@ begin
   FColor.B := utils.readByte(st);
   // Îáúåêò òðóïà
   Obj_LoadState(@FObj, st);
+  FPlayerUID := utils.readWord(st);
   // Åñòü ëè àíèìàöèÿ
   anim := utils.readBool(st);
   // Åñëè åñòü - çàãðóæàåì
@@ -6585,16 +6871,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;
 
@@ -6613,17 +6899,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
@@ -6635,15 +6921,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;
@@ -6663,7 +6949,7 @@ begin
       end;
 
   // Âûáèðàåì óãîë âíèç:
-    if FDirection = D_LEFT then
+    if FDirection = TDirection.D_LEFT then
       angle := ANGLE_LEFTDOWN
     else
       angle := ANGLE_RIGHTDOWN;
@@ -6688,8 +6974,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', '');
@@ -6741,7 +7027,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 // Åñëè çäîðîâû, òî, âîçìîæíî, ñòðåëÿåì áåæèì âëåâî è ñòðåëÿåì
@@ -6764,7 +7050,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 // Åñëè çäîðîâû, òî, âîçìîæíî, áåæèì âïðàâî è ñòðåëÿåì
@@ -6793,7 +7079,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;
@@ -6812,7 +7098,7 @@ begin
               end;
 
           // Èùåì öåëü ñíèçó è ñòðåëÿåì, åñëè åñòü:
-            if FDirection = D_LEFT then
+            if FDirection = TDirection.D_LEFT then
               angle := ANGLE_LEFTDOWN
             else
               angle := ANGLE_RIGHTDOWN;
@@ -6833,8 +7119,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;
@@ -6985,7 +7271,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);
@@ -6993,7 +7279,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;
@@ -7003,7 +7289,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();
@@ -7024,7 +7310,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);
@@ -7037,7 +7323,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
@@ -7055,7 +7341,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
@@ -7078,7 +7364,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
@@ -7124,18 +7410,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;
@@ -7147,7 +7433,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;
 
@@ -7313,7 +7599,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();
 
@@ -7492,7 +7778,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;