DEADSOFTWARE

render: add option -dDISABLE_RENDER
[d2df-sdl.git] / src / game / g_player.pas
index 72766a7600dfb533dcd8ba085812a85f0c2d3d3a..4967d75cc5642ca8a583f5890b552dfb81f986ee 100644 (file)
@@ -74,20 +74,11 @@ const
   TEAM_BLUE         = 2;
   TEAM_COOP         = 3;
 
-  SHELL_BULLET      = 0;
-  SHELL_SHELL       = 1;
-  SHELL_DBLSHELL    = 2;
-
   ANGLE_NONE        = Low(SmallInt);
 
-  CORPSE_STATE_REMOVEME = 0;
-  CORPSE_STATE_NORMAL   = 1;
-  CORPSE_STATE_MESS     = 2;
-
   PLAYER_RECT: TRectWH = (X:15; Y:12; Width:34; Height:52);
   PLAYER_RECT_CX       = 15+(34 div 2);
   PLAYER_RECT_CY       = 12+(52 div 2);
-  PLAYER_CORPSERECT: TRectWH = (X:15; Y:48; Width:34; Height:16);
 
   PLAYER_HP_SOFT  = 100;
   PLAYER_HP_LIMIT = 200;
@@ -191,7 +182,7 @@ type
     FSavedStateNum:   Integer;
 
     FModel:     TPlayerModel;
-    FPunchAnim: TAnimation;
+    FPunchAnim: TAnimationState;
     FActionPrior:    Byte;
     FActionAnim:     Byte;
     FActionForce:    Boolean;
@@ -201,8 +192,10 @@ type
     FIncCamOld:      Integer;
     FIncCam:         Integer;
     FSlopeOld:       Integer;
-    FShellTimer:     Integer;
-    FShellType:      Byte;
+    {$IFDEF ENABLE_SHELLS}
+      FShellTimer:     Integer;
+      FShellType:      Byte;
+    {$ENDIF}
     FSawSound:       TPlayableSound;
     FSawSoundIdle:   TPlayableSound;
     FSawSoundHit:    TPlayableSound;
@@ -250,8 +243,6 @@ type
 
     procedure doDamage (v: Integer);
 
-    function refreshCorpse(): Boolean;
-
   public
     FDamageBuffer:   Integer;
 
@@ -283,7 +274,10 @@ type
     FSpawnInvul: Integer;
     FHandicap:  Integer;
     FWaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
-    FCorpse:    Integer;
+
+    {$IFDEF ENABLE_CORPSES}
+      FCorpse:    Integer;
+    {$ENDIF}
 
     // debug: viewport offset
     viewPortX, viewPortY, viewPortW, viewPortH: Integer;
@@ -355,8 +349,6 @@ type
     procedure getMapBox (out x, y, w, h: Integer); inline;
     procedure moveBy (dx, dy: Integer); inline;
 
-    function getCameraObj(): TObj;
-
     function GetAmmoByWeapon(Weapon: Byte): Word; // private state
 
   public
@@ -403,7 +395,7 @@ type
     property    Berserk: Integer read FBerserk;
     property    Pain: Integer read FPain;
     property    Pickup: Integer read FPickup;
-    property    PunchAnim: TAnimation read FPunchAnim write FPunchAnim;
+    property    PunchAnim: TAnimationState read FPunchAnim write FPunchAnim;
     property    SpawnInvul: Integer read FSpawnInvul;
     property    Ghost: Boolean read FGhost;
 
@@ -441,6 +433,10 @@ type
     // set this before assigning something to `eDamage`
     property eDamageType: Integer read mEDamageType write mEDamageType;
     property eDamage: Integer write doDamage;
+
+    {$IFDEF ENABLE_CORPSES}
+      property Corpse: Integer read FCorpse;
+    {$ENDIF}
   end;
 
   TDifficult = record
@@ -501,75 +497,6 @@ type
     procedure   LoadState (st: TStream); override;
   end;
 
-  PGib = ^TGib;
-  TGib = record
-    alive:    Boolean;
-    ID:       DWORD;
-    MaskID:   DWORD;
-    RAngle:   Integer;
-    Color:    TRGB;
-    Obj:      TObj;
-
-    procedure getMapBox (out x, y, w, h: Integer); inline;
-    procedure moveBy (dx, dy: Integer); inline;
-
-    procedure positionChanged (); inline; //WARNING! call this after entity position was changed, or coldet will not work right!
-  end;
-
-
-  PShell = ^TShell;
-  TShell = record
-    SpriteID: DWORD;
-    alive:     Boolean;
-    SType:    Byte;
-    RAngle:   Integer;
-    Timeout:  Cardinal;
-    CX, CY:   Integer;
-    Obj:      TObj;
-
-    procedure getMapBox (out x, y, w, h: Integer); inline;
-    procedure moveBy (dx, dy: Integer); inline;
-
-    procedure positionChanged ();  inline; //WARNING! call this after entity position was changed, or coldet will not work right!
-  end;
-
-  TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
-  private
-    FModelName:     String;
-    FMess:          Boolean;
-    FState:         Byte;
-    FDamage:        Byte;
-    FColor:         TRGB;
-    FObj:           TObj;
-    FPlayerUID:     Word;
-    FAnimation:     TAnimation;
-    FAnimationMask: TAnimation;
-
-  public
-    constructor Create(X, Y: Integer; ModelName: String; aMess: Boolean);
-    destructor  Destroy(); override;
-    procedure   Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer);
-    procedure   Update();
-    procedure   SaveState (st: TStream);
-    procedure   LoadState (st: TStream);
-
-    procedure getMapBox (out x, y, w, h: Integer); inline;
-    procedure moveBy (dx, dy: Integer); inline;
-
-    procedure positionChanged ();  inline; //WARNING! call this after entity position was changed, or coldet will not work right!
-
-    function ObjPtr (): PObj; inline;
-
-    property    Obj: TObj read FObj; // copies object
-    property    State: Byte read FState;
-    property    Mess: Boolean read FMess;
-
-    (* internal state *)
-    property    Color: TRGB read FColor;
-    property    Animation: TAnimation read FAnimation;
-    property    AnimationMask: TAnimation read FAnimationMask;
-  end;
-
   TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
     record
       Goals: SmallInt;
@@ -577,9 +504,6 @@ type
 
 var
   gPlayers: Array of TPlayer;
-  gCorpses: Array of TCorpse;
-  gGibs: Array of TGib;
-  gShells: Array of TShell;
   gTeamStat: TTeamStat;
   gFly: Boolean = False;
   gAimLine: Boolean = False;
@@ -591,17 +515,9 @@ var
   gSpectLatchPID2: Word = 0;
   MAX_RUNVEL: Integer = 8;
   VEL_JUMP: Integer = 10;
-  SHELL_TIMEOUT: Cardinal = 60000;
 
 function  Lerp(X, Y, Factor: Integer): Integer;
 
-procedure g_Gibs_SetMax(Count: Word);
-function  g_Gibs_GetMax(): Word;
-procedure g_Corpses_SetMax(Count: Word);
-function  g_Corpses_GetMax(): Word;
-procedure g_Shells_SetMax(Count: Word);
-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;
@@ -616,13 +532,6 @@ function  g_Player_Get(UID: Word): TPlayer;
 function  g_Player_GetCount(): Byte;
 function  g_Player_GetStats(): TPlayerStatArray;
 function  g_Player_ValidName(Name: String): Boolean;
-function  g_Player_CreateCorpse(Player: TPlayer): Integer;
-procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: String; fColor: TRGB);
-procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
-procedure g_Player_UpdatePhysicalObjects();
-procedure g_Player_RemoveAllCorpses();
-procedure g_Player_Corpses_SaveState (st: TStream);
-procedure g_Player_Corpses_LoadState (st: TStream);
 procedure g_Player_ResetReady();
 procedure g_Bot_Add(Team, Difficult: Byte; Handicap: Integer = 100);
 procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1; Handicap: Integer = 100);
@@ -632,11 +541,29 @@ procedure g_Bot_RemoveAll();
 implementation
 
 uses
-{$IFDEF ENABLE_HOLMES}
-  g_holmes,
-{$ENDIF}
-  e_log, g_map, g_items, g_console, g_gfx, Math, r_textures, r_animations, r_gfx,
-  g_options, g_triggers, g_menu, g_game, g_grid, e_res,
+  {$IFDEF ENABLE_HOLMES}
+    g_holmes,
+  {$ENDIF}
+  {$IFDEF ENABLE_MENU}
+    g_menu,
+  {$ENDIF}
+  {$IFDEF ENABLE_RENDER}
+    r_render,
+  {$ENDIF}
+  {$IFDEF ENABLE_GFX}
+    g_gfx,
+  {$ENDIF}
+  {$IFDEF ENABLE_GIBS}
+    g_gibs,
+  {$ENDIF}
+  {$IFDEF ENABLE_SHELLS}
+    g_shells,
+  {$ENDIF}
+  {$IFDEF ENABLE_CORPSES}
+    g_corpses,
+  {$ENDIF}
+  e_log, g_map, g_items, g_console, Math,
+  g_options, g_triggers, g_game, g_grid, e_res,
   wadreader, g_monsters, CONFIG, g_language,
   g_net, g_netmsg,
   utils, xstreams;
@@ -708,11 +635,6 @@ const
   BOTLIST_FILENAME = 'botlist.txt';
 
 var
-  MaxGibs: Word = 150;
-  MaxCorpses: Word = 20;
-  MaxShells: Word = 300;
-  CurrentGib: Integer = 0;
-  CurrentShell: Integer = 0;
   BotNames: Array of String;
   BotList: Array of TBotProfile;
   SavedStates: Array of TPlayerSavedState;
@@ -738,46 +660,6 @@ begin
   Result := g_Player_Get(UID1).FTeam = g_Player_Get(UID2).FTeam;
 end;
 
-procedure g_Gibs_SetMax(Count: Word);
-begin
-  MaxGibs := Count;
-  SetLength(gGibs, Count);
-
-  if CurrentGib >= Count then
-    CurrentGib := 0;
-end;
-
-function g_Gibs_GetMax(): Word;
-begin
-  Result := MaxGibs;
-end;
-
-procedure g_Shells_SetMax(Count: Word);
-begin
-  MaxShells := Count;
-  SetLength(gShells, Count);
-
-  if CurrentShell >= Count then
-    CurrentShell := 0;
-end;
-
-function g_Shells_GetMax(): Word;
-begin
-  Result := MaxShells;
-end;
-
-
-procedure g_Corpses_SetMax(Count: Word);
-begin
-  MaxCorpses := Count;
-  SetLength(gCorpses, Count);
-end;
-
-function g_Corpses_GetMax(): Word;
-begin
-  Result := MaxCorpses;
-end;
-
 function g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
 var
   a: Integer;
@@ -1528,391 +1410,6 @@ begin
       end;
 end;
 
-function  g_Player_CreateCorpse(Player: TPlayer): Integer;
-var
-  i: Integer;
-  find_id: DWORD;
-  ok: Boolean;
-begin
-  Result := -1;
-
-  if Player.alive then
-    Exit;
-
-// Разрываем связь с прежним трупом:
-  i := Player.FCorpse;
-  if (i >= 0) and (i < Length(gCorpses)) then
-  begin
-    if (gCorpses[i] <> nil) and (gCorpses[i].FPlayerUID = Player.FUID) then
-      gCorpses[i].FPlayerUID := 0;
-  end;
-
-  if Player.FObj.Y >= gMapInfo.Height+128 then
-    Exit;
-
-  with Player do
-  begin
-    if (FHealth >= -50) or (gGibsCount = 0) then
-      begin
-        if (gCorpses = nil) or (Length(gCorpses) = 0) then
-          Exit;
-
-        ok := False;
-        for find_id := 0 to High(gCorpses) do
-          if gCorpses[find_id] = nil then
-          begin
-            ok := True;
-            Break;
-          end;
-
-        if not ok then
-          find_id := Random(Length(gCorpses));
-
-        gCorpses[find_id] := TCorpse.Create(FObj.X, FObj.Y, FModel.GetName(), FHealth < -20);
-        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;
-
-        Result := find_id;
-      end
-    else
-      g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX,
-                          FObj.Y + PLAYER_RECT_CY,
-                          FModel.GetName(), FModel.Color);
-  end;
-end;
-
-procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
-var
-  SID: DWORD;
-begin
-  if (gShells = nil) or (Length(gShells) = 0) then
-    Exit;
-
-  with gShells[CurrentShell] do
-  begin
-    SpriteID := 0;
-    g_Obj_Init(@Obj);
-    Obj.Rect.X := 0;
-    Obj.Rect.Y := 0;
-    if T = SHELL_BULLET then
-    begin
-      if g_Texture_Get('TEXTURE_SHELL_BULLET', SID) then
-        SpriteID := SID;
-      CX := 2;
-      CY := 1;
-      Obj.Rect.Width := 4;
-      Obj.Rect.Height := 2;
-    end
-    else
-    begin
-      if g_Texture_Get('TEXTURE_SHELL_SHELL', SID) then
-        SpriteID := SID;
-      CX := 4;
-      CY := 2;
-      Obj.Rect.Width := 7;
-      Obj.Rect.Height := 3;
-    end;
-    SType := T;
-    alive := True;
-    Obj.X := fX;
-    Obj.Y := fY;
-    g_Obj_Push(@Obj, dX + Random(4)-Random(4), dY-Random(4));
-    positionChanged(); // this updates spatial accelerators
-    RAngle := Random(360);
-    Timeout := gTime + SHELL_TIMEOUT;
-
-    if CurrentShell >= High(gShells) then
-      CurrentShell := 0
-    else
-      Inc(CurrentShell);
-  end;
-end;
-
-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
-    begin
-      Color := fColor;
-      ID := GibsArray[a].ID;
-      MaskID := GibsArray[a].MaskID;
-      alive := True;
-      g_Obj_Init(@Obj);
-      Obj.Rect := GibsArray[a].Rect;
-      Obj.X := fX-GibsArray[a].Rect.X-(GibsArray[a].Rect.Width div 2);
-      Obj.Y := fY-GibsArray[a].Rect.Y-(GibsArray[a].Rect.Height div 2);
-      g_Obj_PushA(@Obj, 25 + Random(10), Random(361));
-      positionChanged(); // this updates spatial accelerators
-      RAngle := Random(360);
-
-      if gBloodCount > 0 then
-        g_GFX_Blood(fX, fY, 16*gBloodCount+Random(5*gBloodCount), -16+Random(33), -16+Random(33),
-                    Random(48), Random(48), Blood.R, Blood.G, Blood.B, Blood.Kind);
-
-      if CurrentGib >= High(gGibs) then
-        CurrentGib := 0
-      else
-        Inc(CurrentGib);
-    end;
-end;
-
-procedure g_Player_UpdatePhysicalObjects();
-var
-  i: Integer;
-  vel: TPoint2i;
-  mr: Word;
-
-  procedure ShellSound_Bounce(X, Y: Integer; T: Byte);
-  var
-    k: Integer;
-  begin
-    k := 1 + Random(2);
-    if T = SHELL_BULLET then
-      g_Sound_PlayExAt('SOUND_PLAYER_CASING' + IntToStr(k), X, Y)
-    else
-      g_Sound_PlayExAt('SOUND_PLAYER_SHELL' + IntToStr(k), X, Y);
-  end;
-
-begin
-// Куски мяса:
-  if gGibs <> nil then
-    for i := 0 to High(gGibs) do
-      if gGibs[i].alive then
-        with gGibs[i] do
-        begin
-          Obj.oldX := Obj.X;
-          Obj.oldY := Obj.Y;
-
-          vel := Obj.Vel;
-          mr := g_Obj_Move(@Obj, True, False, True);
-          positionChanged(); // this updates spatial accelerators
-
-          if WordBool(mr and MOVE_FALLOUT) then
-          begin
-            alive := False;
-            Continue;
-          end;
-
-        // Отлетает от удара о стену/потолок/пол:
-          if WordBool(mr and MOVE_HITWALL) then
-            Obj.Vel.X := -(vel.X div 2);
-          if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
-            Obj.Vel.Y := -(vel.Y div 2);
-
-          if (Obj.Vel.X >= 0) then
-          begin // Clockwise
-            RAngle := RAngle + Abs(Obj.Vel.X)*6 + Abs(Obj.Vel.Y);
-            if RAngle >= 360 then
-              RAngle := RAngle mod 360;
-          end else begin // Counter-clockwise
-            RAngle := RAngle - Abs(Obj.Vel.X)*6 - Abs(Obj.Vel.Y);
-            if RAngle < 0 then
-              RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
-          end;
-
-        // Сопротивление воздуха для куска трупа:
-          if gTime mod (GAME_TICK*3) = 0 then
-            Obj.Vel.X := z_dec(Obj.Vel.X, 1);
-        end;
-
-// Трупы:
-  if gCorpses <> nil then
-    for i := 0 to High(gCorpses) do
-      if gCorpses[i] <> nil then
-        if gCorpses[i].State = CORPSE_STATE_REMOVEME then
-          begin
-            gCorpses[i].Free();
-            gCorpses[i] := nil;
-          end
-        else
-          gCorpses[i].Update();
-
-// Гильзы:
-  if gShells <> nil then
-    for i := 0 to High(gShells) do
-      if gShells[i].alive then
-        with gShells[i] do
-        begin
-          Obj.oldX := Obj.X;
-          Obj.oldY := Obj.Y;
-
-          vel := Obj.Vel;
-          mr := g_Obj_Move(@Obj, True, False, True);
-          positionChanged(); // this updates spatial accelerators
-
-          if WordBool(mr and MOVE_FALLOUT) or (gShells[i].Timeout < gTime) then
-          begin
-            alive := False;
-            Continue;
-          end;
-
-        // Отлетает от удара о стену/потолок/пол:
-          if WordBool(mr and MOVE_HITWALL) then
-          begin
-            Obj.Vel.X := -(vel.X div 2);
-            if not WordBool(mr and MOVE_INWATER) then
-              ShellSound_Bounce(Obj.X, Obj.Y, SType);
-          end;
-          if WordBool(mr and (MOVE_HITCEIL or MOVE_HITLAND)) then
-          begin
-            Obj.Vel.Y := -(vel.Y div 2);
-            if Obj.Vel.X <> 0 then Obj.Vel.X := Obj.Vel.X div 2;
-            if (Obj.Vel.X = 0) and (Obj.Vel.Y = 0) then
-            begin
-              if RAngle mod 90 <> 0 then
-                RAngle := (RAngle div 90) * 90;
-            end
-            else if not WordBool(mr and MOVE_INWATER) then
-              ShellSound_Bounce(Obj.X, Obj.Y, SType);
-          end;
-
-          if (Obj.Vel.X >= 0) then
-          begin // Clockwise
-            RAngle := RAngle + Abs(Obj.Vel.X)*8 + Abs(Obj.Vel.Y);
-            if RAngle >= 360 then
-              RAngle := RAngle mod 360;
-          end else begin // Counter-clockwise
-            RAngle := RAngle - Abs(Obj.Vel.X)*8 - Abs(Obj.Vel.Y);
-            if RAngle < 0 then
-              RAngle := (360 - (Abs(RAngle) mod 360)) mod 360;
-          end;
-        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_RemoveAllCorpses();
-var
-  i: Integer;
-begin
-  gGibs := nil;
-  gShells := nil;
-  SetLength(gGibs, MaxGibs);
-  SetLength(gShells, MaxGibs);
-  CurrentGib := 0;
-  CurrentShell := 0;
-
-  if gCorpses <> nil then
-    for i := 0 to High(gCorpses) do
-      gCorpses[i].Free();
-
-  gCorpses := nil;
-  SetLength(gCorpses, MaxCorpses);
-end;
-
-procedure g_Player_Corpses_SaveState (st: TStream);
-var
-  count, i: Integer;
-begin
-  // Считаем количество существующих трупов
-  count := 0;
-  for i := 0 to High(gCorpses) do if (gCorpses[i] <> nil) then Inc(count);
-
-  // Количество трупов
-  utils.writeInt(st, LongInt(count));
-
-  if (count = 0) then exit;
-
-  // Сохраняем трупы
-  for i := 0 to High(gCorpses) do
-  begin
-    if gCorpses[i] <> nil then
-    begin
-      // Название модели
-      utils.writeStr(st, gCorpses[i].FModelName);
-      // Тип смерти
-      utils.writeBool(st, gCorpses[i].Mess);
-      // Сохраняем данные трупа:
-      gCorpses[i].SaveState(st);
-    end;
-  end;
-end;
-
-
-procedure g_Player_Corpses_LoadState (st: TStream);
-var
-  count, i: Integer;
-  str: String;
-  b: Boolean;
-begin
-  assert(st <> nil);
-
-  g_Player_RemoveAllCorpses();
-
-  // Количество трупов:
-  count := utils.readLongInt(st);
-  if (count < 0) or (count > Length(gCorpses)) then raise XStreamError.Create('invalid number of corpses');
-
-  if (count = 0) then exit;
-
-  // Загружаем трупы
-  for i := 0 to count-1 do
-  begin
-    // Название модели:
-    str := utils.readStr(st);
-    // Тип смерти
-    b := utils.readBool(st);
-    // Создаем труп
-    gCorpses[i] := TCorpse.Create(0, 0, str, b);
-    // Загружаем данные трупа
-    gCorpses[i].LoadState(st);
-  end;
-end;
-
-
 { T P l a y e r : }
 
 function TPlayer.isValidViewPort (): Boolean; inline; begin result := (viewPortW > 0) and (viewPortH > 0); end;
@@ -2102,12 +1599,17 @@ begin
   FPing := 0;
   FLoss := 0;
   FSavedStateNum := -1;
-  FShellTimer := -1;
+  {$IFDEF ENABLE_SHELLS}
+    FShellTimer := -1;
+  {$ENDIF}
   FFireTime := 0;
   FFirePainTime := 0;
   FFireAttacker := 0;
   FHandicap := 100;
-  FCorpse := -1;
+
+  {$IFDEF ENABLE_CORPSES}
+    FCorpse := -1;
+  {$ENDIF}
 
   FActualModelName := 'doomer';
 
@@ -2119,6 +1621,8 @@ begin
   FNetTime := 0;
 
   FWaitForFirstSpawn := false;
+  FPunchAnim := TAnimationState.Create(False, 1, 4);
+  FPunchAnim.Disable;
 
   resetWeaponQueue();
 end;
@@ -2202,9 +1706,13 @@ begin
           HIT_BFG, HIT_ROCKET, HIT_SOME: MakeBloodVector(c, vx, vy);
         end;
 
-      if t = HIT_WATER then
-        g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
-                      FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
+      {$IFDEF ENABLE_GFX}
+        if t = HIT_WATER then
+        begin
+          g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
+                        FObj.Y+PLAYER_RECT.Y-4, value div 2, 8, 4);
+        end;
+      {$ENDIF}
     end;
 
   // Буфер урона:
@@ -2265,31 +1773,15 @@ begin
   FJetSoundOn.Free();
   FJetSoundOff.Free();
   FModel.Free();
-  if FPunchAnim <> nil then
-    FPunchAnim.Free();
+  FPunchAnim.Free();
 
   inherited;
 end;
 
 procedure TPlayer.DoPunch();
-var
-  id: DWORD;
-  st: String;
 begin
-  if FPunchAnim <> nil then begin
-    FPunchAnim.reset();
-    FPunchAnim.Free;
-    FPunchAnim := nil;
-  end;
-  st := 'FRAMES_PUNCH';
-  if R_BERSERK in FRulez then
-    st := st + '_BERSERK';
-  if FKeys[KEY_UP].Pressed then
-    st := st + '_UP'
-  else if FKeys[KEY_DOWN].Pressed then
-    st := st + '_DN';
-  g_Frames_Get(id, st);
-  FPunchAnim := TAnimation.Create(id, False, 1);
+  FPunchAnim.Reset;
+  FPunchAnim.Enable;
 end;
 
 procedure TPlayer.Fire();
@@ -2381,8 +1873,9 @@ begin
         FFireAngle := FAngle;
         f := True;
         DidFire := True;
-        g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
-                             GameVelX, GameVelY-2, SHELL_BULLET);
+        {$IFDEF ENABLE_SHELLS}
+          g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
+        {$ENDIF}
       end;
 
     WEAPON_SHOTGUN1:
@@ -2395,8 +1888,10 @@ begin
         FFireAngle := FAngle;
         f := True;
         DidFire := True;
-        FShellTimer := 10;
-        FShellType := SHELL_SHELL;
+        {$IFDEF ENABLE_SHELLS}
+          FShellTimer := 10;
+          FShellType := SHELL_SHELL;
+        {$ENDIF}
       end;
 
     WEAPON_SHOTGUN2:
@@ -2408,8 +1903,10 @@ begin
         FFireAngle := FAngle;
         f := True;
         DidFire := True;
-        FShellTimer := 13;
-        FShellType := SHELL_DBLSHELL;
+        {$IFDEF ENABLE_SHELLS}
+          FShellTimer := 13;
+          FShellType := SHELL_DBLSHELL;
+        {$ENDIF}
       end;
 
     WEAPON_CHAINGUN:
@@ -2422,8 +1919,9 @@ begin
         FFireAngle := FAngle;
         f := True;
         DidFire := True;
-        g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
-                             GameVelX, GameVelY-2, SHELL_BULLET);
+        {$IFDEF ENABLE_SHELLS}
+          g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
+        {$ENDIF}
       end;
 
     WEAPON_ROCKETLAUNCHER:
@@ -2468,8 +1966,9 @@ begin
         FFireAngle := FAngle;
         f := True;
         DidFire := True;
-        g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
-                             GameVelX, GameVelY-2, SHELL_SHELL);
+        {$IFDEF ENABLE_SHELLS}
+          g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_SHELL);
+        {$ENDIF}
       end;
 
     WEAPON_FLAMETHROWER:
@@ -2698,7 +2197,10 @@ begin
       FPhysics := True;
     FAlive := False;
   end;
-  FShellTimer := -1;
+
+  {$IFDEF ENABLE_SHELLS}
+    FShellTimer := -1;
+  {$ENDIF}
 
   if (gGameSettings.MaxLives > 0) and Srv and (gLMSRespawn = LMS_RESPAWN_NONE) then
   begin
@@ -2881,7 +2383,9 @@ begin
     DropFlag(KillType = K_FALLKILL);
   end;
 
-  FCorpse := g_Player_CreateCorpse(Self);
+  {$IFDEF ENABLE_CORPSES}
+    FCorpse := g_Corpses_Create(Self);
+  {$ENDIF}
 
   if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and
      (gLMSRespawn = LMS_RESPAWN_NONE) then
@@ -3015,27 +2519,35 @@ begin
 end;
 
 procedure TPlayer.MakeBloodSimple(Count: Word);
-  var Blood: TModelBlood;
-begin
-  Blood := SELF.FModel.GetBlood();
-  g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
-              FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
-              Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
-              Blood.R, Blood.G, Blood.B, Blood.Kind);
-  g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
-              FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
-              Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
-              Blood.R, Blood.G, Blood.B, Blood.Kind);
+  {$IFDEF ENABLE_GFX}
+    var Blood: TModelBlood;
+  {$ENDIF}
+begin
+  {$IFDEF ENABLE_GFX}
+    Blood := SELF.FModel.GetBlood();
+    g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
+                FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
+                Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
+                Blood.R, Blood.G, Blood.B, Blood.Kind);
+    g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
+                FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
+                Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
+                Blood.R, Blood.G, Blood.B, Blood.Kind);
+  {$ENDIF}
 end;
 
 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
-  var Blood: TModelBlood;
+  {$IFDEF ENABLE_GFX}
+    var Blood: TModelBlood;
+  {$ENDIF}
 begin
-  Blood := SELF.FModel.GetBlood();
-  g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
-              FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
-              Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
-              Blood.R, Blood.G, Blood.B, Blood.Kind);
+  {$IFDEF ENABLE_GFX}
+    Blood := SELF.FModel.GetBlood();
+    g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
+                FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
+                Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
+                Blood.R, Blood.G, Blood.B, Blood.Kind);
+  {$ENDIF}
 end;
 
 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
@@ -3696,7 +3208,9 @@ begin
   FDeath := 0;
   FSecrets := 0;
   FSpawnInvul := 0;
-  FCorpse := -1;
+  {$IFDEF ENABLE_CORPSES}
+    FCorpse := -1;
+  {$ENDIF}
   FReady := False;
   if FNoRespawn then
   begin
@@ -3720,7 +3234,9 @@ begin
   FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
-  FShellTimer := -1;
+  {$IFDEF ENABLE_SHELLS}
+    FShellTimer := -1;
+  {$ENDIF}
   FPain := 0;
   FLastHit := 0;
   FLastFrag := 0;
@@ -3803,11 +3319,16 @@ begin
   FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
-  FShellTimer := -1;
+  {$IFDEF ENABLE_SHELLS}
+    FShellTimer := -1;
+  {$ENDIF}
   FPain := 0;
   FLastHit := 0;
   FSpawnInvul := 0;
-  FCorpse := -1;
+
+  {$IFDEF ENABLE_CORPSES}
+    FCorpse := -1;
+  {$ENDIF}
 
   if not g_Game_IsServer then
     Exit;
@@ -3940,13 +3461,17 @@ begin
   FFirePainTime := 0;
   FFireAttacker := 0;
 
-// Анимация возрождения:
-  if (not gLoadGameMode) and (not Silent) then
-    r_GFX_OnceAnim(
-      R_GFX_TELEPORT_FAST,
-      FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
-      FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32
-    );
+  {$IFDEF ENABLE_GFX}
+    // Анимация возрождения:
+    if (not gLoadGameMode) and (not Silent) then
+    begin
+      g_GFX_QueueEffect(
+        R_GFX_TELEPORT_FAST,
+        FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
+        FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
+      );
+    end;
+  {$ENDIF}
 
   FSpectator := False;
   FGhost := False;
@@ -3988,7 +3513,10 @@ begin
   FPhysics := False;
   FWantsInGame := False;
   FSpawned := False;
-  FCorpse := -1;
+
+  {$IFDEF ENABLE_CORPSES}
+    FCorpse := -1;
+  {$ENDIF}
 
   if FNoRespawn then
   begin
@@ -4026,8 +3554,9 @@ begin
 end;
 
 procedure TPlayer.Run(Direction: TDirection);
-var
-  a, b: Integer;
+  {$IFDEF ENABLE_GIBS}
+    var a, b: Integer;
+  {$ENDIF}
 begin
   if MAX_RUNVEL > 8 then
     FlySmoke();
@@ -4042,30 +3571,32 @@ begin
     if FObj.Vel.X < MAX_RUNVEL then
       FObj.Vel.X := FObj.Vel.X + (MAX_RUNVEL shr 3);
 
-// Возможно, пинаем куски:
-  if (FObj.Vel.X <> 0) and (gGibs <> nil) then
-  begin
-    b := Abs(FObj.Vel.X);
-    if b > 1 then b := b * (Random(8 div b) + 1);
-    for a := 0 to High(gGibs) do
+  {$IFDEF ENABLE_GIBS}
+    // Возможно, пинаем куски:
+    if (FObj.Vel.X <> 0) and (gGibs <> nil) then
     begin
-      if gGibs[a].alive and
-         g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
-                       FObj.Rect.Width, 8, @gGibs[a].Obj) and (Random(3) = 0) then
+      b := Abs(FObj.Vel.X);
+      if b > 1 then b := b * (Random(8 div b) + 1);
+      for a := 0 to High(gGibs) do
       begin
-        // Пинаем куски
-        if FObj.Vel.X < 0 then
+        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
-          g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // налево
-        end
-        else
-        begin
-          g_Obj_PushA(@gGibs[a].Obj, b, Random(61));    // направо
+          // Пинаем куски
+          if FObj.Vel.X < 0 then
+          begin
+            g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120) // налево
+          end
+          else
+          begin
+            g_Obj_PushA(@gGibs[a].Obj, b, Random(61));    // направо
+          end;
+          gGibs[a].positionChanged(); // this updates spatial accelerators
         end;
-        gGibs[a].positionChanged(); // this updates spatial accelerators
       end;
     end;
-  end;
+  {$ENDIF}
 
   SetAction(A_WALK);
 end;
@@ -4142,11 +3673,13 @@ begin
   if not silent then
   begin
     g_Sound_PlayExAt('SOUND_GAME_TELEPORT', FObj.X, FObj.Y);
-    r_GFX_OnceAnim(
-      R_GFX_TELEPORT_FAST,
-      FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
-      FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32
-    );
+    {$IFDEF ENABLE_GFX}
+      g_GFX_QueueEffect(
+        R_GFX_TELEPORT_FAST,
+        FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
+        FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
+      );
+    {$ENDIF}
     if g_Game_IsServer and g_Game_IsNet then
       MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
                      FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 1,
@@ -4194,11 +3727,13 @@ begin
 
   if not silent then
   begin
-    r_GFX_OnceAnim(
-      R_GFX_TELEPORT_FAST,
-      FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
-      FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32
-    );
+    {$IFDEF ENABLE_GFX}
+      g_GFX_QueueEffect(
+        R_GFX_TELEPORT_FAST,
+        FObj.X + PLAYER_RECT.X + (PLAYER_RECT.Width div 2) - 32,
+        FObj.Y + PLAYER_RECT.Y + (PLAYER_RECT.Height div 2) - 32
+      );
+    {$ENDIF}
     if g_Game_IsServer and g_Game_IsNet then
       MH_SEND_Effect(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-32,
                      FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2)-32, 0,
@@ -4216,41 +3751,6 @@ begin
     Result := 1;
 end;
 
-function TPlayer.refreshCorpse(): Boolean;
-var
-  i: Integer;
-begin
-  Result := False;
-  FCorpse := -1;
-  if FAlive or FSpectator then
-    Exit;
-  if (gCorpses = nil) or (Length(gCorpses) = 0) then
-    Exit;
-  for i := 0 to High(gCorpses) do
-    if gCorpses[i] <> nil then
-      if gCorpses[i].FPlayerUID = FUID then
-      begin
-        Result := True;
-        FCorpse := i;
-        break;
-      end;
-end;
-
-function TPlayer.getCameraObj(): TObj;
-begin
-  if (not FAlive) and (not FSpectator) and
-     (FCorpse >= 0) and (FCorpse < Length(gCorpses)) and
-     (gCorpses[FCorpse] <> nil) and (gCorpses[FCorpse].FPlayerUID = FUID) then
-  begin
-    gCorpses[FCorpse].FObj.slopeUpLeft := FObj.slopeUpLeft;
-    Result := gCorpses[FCorpse].FObj;
-  end
-  else
-  begin
-    Result := FObj;
-  end;
-end;
-
 procedure TPlayer.PreUpdate();
 begin
   FSlopeOld := FObj.slopeUpLeft;
@@ -4291,8 +3791,10 @@ begin
       FLoss := 0;
     end;
 
-  if FAlive and (FPunchAnim <> nil) then
-    FPunchAnim.Update();
+  if FAlive then
+    FPunchAnim.Update;
+  if FPunchAnim.played then
+    FPunchAnim.Disable;
 
   if FAlive and (gFly or FJetpack) then
     FlySmoke();
@@ -4533,21 +4035,23 @@ begin
         else
           Dec(FReloading[b]);
 
+{$IFDEF ENABLE_SHELLS}
     if FShellTimer > -1 then
       if FShellTimer = 0 then
       begin
         if FShellType = SHELL_SHELL then
-          g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
+          g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
                                GameVelX, GameVelY-2, SHELL_SHELL)
         else if FShellType = SHELL_DBLSHELL then
         begin
-          g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
+          g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
                                GameVelX+1, GameVelY-2, SHELL_SHELL);
-          g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
+          g_Shells_Create(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
                                GameVelX-1, GameVelY-2, SHELL_SHELL);
         end;
         FShellTimer := -1;
       end else Dec(FShellTimer);
+{$ENDIF}
 
     if (FBFGFireCounter > -1) then
       if FBFGFireCounter = 0 then
@@ -4591,7 +4095,9 @@ begin
       end
       else if (FAir mod 31 = 0) and not blockmon then
       begin
-        g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2), FObj.Y+PLAYER_RECT.Y-4, 5+Random(6), 8, 4);
+        {$IFDEF ENABLE_GFX}
+          g_GFX_Bubbles(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2), FObj.Y+PLAYER_RECT.Y-4, 5+Random(6), 8, 4);
+        {$ENDIF}
         if Random(2) = 0 then
           g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
         else
@@ -4863,8 +4369,9 @@ begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', GameX, Gamey);
       FFireAngle := FAngle;
       f := True;
-      g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
-                             GameVelX, GameVelY-2, SHELL_BULLET);
+      {$IFDEF ENABLE_SHELLS}
+        g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
+      {$ENDIF}
     end;
 
     WEAPON_SHOTGUN1:
@@ -4872,8 +4379,10 @@ begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
       FFireAngle := FAngle;
       f := True;
-      FShellTimer := 10;
-      FShellType := SHELL_SHELL;
+      {$IFDEF ENABLE_SHELLS}
+        FShellTimer := 10;
+        FShellType := SHELL_SHELL;
+      {$ENDIF}
     end;
 
     WEAPON_SHOTGUN2:
@@ -4881,8 +4390,10 @@ begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', Gamex, Gamey);
       FFireAngle := FAngle;
       f := True;
-      FShellTimer := 13;
-      FShellType := SHELL_DBLSHELL;
+      {$IFDEF ENABLE_SHELLS}
+        FShellTimer := 13;
+        FShellType := SHELL_DBLSHELL;
+      {$ENDIF}
     end;
 
     WEAPON_CHAINGUN:
@@ -4890,8 +4401,9 @@ begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', Gamex, Gamey);
       FFireAngle := FAngle;
       f := True;
-      g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
-                             GameVelX, GameVelY-2, SHELL_BULLET);
+      {$IFDEF ENABLE_SHELLS}
+        g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_BULLET);
+      {$ENDIF}
     end;
 
     WEAPON_ROCKETLAUNCHER:
@@ -4920,8 +4432,9 @@ begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', Gamex, Gamey);
       FFireAngle := FAngle;
       f := True;
-      g_Player_CreateShell(GameX+PLAYER_RECT_CX, GameY+PLAYER_RECT_CX,
-                             GameVelX, GameVelY-2, SHELL_SHELL);
+      {$IFDEF ENABLE_SHELLS}
+        g_Shells_Create(GameX + PLAYER_RECT_CX, GameY + PLAYER_RECT_CX, GameVelX, GameVelY - 2, SHELL_SHELL);
+      {$ENDIF}
     end;
 
     WEAPON_FLAMETHROWER:
@@ -5682,8 +5195,10 @@ begin
 
   if BodyInLiquid(0, 0) then
   begin
-    g_GFX_Bubbles(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)+Random(3)-1,
-                  Obj.Y+Obj.Rect.Height+8, 1, 8, 4);
+    {$IFDEF ENABLE_GFX}
+      g_GFX_Bubbles(Obj.X+Obj.Rect.X+(Obj.Rect.Width div 2)+Random(3)-1,
+                    Obj.Y+Obj.Rect.Height+8, 1, 8, 4);
+    {$ENDIF}
     if Random(2) = 0 then
       g_Sound_PlayExAt('SOUND_GAME_BUBBLE1', FObj.X, FObj.Y)
     else
@@ -5693,11 +5208,13 @@ begin
 
   for i := 1 to Times do
   begin
-    r_GFX_OnceAnim(
-      R_GFX_SMOKE_TRANS,
-      Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_SMOKE_WIDTH div 2),
-      Obj.Y+Obj.Rect.Height-4+Random(8+Times*2)
-    );
+    {$IFDEF ENABLE_GFX}
+      g_GFX_QueueEffect(
+        R_GFX_SMOKE_TRANS,
+        Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_SMOKE_WIDTH div 2),
+        Obj.Y+Obj.Rect.Height-4+Random(8+Times*2)
+      );
+    {$ENDIF}
   end;
 end;
 
@@ -5709,11 +5226,13 @@ begin
 
   for i := 1 to Times do
   begin
-    r_GFX_OnceAnim(
-      R_GFX_FLAME,
-      Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_FLAME_WIDTH div 2),
-      Obj.Y+8+Random(8+Times*2)
-    );
+    {$IFDEF ENABLE_GFX}
+      g_GFX_QueueEffect(
+        R_GFX_FLAME,
+        Obj.X+Obj.Rect.X+Random(Obj.Rect.Width+Times*2)-(R_GFX_FLAME_WIDTH div 2),
+        Obj.Y+8+Random(8+Times*2)
+      );
+    {$ENDIF}
   end;
 end;
 
@@ -5731,213 +5250,6 @@ begin
   FJetSoundOff.Pause(Enable);
 end;
 
-{ T C o r p s e : }
-
-constructor TCorpse.Create(X, Y: Integer; ModelName: String; aMess: Boolean);
-begin
-  g_Obj_Init(@FObj);
-  FObj.X := X;
-  FObj.Y := Y;
-  FObj.Rect := PLAYER_CORPSERECT;
-  FModelName := ModelName;
-  FMess := aMess;
-
-  if FMess then
-    begin
-      FState := CORPSE_STATE_MESS;
-      g_PlayerModel_GetAnim(ModelName, A_DIE2, FAnimation, FAnimationMask);
-    end
-  else
-    begin
-      FState := CORPSE_STATE_NORMAL;
-      g_PlayerModel_GetAnim(ModelName, A_DIE1, FAnimation, FAnimationMask);
-    end;
-end;
-
-destructor TCorpse.Destroy();
-begin
-  FAnimation.Free();
-
-  inherited;
-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; SpawnerUID: Word; vx, vy: Integer);
-var
-  pm: TPlayerModel;
-  Blood: TModelBlood;
-begin
-  if FState = CORPSE_STATE_REMOVEME then
-    Exit;
-
-  FDamage := FDamage + Value;
-
-  if FDamage > 150 then
-    begin
-      if FAnimation <> nil then
-      begin
-        FAnimation.Free();
-        FAnimation := nil;
-
-        FState := CORPSE_STATE_REMOVEME;
-
-        g_Player_CreateGibs(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
-                            FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
-                            FModelName, FColor);
-        // Звук мяса от трупа:
-        pm := g_PlayerModel_Get(FModelName);
-        pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
-        pm.Free;
-
-        // Зловещий смех:
-        if (gBodyKillEvent <> -1)
-        and gDelayedEvents[gBodyKillEvent].Pending then
-          gDelayedEvents[gBodyKillEvent].Pending := False;
-        gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, SpawnerUID);
-      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,
-                  Blood.R, Blood.G, Blood.B, Blood.Kind);
-    end;
-end;
-
-procedure TCorpse.Update();
-var
-  st: Word;
-begin
-  if FState = CORPSE_STATE_REMOVEME then
-    Exit;
-
-  FObj.oldX := FObj.X;
-  FObj.oldY := FObj.Y;
-
-  if gTime mod (GAME_TICK*2) <> 0 then
-  begin
-    g_Obj_Move(@FObj, True, True, True);
-    positionChanged(); // this updates spatial accelerators
-    Exit;
-  end;
-
-// Сопротивление воздуха для трупа:
-  FObj.Vel.X := z_dec(FObj.Vel.X, 1);
-
-  st := g_Obj_Move(@FObj, True, True, True);
-  positionChanged(); // this updates spatial accelerators
-
-  if WordBool(st and MOVE_FALLOUT) then
-  begin
-    FState := CORPSE_STATE_REMOVEME;
-    Exit;
-  end;
-
-  if FAnimation <> nil then
-    FAnimation.Update();
-  if FAnimationMask <> nil then
-    FAnimationMask.Update();
-end;
-
-
-procedure TCorpse.SaveState (st: TStream);
-var
-  anim: Boolean;
-begin
-  assert(st <> nil);
-
-  // Сигнатура трупа
-  utils.writeSign(st, 'CORP');
-  utils.writeInt(st, Byte(0));
-  // Состояние
-  utils.writeInt(st, Byte(FState));
-  // Накопленный урон
-  utils.writeInt(st, Byte(FDamage));
-  // Цвет
-  utils.writeInt(st, Byte(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
-  anim: Boolean;
-begin
-  assert(st <> nil);
-
-  // Сигнатура трупа
-  if not utils.checkSign(st, 'CORP') then raise XStreamError.Create('invalid corpse signature');
-  if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid corpse version');
-  // Состояние
-  FState := utils.readByte(st);
-  // Накопленный урон
-  FDamage := utils.readByte(st);
-  // Цвет
-  FColor.R := utils.readByte(st);
-  FColor.G := utils.readByte(st);
-  FColor.B := utils.readByte(st);
-  // Объект трупа
-  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(st);
-  end;
-  // Есть ли маска анимации
-  anim := utils.readBool(st);
-  // Если есть - загружаем
-  if anim then
-  begin
-    Assert(FAnimationMask <> nil, 'TCorpse.LoadState: no FAnimationMask');
-    FAnimationMask.LoadState(st);
-  end;
-end;
-
 { T B o t : }
 
 constructor TBot.Create();