DEADSOFTWARE

game: disable gfx for server
[d2df-sdl.git] / src / game / g_player.pas
index c2605a092509cd28d1f5bbf36c64a5e7fb7a1014..db438329dd146c0de2d43eb20ec662bc99002c09 100644 (file)
@@ -191,7 +191,7 @@ type
     FSavedStateNum:   Integer;
 
     FModel:     TPlayerModel;
-    FPunchAnim: TAnimation;
+    FPunchAnim: TAnimationState;
     FActionPrior:    Byte;
     FActionAnim:     Byte;
     FActionForce:    Boolean;
@@ -403,7 +403,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;
 
@@ -504,12 +504,13 @@ type
   PGib = ^TGib;
   TGib = record
     alive:    Boolean;
-    ID:       DWORD;
-    MaskID:   DWORD;
     RAngle:   Integer;
     Color:    TRGB;
     Obj:      TObj;
 
+    ModelID: Integer;
+    GibID: Integer;
+
     procedure getMapBox (out x, y, w, h: Integer); inline;
     procedure moveBy (dx, dy: Integer); inline;
 
@@ -519,12 +520,10 @@ type
 
   PShell = ^TShell;
   TShell = record
-    SpriteID: DWORD;
-    alive:     Boolean;
+    alive:    Boolean;
     SType:    Byte;
     RAngle:   Integer;
     Timeout:  Cardinal;
-    CX, CY:   Integer;
     Obj:      TObj;
 
     procedure getMapBox (out x, y, w, h: Integer); inline;
@@ -535,15 +534,12 @@ type
 
   TCorpse = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
   private
-    FModelName:     String;
     FMess:          Boolean;
     FState:         Byte;
     FDamage:        Byte;
-    FColor:         TRGB;
     FObj:           TObj;
     FPlayerUID:     Word;
-    FAnimation:     TAnimation;
-    FAnimationMask: TAnimation;
+    FModel:   TPlayerModel;
 
   public
     constructor Create(X, Y: Integer; ModelName: String; aMess: Boolean);
@@ -563,11 +559,7 @@ type
     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;
+    property    Model: TPlayerModel read FModel;
   end;
 
   TTeamStat = Array [TEAM_RED..TEAM_BLUE] of
@@ -617,7 +609,7 @@ 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_CreateGibs (fX, fY, mid: Integer; fColor: TRGB);
 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
 procedure g_Player_UpdatePhysicalObjects();
 procedure g_Player_RemoveAllCorpses();
@@ -632,11 +624,20 @@ 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}
+  {$IFNDEF HEADLESS}
+    r_render,
+  {$ENDIF}
+  {$IFDEF ENABLE_GFX}
+    g_gfx,
+  {$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;
@@ -1569,7 +1570,7 @@ begin
           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].FModel.Color := FModel.Color;
         gCorpses[find_id].FObj.Vel := FObj.Vel;
         gCorpses[find_id].FObj.Accel := FObj.Accel;
         gCorpses[find_id].FPlayerUID := FUID;
@@ -1577,40 +1578,27 @@ begin
         Result := find_id;
       end
     else
-      g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX,
-                          FObj.Y + PLAYER_RECT_CY,
-                          FModel.GetName(), FModel.Color);
+      g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX, FObj.Y + PLAYER_RECT_CY, FModel.id, FModel.Color);
   end;
 end;
 
 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
-var
-  SID: DWORD;
 begin
   if (gShells = nil) or (Length(gShells) = 0) then
     Exit;
 
   with gShells[CurrentShell] do
   begin
-    SpriteID := 0;
     g_Obj_Init(@Obj);
     Obj.Rect.X := 0;
     Obj.Rect.Y := 0;
     if T = SHELL_BULLET then
     begin
-      if g_Texture_Get('TEXTURE_SHELL_BULLET', SID) then
-        SpriteID := SID;
-      CX := 2;
-      CY := 1;
       Obj.Rect.Width := 4;
       Obj.Rect.Height := 2;
     end
     else
     begin
-      if g_Texture_Get('TEXTURE_SHELL_SHELL', SID) then
-        SpriteID := SID;
-      CX := 4;
-      CY := 2;
       Obj.Rect.Width := 7;
       Obj.Rect.Height := 3;
     end;
@@ -1630,36 +1618,65 @@ begin
   end;
 end;
 
-procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: string; fColor: TRGB);
+procedure g_Player_CreateGibs (fX, fY, mid: Integer; fColor: TRGB);
 var
   a: Integer;
   GibsArray: TGibsArray;
-  Blood: TModelBlood;
+  {$IFDEF ENABLE_GFX}
+    Blood: TModelBlood;
+  {$ENDIF}
 begin
+  if mid = -1 then
+    Exit;
   if (gGibs = nil) or (Length(gGibs) = 0) then
     Exit;
-  if not g_PlayerModel_GetGibs(ModelName, GibsArray) then
+  if not g_PlayerModel_GetGibs(mid, GibsArray) then
     Exit;
-  Blood := g_PlayerModel_GetBlood(ModelName);
+
+  {$IFDEF ENABLE_GFX}
+    Blood := PlayerModelsArray[mid].Blood;
+  {$ENDIF}
 
   for a := 0 to High(GibsArray) do
     with gGibs[CurrentGib] do
     begin
+      ModelID := mid;
+      GibID := GibsArray[a];
       Color := fColor;
-      ID := GibsArray[a].ID;
-      MaskID := GibsArray[a].MaskID;
       alive := True;
       g_Obj_Init(@Obj);
-      Obj.Rect := GibsArray[a].Rect;
-      Obj.X := fX-GibsArray[a].Rect.X-(GibsArray[a].Rect.Width div 2);
-      Obj.Y := fY-GibsArray[a].Rect.Y-(GibsArray[a].Rect.Height div 2);
+      {$IFNDEF HEADLESS}
+        Obj.Rect := r_Render_GetGibRect(ModelID, GibID);
+      {$ELSE}
+        Obj.Rect.X := 16;
+        Obj.Rect.Y := 16;
+        Obj.Rect.Width := 16;
+        Obj.Rect.Height := 16;
+      {$ENDIF}
+      Obj.X := fX - Obj.Rect.X - (Obj.Rect.Width div 2);
+      Obj.Y := fY - Obj.Rect.Y - (Obj.Rect.Height div 2);
       g_Obj_PushA(@Obj, 25 + Random(10), Random(361));
       positionChanged(); // this updates spatial accelerators
       RAngle := Random(360);
 
-      if gBloodCount > 0 then
-        g_GFX_Blood(fX, fY, 16*gBloodCount+Random(5*gBloodCount), -16+Random(33), -16+Random(33),
-                    Random(48), Random(48), Blood.R, Blood.G, Blood.B, Blood.Kind);
+      {$IFDEF ENABLE_GFX}
+        if gBloodCount > 0 then
+        begin
+          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
+         );
+        end;
+      {$ENDIF}
 
       if CurrentGib >= High(gGibs) then
         CurrentGib := 0
@@ -1872,7 +1889,7 @@ begin
     if gCorpses[i] <> nil then
     begin
       // Название модели
-      utils.writeStr(st, gCorpses[i].FModelName);
+      utils.writeStr(st, gCorpses[i].FModel.GetName());
       // Тип смерти
       utils.writeBool(st, gCorpses[i].Mess);
       // Сохраняем данные трупа:
@@ -2119,6 +2136,8 @@ begin
   FNetTime := 0;
 
   FWaitForFirstSpawn := false;
+  FPunchAnim := TAnimationState.Create(False, 1, 4);
+  FPunchAnim.Disable;
 
   resetWeaponQueue();
 end;
@@ -2202,9 +2221,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 +2288,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();
@@ -3015,27 +3022,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);
@@ -3940,13 +3955,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;
@@ -4142,11 +4161,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 +4215,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,
@@ -4291,8 +4314,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();
@@ -4591,7 +4616,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
@@ -4680,7 +4707,7 @@ begin
   if (FModel.AnimState.Played or ((not FActionChanged) and (FModel.Animation = A_WALK)))
   then SetAction(A_STAND, True);
 
-  if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.Fire) then FModel.Update;
+  if not ((FModel.Animation = A_WALK) and (Abs(FObj.Vel.X) < 4) and not FModel.GetFire()) then FModel.Update;
 
   for b := Low(FKeys) to High(FKeys) do
     if FKeys[b].Time = 0 then FKeys[b].Pressed := False else Dec(FKeys[b].Time);
@@ -5682,8 +5709,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 +5722,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 +5740,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;
 
@@ -5739,25 +5772,24 @@ begin
   FObj.X := X;
   FObj.Y := Y;
   FObj.Rect := PLAYER_CORPSERECT;
-  FModelName := ModelName;
   FMess := aMess;
+  FModel := g_PlayerModel_Get(ModelName);
 
   if FMess then
-    begin
-      FState := CORPSE_STATE_MESS;
-      g_PlayerModel_GetAnim(ModelName, A_DIE2, FAnimation, FAnimationMask);
-    end
+  begin
+    FState := CORPSE_STATE_MESS;
+    FModel.ChangeAnimation(A_DIE2);
+  end
   else
-    begin
-      FState := CORPSE_STATE_NORMAL;
-      g_PlayerModel_GetAnim(ModelName, A_DIE1, FAnimation, FAnimationMask);
-    end;
+  begin
+    FState := CORPSE_STATE_NORMAL;
+    FModel.ChangeAnimation(A_DIE1);
+  end;
 end;
 
 destructor TCorpse.Destroy();
 begin
-  FAnimation.Free();
-
+  FModel.Free;
   inherited;
 end;
 
@@ -5786,9 +5818,9 @@ end;
 
 
 procedure TCorpse.Damage(Value: Word; SpawnerUID: Word; vx, vy: Integer);
-var
-  pm: TPlayerModel;
-  Blood: TModelBlood;
+  {$IFDEF ENABLE_GFX}
+    var Blood: TModelBlood;
+  {$ENDIF}
 begin
   if FState = CORPSE_STATE_REMOVEME then
     Exit;
@@ -5796,38 +5828,41 @@ begin
   FDamage := FDamage + Value;
 
   if FDamage > 150 then
+  begin
+    if FModel <> nil then
     begin
-      if FAnimation <> nil then
-      begin
-        FAnimation.Free();
-        FAnimation := nil;
-
-        FState := CORPSE_STATE_REMOVEME;
-
-        g_Player_CreateGibs(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
-                            FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
-                            FModelName, FColor);
-        // Звук мяса от трупа:
-        pm := g_PlayerModel_Get(FModelName);
-        pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
-        pm.Free;
-
-        // Зловещий смех:
-        if (gBodyKillEvent <> -1)
-        and gDelayedEvents[gBodyKillEvent].Pending then
-          gDelayedEvents[gBodyKillEvent].Pending := False;
-        gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, SpawnerUID);
-      end;
+      FState := CORPSE_STATE_REMOVEME;
+
+      g_Player_CreateGibs(
+        FObj.X + FObj.Rect.X + (FObj.Rect.Width div 2),
+        FObj.Y + FObj.Rect.Y + (FObj.Rect.Height div 2),
+        FModel.id,
+        FModel.Color
+      );
+
+      // Звук мяса от трупа:
+      FModel.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
+
+      // Зловещий смех:
+      if (gBodyKillEvent <> -1) and gDelayedEvents[gBodyKillEvent].Pending then
+        gDelayedEvents[gBodyKillEvent].Pending := False;
+      gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, SpawnerUID);
+
+      FModel.Free;
+      FModel := nil;
     end
+  end
   else
     begin
-      Blood := g_PlayerModel_GetBlood(FModelName);
       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);
+      {$IFDEF ENABLE_GFX}
+        Blood := FModel.GetBlood();
+        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);
+      {$ENDIF}
     end;
 end;
 
@@ -5860,16 +5895,13 @@ begin
     Exit;
   end;
 
-  if FAnimation <> nil then
-    FAnimation.Update();
-  if FAnimationMask <> nil then
-    FAnimationMask.Update();
+  if FModel <> nil then
+    FModel.Update;
 end;
 
 
 procedure TCorpse.SaveState (st: TStream);
-var
-  anim: Boolean;
+  var anim: Boolean;
 begin
   assert(st <> nil);
 
@@ -5881,28 +5913,25 @@ begin
   // Накопленный урон
   utils.writeInt(st, Byte(FDamage));
   // Цвет
-  utils.writeInt(st, Byte(FColor.R));
-  utils.writeInt(st, Byte(FColor.G));
-  utils.writeInt(st, Byte(FColor.B));
+  utils.writeInt(st, Byte(FModel.Color.R));
+  utils.writeInt(st, Byte(FModel.Color.G));
+  utils.writeInt(st, Byte(FModel.Color.B));
   // Объект трупа
   Obj_SaveState(st, @FObj);
   utils.writeInt(st, Word(FPlayerUID));
-  // Есть ли анимация
-  anim := (FAnimation <> nil);
+  // animation
+  anim := (FModel <> nil);
   utils.writeBool(st, anim);
-  // Если есть - сохраняем
-  if anim then FAnimation.SaveState(st);
-  // Есть ли маска анимации
-  anim := (FAnimationMask <> nil);
+  if anim then FModel.AnimState.SaveState(st, 0, False);
+  // animation for mask (same as animation, compat with older saves)
+  anim := (FModel <> nil);
   utils.writeBool(st, anim);
-  // Если есть - сохраняем
-  if anim then FAnimationMask.SaveState(st);
+  if anim then FModel.AnimState.SaveState(st, 0, False);
 end;
 
 
 procedure TCorpse.LoadState (st: TStream);
-var
-  anim: Boolean;
+  var anim, blending: Boolean; r, g, b, alpha: Byte; stub: TAnimationState;
 begin
   assert(st <> nil);
 
@@ -5914,28 +5943,30 @@ begin
   // Накопленный урон
   FDamage := utils.readByte(st);
   // Цвет
-  FColor.R := utils.readByte(st);
-  FColor.G := utils.readByte(st);
-  FColor.B := utils.readByte(st);
+  r := utils.readByte(st);
+  g := utils.readByte(st);
+  b := utils.readByte(st);
+  FModel.SetColor(r, g, b);
   // Объект трупа
   Obj_LoadState(@FObj, st);
   FPlayerUID := utils.readWord(st);
-  // Есть ли анимация
+  // animation
+  stub := TAnimationState.Create(False, 0, 0);
   anim := utils.readBool(st);
-  // Если есть - загружаем
   if anim then
   begin
-    Assert(FAnimation <> nil, 'TCorpse.LoadState: no FAnimation');
-    FAnimation.LoadState(st);
-  end;
-  // Есть ли маска анимации
-  anim := utils.readBool(st);
-  // Если есть - загружаем
-  if anim then
+    stub.LoadState(st, alpha, blending);
+    FModel.AnimState.CurrentFrame := Min(stub.CurrentFrame, FModel.AnimState.Length);
+  end
+  else
   begin
-    Assert(FAnimationMask <> nil, 'TCorpse.LoadState: no FAnimationMask');
-    FAnimationMask.LoadState(st);
+    FModel.Free;
+    FModel := nil
   end;
+  // animation for mask (same as animation, compat with older saves)
+  anim := utils.readBool(st);
+  if anim then stub.LoadState(st, alpha, blending);
+  stub.Free;
 end;
 
 { T B o t : }