DEADSOFTWARE

gl: implement load screen
[d2df-sdl.git] / src / game / g_monsters.pas
index 213e264181188a5386926fecb922aadc4418f9d7..5fa50114660f43d7a5df764ce662a16ab4b11b0f 100644 (file)
@@ -23,7 +23,7 @@ uses
   SysUtils, Classes,
   mempool,
   MAPDEF,
-  g_base, g_basic, g_phys, g_textures, g_grid,
+  g_base, g_basic, g_phys, g_animations, g_grid,
   g_saveload, g_panel, xprofiler;
 
 const
@@ -52,7 +52,7 @@ const
 }
 
 type
-  ADirectedAnim = Array of Array [TDirection.D_LEFT..TDirection.D_RIGHT] of TAnimationState;
+  ADirectedAnim = Array of Array [TDirection.D_LEFT..TDirection.D_RIGHT] of TAnimState;
 
   PMonster = ^TMonster;
   TMonster = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
@@ -87,11 +87,13 @@ type
       FBloodBlue: Byte;
       FBloodKind: Byte;
     {$ENDIF}
-    FShellTimer: Integer;
-    FShellType: Byte;
+    {$IFDEF ENABLE_SHELLS}
+      FShellTimer: Integer;
+      FShellType: Byte;
+    {$ENDIF}
     FFirePainTime: Integer;
     FFireAttacker: Word;
-    vilefire: TAnimationState;
+    FVileFireTime: LongWord;
     mProxyId: Integer; // node in dyntree or -1
     mArrIdx: Integer; // in gMonsters
 
@@ -196,7 +198,7 @@ type
 
     property StartID: Integer read FStartID;
 
-    property VileFireAnim: TAnimationState read vilefire;
+    property VileFireTime: LongWord read FVileFireTime;
     property DirAnim: ADirectedAnim read FAnim;
 
   published
@@ -326,6 +328,7 @@ var
     ANIM_ATTACK  = 4;
     ANIM_ATTACK2 = 5;
     ANIM_PAIN    = 6;
+    ANIM_LAST    = ANIM_PAIN;
 
 // Таблица характеристик монстров:
   MONSTERTABLE: Array [MONSTER_DEMON..MONSTER_MAN] of
@@ -529,8 +532,17 @@ uses
   {$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_sound, g_player, g_game,
-  g_weapons, g_triggers, g_items, g_options,
+  g_weapons, g_triggers, g_items, g_options, g_window,
   g_console, g_map, Math, wadreader,
   g_language, g_netmsg, idpool, utils, xstreams;
 
@@ -884,10 +896,6 @@ end;
 procedure g_Monsters_LoadData();
 begin
   e_WriteLog('Loading monsters data...', TMsgType.Notify);
-
-  g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_TEXTURES], 133, False);
-  g_Game_StepLoading(133);
-
   g_Game_SetLoadingText(_lc[I_LOAD_MONSTER_SOUNDS], 0, False);
 
   g_Sound_CreateWADEx('SOUND_MONSTER_BARREL_DIE', GameWAD+':MSOUNDS\BARREL_DIE');
@@ -1588,7 +1596,9 @@ begin
   FDieTriggers := nil;
   FWaitAttackAnim := False;
   FChainFire := False;
-  FShellTimer := -1;
+  {$IFDEF ENABLE_SHELLS}
+    FShellTimer := -1;
+  {$ENDIF}
 
   FState := MONSTATE_SLEEP;
   FCurAnim := ANIM_SLEEP;
@@ -1625,7 +1635,9 @@ begin
   FChainFire := False;
   FStartID := aID;
   FNoRespawn := False;
-  FShellTimer := -1;
+  {$IFDEF ENABLE_SHELLS}
+    FShellTimer := -1;
+  {$ENDIF}
   FBehaviour := BH_NORMAL;
   FFireTime := 0;
   FFirePainTime := 0;
@@ -1666,11 +1678,11 @@ begin
   SetLength(FAnim, Length(ANIMTABLE));
   for a := ANIM_SLEEP to ANIM_PAIN do
   begin
-    FAnim[a, TDirection.D_RIGHT] := TAnimationState.Create(ANIMTABLE[a].loop, MONSTER_ANIMTABLE[MonsterType].AnimSpeed[a], MONSTER_ANIMTABLE[MonsterType].AnimCount[a]);
-    FAnim[a, TDirection.D_LEFT] := TAnimationState.Create(ANIMTABLE[a].loop, MONSTER_ANIMTABLE[MonsterType].AnimSpeed[a], MONSTER_ANIMTABLE[MonsterType].AnimCount[a]);
+    FAnim[a, TDirection.D_RIGHT] := TAnimState.Create(ANIMTABLE[a].loop, MONSTER_ANIMTABLE[MonsterType].AnimSpeed[a], MONSTER_ANIMTABLE[MonsterType].AnimCount[a]);
+    FAnim[a, TDirection.D_LEFT] := TAnimState.Create(ANIMTABLE[a].loop, MONSTER_ANIMTABLE[MonsterType].AnimSpeed[a], MONSTER_ANIMTABLE[MonsterType].AnimCount[a]);
   end;
-  if MonsterType = MONSTER_VILE then
-    vilefire := TAnimationState.Create(True, 2, 8);
+
+  FVileFireTime := gTime;
 end;
 
 function TMonster.Damage(aDamage: Word; VelX, VelY: Integer; SpawnerUID: Word; t: Byte): Boolean;
@@ -1863,11 +1875,11 @@ var
 begin
   for a := 0 to High(FAnim) do
   begin
-    FAnim[a, TDirection.D_LEFT].Free();
-    FAnim[a, TDirection.D_RIGHT].Free();
+    FAnim[a, TDirection.D_LEFT].Invalidate;
+    FAnim[a, TDirection.D_RIGHT].Invalidate;
   end;
 
-  vilefire.Free();
+  FVileFireTime := 0;
 
   if (mProxyId <> -1) then
   begin
@@ -1974,7 +1986,7 @@ begin
 
 // Если анимация новая - перезапускаем её:
   if FCurAnim <> Anim then
-    if FAnim[Anim, FDirection] <> nil then
+    if FAnim[Anim, FDirection].IsValid() then
     begin
       FAnim[Anim, FDirection].Reset();
       FCurAnim := Anim;
@@ -2058,14 +2070,20 @@ begin
 end;
 
 procedure TMonster.Update();
-var
-  a, b, sx, sy, wx, wy, oldvelx: Integer;
-  st: Word;
-  o, co: TObj;
-  fall, bubbles: Boolean;
-  mon: TMonster;
-  mit: PMonster;
-  it: TMonsterGrid.Iter;
+  {$IFDEF ENABLE_CORPSES}
+    var co: TObj;
+  {$ENDIF}
+  {$IF DEFINED(ENABLE_GIBS) OR DEFINED(ENABLE_CORPSES)}
+    var b: Integer;
+  {$ENDIF}
+  var
+    a, sx, sy, wx, wy, oldvelx: Integer;
+    st: Word;
+    o: TObj;
+    fall, bubbles: Boolean;
+    mon: TMonster;
+    mit: PMonster;
+    it: TMonsterGrid.Iter;
 label
   _end;
 begin
@@ -2215,25 +2233,35 @@ begin
 // Таймер - ждем после потери цели:
   FTargetTime := FTargetTime + 1;
 
-// Гильзы
+{$IFDEF ENABLE_SHELLS}
+  // Гильзы
   if FShellTimer > -1 then
+  begin
     if FShellTimer = 0 then
     begin
       if FShellType = SHELL_SHELL then
-        g_Player_CreateShell(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
+      begin
+        g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
                              FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
                              GameVelX, GameVelY-2, SHELL_SHELL)
+      end
       else if FShellType = SHELL_DBLSHELL then
       begin
-        g_Player_CreateShell(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
+        g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
                              FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
                              GameVelX-1, GameVelY-2, SHELL_SHELL);
-        g_Player_CreateShell(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
+        g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
                              FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
                              GameVelX+1, GameVelY-2, SHELL_SHELL);
       end;
       FShellTimer := -1;
-    end else Dec(FShellTimer);
+    end
+    else
+    begin
+      Dec(FShellTimer);
+    end;
+  end;
+{$ENDIF}
 
 // Пробуем увернуться от летящей пули:
   if fall then
@@ -2498,48 +2526,52 @@ begin
           end
         else // "Наземные" монстры
           begin
-          // Возможно, пинаем куски:
-            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));    // направо
-                  end;
-                end;
-              end;
-            end;
-          // Боссы могут пинать трупы:
-            if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and
-               (FObj.Vel.X <> 0) and (gCorpses <> nil) then
-            begin
-              b := Abs(FObj.Vel.X);
-              if b > 1 then b := b * (Random(8 div b) + 1);
-              for a := 0 to High(gCorpses) do
-                if (gCorpses[a] <> nil) and (gCorpses[a].State > 0) then
-                begin
-                  co := gCorpses[a].Obj;
-                  if g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
-                                   FObj.Rect.Width, 8, @co) and (Random(3) = 0) then
-                    // Пинаем трупы
+                    // Пинаем куски
                     if FObj.Vel.X < 0 then
-                      gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево
+                    begin
+                      g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево
+                    end
                     else
-                      gCorpses[a].Damage(b*2, FUID, b, Random(7)); // направо
+                    begin
+                      g_Obj_PushA(@gGibs[a].Obj, b, Random(61));    // направо
+                    end;
+                  end;
                 end;
-            end;
+              end;
+            {$ENDIF}
+            {$IFDEF ENABLE_CORPSES}
+              // Боссы могут пинать трупы:
+              if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and
+                 (FObj.Vel.X <> 0) and (gCorpses <> nil) then
+              begin
+                b := Abs(FObj.Vel.X);
+                if b > 1 then b := b * (Random(8 div b) + 1);
+                for a := 0 to High(gCorpses) do
+                  if (gCorpses[a] <> nil) and (gCorpses[a].State > 0) then
+                  begin
+                    co := gCorpses[a].Obj;
+                    if g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
+                                     FObj.Rect.Width, 8, @co) and (Random(3) = 0) then
+                      // Пинаем трупы
+                      if FObj.Vel.X < 0 then
+                        gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево
+                      else
+                        gCorpses[a].Damage(b*2, FUID, b, Random(7)); // направо
+                  end;
+              end;
+            {$ENDIF}
           // Если цель высоко, то, возможно, прыгаем:
             if sy < -40 then
               if g_Obj_CollideLevel(@FObj, 0, 1) or g_Obj_StayOnStep(@FObj) then
@@ -2741,13 +2773,9 @@ _end:
       SetState(MONSTATE_GO);
     end;
 
-// Если есть анимация огня колдуна - пусть она идет:
-  if vilefire <> nil then
-    vilefire.Update();
-
 // Состояние - Умирает и текущая анимация проиграна:
   if (FState = MONSTATE_DIE) and
-     (FAnim[FCurAnim, FDirection] <> nil) and
+     (FAnim[FCurAnim, FDirection].IsValid()) and
      (FAnim[FCurAnim, FDirection].Played) then
     begin
     // Умер:
@@ -2798,7 +2826,7 @@ _end:
 
 // Совершение атаки и стрельбы:
   if (FState = MONSTATE_ATTACK) or (FState = MONSTATE_SHOOT) then
-    if (FAnim[FCurAnim, FDirection] <> nil) then
+    if (FAnim[FCurAnim, FDirection].IsValid()) then
     // Анимация атаки есть - можно атаковать
       if (FAnim[FCurAnim, FDirection].Played) then
         begin // Анимация атаки закончилась => переходим на шаг
@@ -2912,20 +2940,26 @@ _end:
                   begin
                     g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
                     g_Weapon_gun(wx, wy, tx, ty, 1, 3, FUID, True);
-                    g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET);
+                    {$IFDEF ENABLE_SHELLS}
+                      g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
+                    {$ENDIF}
                   end;
                 MONSTER_SERG:
                   begin
                     g_Weapon_shotgun(wx, wy, tx, ty, FUID);
                     if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
-                    FShellTimer := 10;
-                    FShellType := SHELL_SHELL;
+                    {$IFDEF ENABLE_SHELLS}
+                      FShellTimer := 10;
+                      FShellType := SHELL_SHELL;
+                    {$ENDIF}
                   end;
                 MONSTER_MAN:
                   begin
                     g_Weapon_dshotgun(wx, wy, tx, ty, FUID);
-                    FShellTimer := 13;
-                    FShellType := SHELL_DBLSHELL;
+                    {$IFDEF ENABLE_SHELLS}
+                      FShellTimer := 13;
+                      FShellType := SHELL_DBLSHELL;
+                    {$ENDIF}
                     FAmmo := -36;
                   end;
                 MONSTER_CYBER:
@@ -2939,13 +2973,17 @@ _end:
                   begin
                     g_Weapon_mgun(wx, wy, tx, ty, FUID);
                     if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
-                    g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET);
+                    {$IFDEF ENABLE_SHELLS}
+                      g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
+                    {$ENDIF}
                   end;
                 MONSTER_SPIDER:
                   begin
                     g_Weapon_mgun(wx, wy, tx, ty, FUID);
                     if not gSoundEffectsDF then g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
-                    g_Player_CreateShell(wx, wy, 0, -2, SHELL_SHELL);
+                    {$IFDEF ENABLE_SHELLS}
+                      g_Shells_Create(wx, wy, 0, -2, SHELL_SHELL);
+                    {$ENDIF}
                   end;
                 MONSTER_BSP:
                   g_Weapon_aplasma(wx, wy, tx, ty, FUID);
@@ -3029,7 +3067,7 @@ _end:
     FObj.Vel.X := oldvelx;
 
 // Если есть анимация, то пусть она идет:
-  if FAnim[FCurAnim, FDirection] <> nil then
+  if FAnim[FCurAnim, FDirection].IsValid() then
     FAnim[FCurAnim, FDirection].Update();
 end;
 
@@ -3055,11 +3093,14 @@ begin
 end;
 
 procedure TMonster.ClientUpdate();
-var
-  a, b, sx, sy, oldvelx: Integer;
-  st: Word;
-  o, co: TObj;
-  fall, bubbles: Boolean;
+  {$IFDEF ENABLE_CORPSES}
+    var a, b: Integer; co: TObj;
+  {$ENDIF}
+  var
+    sx, sy, oldvelx: Integer;
+    st: Word;
+    o: TObj;
+    fall, bubbles: Boolean;
 label
   _end;
 begin
@@ -3179,24 +3220,34 @@ begin
 // Таймер - ждем после потери цели:
   FTargetTime := FTargetTime + 1;
 
+{$IFDEF ENABLE_SHELLS}
   if FShellTimer > -1 then
+  begin
     if FShellTimer = 0 then
     begin
       if FShellType = SHELL_SHELL then
-        g_Player_CreateShell(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
+      begin
+        g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
                              FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
                              GameVelX, GameVelY-2, SHELL_SHELL)
+      end
       else if FShellType = SHELL_DBLSHELL then
       begin
-        g_Player_CreateShell(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
+        g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
                              FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
                              GameVelX-1, GameVelY-2, SHELL_SHELL);
-        g_Player_CreateShell(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
+        g_Shells_Create(FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2),
                              FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2),
                              GameVelX+1, GameVelY-2, SHELL_SHELL);
       end;
       FShellTimer := -1;
-    end else Dec(FShellTimer);
+    end
+    else
+    begin
+      Dec(FShellTimer);
+    end;
+  end;
+{$ENDIF}
 
 // Пробуем увернуться от летящей пули:
   if fall then
@@ -3350,49 +3401,53 @@ begin
           end
         else // "Наземные" монстры
           begin
-          // Возможно, пинаем куски:
-            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 ENBALE_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));    // направо
-                  end;
-                  positionChanged(); // this updates spatial accelerators
-                end;
-              end;
-            end;
-          // Боссы могут пинать трупы:
-            if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and
-               (FObj.Vel.X <> 0) and (gCorpses <> nil) then
-            begin
-              b := Abs(FObj.Vel.X);
-              if b > 1 then b := b * (Random(8 div b) + 1);
-              for a := 0 to High(gCorpses) do
-                if (gCorpses[a] <> nil) and (gCorpses[a].State > 0) then
-                begin
-                  co := gCorpses[a].Obj;
-                  if g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
-                                   FObj.Rect.Width, 8, @co) and (Random(3) = 0) then
-                    // Пинаем трупы
+                    // Пинаем куски
                     if FObj.Vel.X < 0 then
-                      gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево
+                    begin
+                      g_Obj_PushA(@gGibs[a].Obj, b, Random(61)+120); // налево
+                    end
                     else
-                      gCorpses[a].Damage(b*2, FUID, b, Random(7)); // направо
+                    begin
+                      g_Obj_PushA(@gGibs[a].Obj, b, Random(61));    // направо
+                    end;
+                    positionChanged(); // this updates spatial accelerators
+                  end;
                 end;
-            end;
+              end;
+            {$ENDIF}
+            {$IFDEF ENABLE_CORPSES}
+              // Боссы могут пинать трупы:
+              if (FMonsterType in [MONSTER_CYBER, MONSTER_SPIDER, MONSTER_ROBO]) and
+                 (FObj.Vel.X <> 0) and (gCorpses <> nil) then
+              begin
+                b := Abs(FObj.Vel.X);
+                if b > 1 then b := b * (Random(8 div b) + 1);
+                for a := 0 to High(gCorpses) do
+                  if (gCorpses[a] <> nil) and (gCorpses[a].State > 0) then
+                  begin
+                    co := gCorpses[a].Obj;
+                    if g_Obj_Collide(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y+FObj.Rect.Height-4,
+                                     FObj.Rect.Width, 8, @co) and (Random(3) = 0) then
+                      // Пинаем трупы
+                      if FObj.Vel.X < 0 then
+                        gCorpses[a].Damage(b*2, FUID, -b, Random(7)) // налево
+                      else
+                        gCorpses[a].Damage(b*2, FUID, b, Random(7)); // направо
+                  end;
+              end;
+            {$ENDIF}
           end;
 
         FSleep := FSleep + 1;
@@ -3577,13 +3632,9 @@ _end:
       SetState(MONSTATE_GO);
     end;
 
-// Если есть анимация огня колдуна - пусть она идет:
-  if vilefire <> nil then
-    vilefire.Update();
-
 // Состояние - Умирает и текущая анимация проиграна:
   if (FState = MONSTATE_DIE) and
-     (FAnim[FCurAnim, FDirection] <> nil) and
+     (FAnim[FCurAnim, FDirection].IsValid()) and
      (FAnim[FCurAnim, FDirection].Played) then
     begin
     // Умер:
@@ -3600,7 +3651,7 @@ _end:
 
 // Совершение атаки и стрельбы:
   if (FState = MONSTATE_ATTACK) or (FState = MONSTATE_SHOOT) then
-    if (FAnim[FCurAnim, FDirection] <> nil) then
+    if (FAnim[FCurAnim, FDirection].IsValid()) then
     // Анимация атаки есть - можно атаковать
       if (FAnim[FCurAnim, FDirection].Played) then
         begin // Анимация атаки закончилась => переходим на шаг
@@ -3726,7 +3777,7 @@ _end:
     FObj.Vel.X := oldvelx;
 
 // Если есть анимация, то пусть она идет:
-  if FAnim[FCurAnim, FDirection] <> nil then
+  if FAnim[FCurAnim, FDirection].IsValid() then
     FAnim[FCurAnim, FDirection].Update();
 end;
 
@@ -3736,24 +3787,32 @@ begin
     MONSTER_ZOMBY:
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIREPISTOL', wx, wy);
-      g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET);
+      {$IFDEF ENABLE_SHELLS}
+        g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
+      {$ENDIF}
     end;
     MONSTER_SERG:
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN', wx, wy);
-      FShellTimer := 10;
-      FShellType := SHELL_SHELL;
+      {$IFDEF ENABLE_SHELLS}
+        FShellTimer := 10;
+        FShellType := SHELL_SHELL;
+      {$ENDIF}
     end;
     MONSTER_MAN:
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRESHOTGUN2', wx, wy);
-      FShellTimer := 13;
-      FShellType := SHELL_DBLSHELL;
+      {$IFDEF ENABLE_SHELLS}
+        FShellTimer := 13;
+        FShellType := SHELL_DBLSHELL;
+      {$ENDIF}
     end;
     MONSTER_CGUN, MONSTER_SPIDER:
     begin
       g_Sound_PlayExAt('SOUND_WEAPON_FIRECGUN', wx, wy);
-      g_Player_CreateShell(wx, wy, 0, -2, SHELL_BULLET);
+      {$IFDEF ENABLE_SHELLS}
+        g_Shells_Create(wx, wy, 0, -2, SHELL_BULLET);
+      {$ENDIF}
     end;
     MONSTER_IMP:
       g_Weapon_ball1(wx, wy, atx, aty, FUID);
@@ -4044,7 +4103,7 @@ begin
         ty := o^.Y+o^.Rect.Y;
         SetState(MONSTATE_SHOOT);
 
-        vilefire.Reset();
+        FVileFireTime := gTime;
 
         g_Sound_PlayExAt('SOUND_MONSTER_VILE_ATTACK', FObj.X, FObj.Y);
         g_Sound_PlayExAt('SOUND_FIRE', o^.X, o^.Y);
@@ -4107,6 +4166,7 @@ var
   i: Integer;
   b: Byte;
   anim: Boolean;
+  stub: TAnimState;
 begin
   assert(st <> nil);
 
@@ -4156,20 +4216,25 @@ begin
   // Объект монстра
   Obj_SaveState(st, @FObj);
   // Есть ли анимация огня колдуна
-  anim := (vilefire <> nil);
+  anim := FMonsterType = MONSTER_VILE;
   utils.writeBool(st, anim);
   // Если есть - сохраняем:
-  if anim then vilefire.SaveState(st, 0, False);
+  if anim then
+  begin
+    stub := TAnimState.Create(true, 2, 8);
+    stub.SaveState(st, 0, False);
+    stub.Invalidate;
+  end;
   // Анимации
   for i := ANIM_SLEEP to ANIM_PAIN do
   begin
     // Есть ли левая анимация
-    anim := (FAnim[i, TDirection.D_LEFT] <> nil);
+    anim := (FAnim[i, TDirection.D_LEFT].IsValid());
     utils.writeBool(st, anim);
     // Если есть - сохраняем
     if anim then FAnim[i, TDirection.D_LEFT].SaveState(st, 0, False);
     // Есть ли правая анимация
-    anim := (FAnim[i, TDirection.D_RIGHT] <> nil);
+    anim := (FAnim[i, TDirection.D_RIGHT].IsValid());
     utils.writeBool(st, anim);
     // Если есть - сохраняем
     if anim then FAnim[i, TDirection.D_RIGHT].SaveState(st, 0, False);
@@ -4182,6 +4247,7 @@ var
   i: Integer;
   b, alpha: Byte;
   anim, blending: Boolean;
+  stub: TAnimState;
 begin
   assert(st <> nil);
 
@@ -4240,9 +4306,11 @@ begin
   // Если есть - загружаем:
   if anim then
   begin
-    Assert(vilefire <> nil, 'TMonster.LoadState: no vilefire anim');
-    vilefire.LoadState(st, alpha, blending);
+    stub := TAnimState.Create(true, 2, 8);
+    stub.LoadState(st, alpha, blending);
+    stub.Invalidate;
   end;
+  FVileFireTime := gTime;
   // Анимации
   for i := ANIM_SLEEP to ANIM_PAIN do
   begin
@@ -4251,7 +4319,7 @@ begin
     // Если есть - загружаем
     if anim then
     begin
-      Assert(FAnim[i, TDirection.D_LEFT] <> nil, 'TMonster.LoadState: no '+IntToStr(i)+'_left anim');
+      Assert(FAnim[i, TDirection.D_LEFT].IsValid(), 'TMonster.LoadState: no '+IntToStr(i)+'_left anim');
       FAnim[i, TDirection.D_LEFT].LoadState(st, alpha, blending);
     end;
     // Есть ли правая анимация
@@ -4259,7 +4327,7 @@ begin
     // Если есть - загружаем
     if anim then
     begin
-      Assert(FAnim[i, TDirection.D_RIGHT] <> nil, 'TMonster.LoadState: no '+IntToStr(i)+'_right anim');
+      Assert(FAnim[i, TDirection.D_RIGHT].IsValid(), 'TMonster.LoadState: no '+IntToStr(i)+'_right anim');
       FAnim[i, TDirection.D_RIGHT].LoadState(st, alpha, blending);
     end;
   end;