DEADSOFTWARE

cleanup: remove g_main.pas
[d2df-sdl.git] / src / game / g_playermodel.pas
index 9b67c0033f19a42d0fff2d793356da90851090f5..b2f57e6c95997ff2835acc80f58380b4ec6827ca 100644 (file)
@@ -1,9 +1,8 @@
-(* 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, version 3 of the License ONLY.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *)
-{$MODE DELPHI}
+{$INCLUDE ../shared/a_modes.inc}
+{$M+}
 unit g_playermodel;
 
 interface
 
 uses
-  g_textures, g_basic, g_weapons, e_graphics, wadreader;
+  MAPDEF, g_textures, g_base, g_basic, g_weapons, r_graphics, utils, g_gfx,
+  ImagingTypes, Imaging, ImagingUtility;
 
 const
   A_STAND      = 0;
@@ -32,10 +33,37 @@ const
   A_ATTACKUP   = 7;
   A_ATTACKDOWN = 8;
   A_PAIN       = 9;
+  // EXTENDED
+  A_WALKATTACK     = 10;
+  A_WALKSEEUP      = 11;
+  A_WALKSEEDOWN    = 12;
+  A_WALKATTACKUP   = 13;
+  A_WALKATTACKDOWN = 14;
+  A_FISTSTAND      = 15;
+  A_FISTWALK       = 16;
+  A_FISTATTACK     = 17;
+  A_FISTWALKATTACK = 18;
+  A_FISTSEEUP      = 19;
+  A_FISTSEEDOWN    = 20;
+  A_FISTATTACKUP   = 21;
+  A_FISTATTACKDOWN = 22;
+
+  A_LASTBASE = A_PAIN;
+  A_LASTEXT  = A_FISTATTACKDOWN;
+  A_LAST     = A_LASTEXT;
 
   MODELSOUND_PAIN = 0;
   MODELSOUND_DIE  = 1;
 
+  W_POS_NORMAL = 0;
+  W_POS_UP     = 1;
+  W_POS_DOWN   = 2;
+
+  W_ACT_NORMAL = 0;
+  W_ACT_FIRE   = 1;
+
+  FLAG_BASEPOINT: TDFPoint = (X:16; Y:43);
+
 type
   TModelInfo = record
     Name:        String;
@@ -44,6 +72,10 @@ type
     HaveWeapon:  Boolean;
   end;
 
+  TModelBlood = record
+    R, G, B, Kind: Byte;
+  end;
+
   TModelSound = record
     ID:    DWORD;
     Level: Byte;
@@ -58,18 +90,20 @@ type
 
   TModelSoundArray = Array of TModelSound;
   TGibsArray = Array of TGibSprite;
-  TWeaponPoints = Array [WEAPON_SAW..WEAPON_SUPERPULEMET] of
-                  Array [A_STAND..A_PAIN] of
-                  Array [D_LEFT..D_RIGHT] of Array of TPoint;
+  TWeaponPoints = Array [WP_FIRST + 1..WP_LAST] of
+                  Array [A_STAND..A_LAST] of
+                  Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array of TDFPoint;
+  TModelMatrix = Array [TDirection.D_LEFT..TDirection.D_RIGHT] of Array [A_STAND..A_LAST] of TAnimation;
 
-  TPlayerModel = class (TObject)
+  TPlayerModel = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
   private
     FName:             String;
     FDirection:        TDirection;
     FColor:            TRGB;
+    FBlood:            TModelBlood;
     FCurrentAnimation: Byte;
-    FAnim:             Array [D_LEFT..D_RIGHT] of Array [A_STAND..A_PAIN] of TAnimation;
-    FMaskAnim:         Array [D_LEFT..D_RIGHT] of Array [A_STAND..A_PAIN] of TAnimation;
+    FAnim:             TModelMatrix;
+    FMaskAnim:         TModelMatrix;
     FWeaponPoints:     TWeaponPoints;
     FPainSounds:       TModelSoundArray;
     FDieSounds:        TModelSoundArray;
@@ -77,7 +111,7 @@ type
     FCurrentWeapon:    Byte;
     FDrawWeapon:       Boolean;
     FFlag:             Byte;
-    FFlagPoint:        TPoint;
+    FFlagPoint:        TDFPoint;
     FFlagAngle:        SmallInt;
     FFlagAnim:         TAnimation;
     FFire:             Boolean;
@@ -94,87 +128,85 @@ type
     procedure   SetFire(Fire: Boolean);
     function    PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
     procedure   Update();
-    procedure   Draw(X, Y: Integer; Alpha: Byte = 0);
 
+  published
     property    Fire: Boolean read FFire;
     property    Direction: TDirection read FDirection write FDirection;
     property    Animation: Byte read FCurrentAnimation;
     property    Weapon: Byte read FCurrentWeapon;
     property    Name: String read FName;
+
+  public
     property    Color: TRGB read FColor write FColor;
+    property    Blood: TModelBlood read FBlood;
+
+    property    Anim: TModelMatrix read FAnim;
+    property    MaskAnim: TModelMatrix read FMaskAnim;
+    property    CurrentAnimation: Byte read FCurrentAnimation;
+
+    property    CurrentWeapon: Byte read FCurrentWeapon;
+    property    DrawWeapon: Boolean read FDrawWeapon;
+    property    WeaponPoints: TWeaponPoints read FWeaponPoints;
+
+    property    Flag: Byte read FFlag;
+    property    FlagAnim: TAnimation read FFlagAnim;
+    property    FlagAngle: SmallInt read FFlagAngle;
+    property    FlagPoint: TDFPoint read FFlagPoint;
   end;
 
-procedure g_PlayerModel_LoadData();
 procedure g_PlayerModel_FreeData();
 function  g_PlayerModel_Load(FileName: String): Boolean;
-function  g_PlayerModel_GetNames(): SArray;
+function  g_PlayerModel_GetNames(): SSArray;
 function  g_PlayerModel_GetInfo(ModelName: String): TModelInfo;
+function  g_PlayerModel_GetBlood(ModelName: String): TModelBlood;
 function  g_PlayerModel_Get(ModelName: String): TPlayerModel;
 function  g_PlayerModel_GetAnim(ModelName: String; Anim: Byte; var _Anim, _Mask: TAnimation): Boolean;
 function  g_PlayerModel_GetGibs(ModelName: String; var Gibs: TGibsArray): Boolean;
 
+
 implementation
 
 uses
-  g_main, g_sound, g_console, SysUtils, g_player, CONFIG,
-  GL, GLExt, e_sound, g_options, g_map, Math, e_log;
+  g_sound, g_console, SysUtils, g_player, CONFIG,
+  e_sound, g_options, g_map, Math, e_log, wadreader;
 
 type
   TPlayerModelInfo = record
     Info:         TModelInfo;
     ModelSpeed:   Array [A_STAND..A_PAIN] of Byte;
-    FlagPoint:    TPoint;
+    FlagPoint:    TDFPoint;
     FlagAngle:    SmallInt;
     WeaponPoints: TWeaponPoints;
     Gibs:         TGibsArray;
     PainSounds:   TModelSoundArray;
     DieSounds:    TModelSoundArray;
     SlopSound:    Byte;
+    Blood:        TModelBlood;
   end;
 
 const
-  W_POS_NORMAL = 0;
-  W_POS_UP     = 1;
-  W_POS_DOWN   = 2;
-
-  W_ACT_NORMAL = 0;
-  W_ACT_FIRE   = 1;
-
-  FLAG_BASEPOINT: TPoint = (X:16; Y:43);
-  FLAG_DEFPOINT:  TPoint = (X:32; Y:16);
+  FLAG_DEFPOINT:  TDFPoint = (X:32; Y:16);
   FLAG_DEFANGLE = -20;
-  WEAPONBASE: Array [WEAPON_SAW..WEAPON_SUPERPULEMET] of TPoint =
+  WEAPONBASE: Array [WP_FIRST + 1..WP_LAST] of TDFPoint =
               ((X:8; Y:4), (X:8; Y:8), (X:16; Y:16), (X:16; Y:24),
-               (X:16; Y:16), (X:24; Y:24), (X:16; Y:16), (X:24; Y:24), (X:16; Y:16));
+               (X:16; Y:16), (X:24; Y:24), (X:16; Y:16), (X:24; Y:24),
+               (X:16; Y:16), (X:8; Y:8));
 
-  AnimNames: Array [A_STAND..A_PAIN] of String =
+  AnimNames: Array [A_STAND..A_LASTEXT] of String =
              ('StandAnim','WalkAnim','Die1Anim','Die2Anim','AttackAnim',
-              'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim');
-  WeapNames: Array [WEAPON_SAW..WEAPON_SUPERPULEMET] of String =
-             ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl');
+              'SeeUpAnim','SeeDownAnim','AttackUpAnim','AttackDownAnim','PainAnim',
+              // EXTENDED
+              'WalkAttackAnim', 'WalkSeeUpAnim', 'WalkSeeDownAnim',
+              'WalkAttackUpAnim', 'WalkAttackDownAnim', 'FistStandAnim', 'FistWalkAnim',
+              'FistAttackAnim', 'FistWalkAttackAnim', 'FistSeeUpAnim', 'FistSeeDownAnim',
+              'FistAttackUpAnim', 'FistAttackDownAnim');
+  WeapNames: Array [WP_FIRST + 1..WP_LAST] of String =
+             ('csaw', 'hgun', 'sg', 'ssg', 'mgun', 'rkt', 'plz', 'bfg', 'spl', 'flm');
 
 var
-  WeaponID: Array [WEAPON_SAW..WEAPON_SUPERPULEMET] of
-            Array [W_POS_NORMAL..W_POS_DOWN] of
-            Array [W_ACT_NORMAL..W_ACT_FIRE] of DWORD;
   PlayerModelsArray: Array of TPlayerModelInfo;
 
-procedure g_PlayerModel_LoadData();
-var
-  a: Integer;
-begin
-  for a := WEAPON_SAW to WEAPON_SUPERPULEMET do
-  begin
-    g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a]));
-    g_Texture_CreateWAD(WeaponID[a][W_POS_NORMAL][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_FIRE');
-    g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP');
-    g_Texture_CreateWAD(WeaponID[a][W_POS_UP][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_UP_FIRE');
-    g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_NORMAL], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN');
-    g_Texture_CreateWAD(WeaponID[a][W_POS_DOWN][W_ACT_FIRE], GameWAD+':WEAPONS\'+UpperCase(WeapNames[a])+'_DN_FIRE');
-  end;
-end;
-
-function GetPoint(var str: String; var point: TPoint): Boolean;
+function GetPoint(var str: String; var point: TDFPoint): Boolean;
 var
   a, x, y: Integer;
   s: String;
@@ -230,7 +262,7 @@ begin
     begin
       X := X - WEAPONBASE[weapon].X;
       Y := Y - WEAPONBASE[weapon].Y;
-      if dir = D_LEFT then
+      if dir = TDirection.D_LEFT then
         X := -X;
     end;
   end;
@@ -243,6 +275,109 @@ begin
   Result := True;
 end;
 
+procedure ExtAnimFromBaseAnim(MName: String; AIdx: Integer);
+const
+  CopyAnim: array [A_LASTBASE+1..A_LASTEXT] of Integer = (
+    A_WALK, A_WALK, A_WALK, A_WALK, A_WALK,
+    A_STAND, A_WALK, A_ATTACK, A_WALK, A_SEEUP, A_SEEDOWN,
+    A_ATTACKUP, A_ATTACKDOWN
+  );
+var
+  OIdx, W, I: Integer;
+  D: TDirection;
+  AName, OName: String;
+begin
+  // HACK: shitty workaround to duplicate base animations
+  //       in place of extended, replace with something better later
+
+  Assert((AIdx > A_LASTBASE) and (AIdx <= A_LASTEXT));
+  OIdx := CopyAnim[AIdx];
+
+  AName := MName + '_RIGHTANIM' + IntToStr(AIdx);
+  OName := MName + '_RIGHTANIM' + IntToStr(OIdx);
+  Assert(g_Frames_Dup(AName, OName));
+  Assert(g_Frames_Dup(AName + '_MASK', OName + '_MASK'));
+  AName := MName + '_LEFTANIM' + IntToStr(AIdx);
+  OName := MName + '_LEFTANIM' + IntToStr(OIdx);
+  if g_Frames_Exists(AName) then
+  begin
+    g_Frames_Dup(AName, OName);
+    g_Frames_Dup(AName + '_MASK', OName + '_MASK');
+  end;
+
+  with PlayerModelsArray[High(PlayerModelsArray)] do
+  begin
+    for W := WP_FIRST + 1 to WP_LAST do
+    begin
+      for D := TDirection.D_LEFT to TDirection.D_RIGHT do
+      begin
+        SetLength(WeaponPoints[W, AIdx, D], Length(WeaponPoints[W, OIdx, D]));
+        for I := 0 to High(WeaponPoints[W, AIdx, D]) do
+          WeaponPoints[W, AIdx, D, I] := WeaponPoints[W, OIdx, D, I]
+      end;
+    end;
+  end;
+end;
+
+function g_PlayerModel_CalcGibSize (pData: Pointer; dataSize, x, y, w, h: Integer): TRectWH;
+  var i, j: Integer; done: Boolean; img: TImageData;
+
+  function IsVoid (i, j: Integer): Boolean;
+  begin
+    result := Byte((PByte(img.bits) + (y+j)*img.width*4 + (x+i)*4 + 3)^) = 0
+  end;
+
+begin
+  InitImage(img);
+  assert(LoadImageFromMemory(pData, dataSize, img));
+
+  (* trace x from right to left *)
+  done := false; i := 0;
+  while not done and (i < w) do
+  begin
+    j := 0;
+    while (j < h) and IsVoid(i, j) do inc(j);
+    done := (j < h) and (IsVoid(i, j) = false);
+    result.x := i;
+    inc(i);
+  end;
+
+  (* trace y from up to down *)
+  done := false; j := 0;
+  while not done and (j < h) do
+  begin
+    i := 0;
+    while (i < w) and IsVoid(i, j) do inc(i);
+    done := (i < w) and (IsVoid(i, j) = false);
+    result.y := j;
+    inc(j);
+  end;
+  
+  (* trace x from right to left *)
+  done := false; i := w - 1;
+  while not done and (i >= 0) do
+  begin
+    j := 0;
+    while (j < h) and IsVoid(i, j) do inc(j);
+    done := (j < h) and (IsVoid(i, j) = false);
+    result.width := i - result.x + 1;
+    dec(i);
+  end;
+
+  (* trace y from down to up *)
+  done := false; j := h - 1;
+  while not done and (j >= 0) do
+  begin
+    i := 0;
+    while (i < w) and IsVoid(i, j) do inc(i);
+    done := (i < w) and (IsVoid(i, j) = false);
+    result.height := j - result.y + 1;
+    dec(j);
+  end;
+
+  FreeImage(img);
+end;
+
 function g_PlayerModel_Load(FileName: string): Boolean;
 var
   ID: DWORD;
@@ -251,11 +386,11 @@ var
   config: TConfig;
   pData, pData2: Pointer;
   WAD: TWADFile;
-  s: string;
+  s, aname: string;
   prefix: string;
-  ok: Boolean;
+  ok, chk: Boolean;
 begin
-  e_WriteLog(Format('Loading player model: %s', [ExtractFileName(FileName)]), MSG_NOTIFY);
+  e_WriteLog(Format('Loading player model "%s"...', [FileName]), TMsgType.Notify);
 
   Result := False;
 
@@ -297,25 +432,49 @@ begin
     Description := config.ReadStr('Model', 'description', '');
   end;
 
-  for b := A_STAND to A_PAIN do
+  with PlayerModelsArray[ID] do
+  begin
+    Blood.R := MAX(0, MIN(255, config.ReadInt('Blood', 'R', 150)));
+    Blood.G := MAX(0, MIN(255, config.ReadInt('Blood', 'G', 0)));
+    Blood.B := MAX(0, MIN(255, config.ReadInt('Blood', 'B', 0)));
+    case config.ReadStr('Blood', 'Kind', 'NORMAL') of
+      'NORMAL': Blood.Kind := BLOOD_NORMAL;
+      'SPARKS': Blood.Kind := BLOOD_CSPARKS;
+      'COMBINE': Blood.Kind := BLOOD_COMBINE;
+    else
+      Blood.Kind := BLOOD_NORMAL
+    end
+  end;
+
+  for b := A_STAND to A_LAST do
   begin
-    if not (g_Frames_CreateWAD(nil, s+'_RIGHTANIM'+IntToStr(b),
+    aname := s+'_RIGHTANIM'+IntToStr(b);
+    //e_LogWritefln('### MODEL FILE: [%s]', [prefix+config.ReadStr(AnimNames[b], 'resource', '')]);
+    if not (g_Frames_CreateWAD(nil, aname,
                                prefix+config.ReadStr(AnimNames[b], 'resource', ''),
                                64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
                                config.ReadBool(AnimNames[b], 'backanim', False)) and
-            g_Frames_CreateWAD(nil, s+'_RIGHTANIM'+IntToStr(b)+'_MASK',
+            g_Frames_CreateWAD(nil, aname+'_MASK',
                                prefix+config.ReadStr(AnimNames[b], 'mask', ''),
                                64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
                                config.ReadBool(AnimNames[b], 'backanim', False))) then
     begin
-      config.Free();
-      WAD.Free();
-      Exit;
+      if b <= A_LASTBASE then
+      begin
+        config.Free();
+        WAD.Free();
+        Exit;
+      end
+      else
+      begin
+        ExtAnimFromBaseAnim(s, b);
+        continue;
+      end;
     end;
 
-    for aa := WEAPON_SAW to WEAPON_SUPERPULEMET do
-      for bb := A_STAND to A_PAIN do
-        for cc := D_LEFT to D_RIGHT do
+    for aa := WP_FIRST + 1 to WP_LAST do
+      for bb := A_STAND to A_LAST do
+        for cc := TDirection.D_LEFT to TDirection.D_RIGHT do
         begin
           f := config.ReadInt(AnimNames[bb], 'frames', 1);
           if config.ReadBool(AnimNames[bb], 'backanim', False) then
@@ -326,12 +485,13 @@ begin
     if (config.ReadStr(AnimNames[b], 'resource2', '') <> '') and
        (config.ReadStr(AnimNames[b], 'mask2', '') <> '') then
     begin
-      g_Frames_CreateWAD(nil, s+'_LEFTANIM'+IntToStr(b),
+      aname := s+'_LEFTANIM'+IntToStr(b);
+      g_Frames_CreateWAD(nil, aname,
                          prefix+config.ReadStr(AnimNames[b], 'resource2', ''),
                          64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
                          config.ReadBool(AnimNames[b], 'backanim', False));
 
-      g_Frames_CreateWAD(nil, s+'_LEFTANIM'+IntToStr(b)+'_MASK',
+      g_Frames_CreateWAD(nil, aname+'_MASK',
                          prefix+config.ReadStr(AnimNames[b], 'mask2', ''),
                          64, 64, config.ReadInt(AnimNames[b], 'frames', 1),
                          config.ReadBool(AnimNames[b], 'backanim', False));
@@ -380,7 +540,8 @@ begin
         if e_CreateTextureMemEx(pData, lenpd, Gibs[a].ID, a*32, 0, 32, 32) and
           e_CreateTextureMemEx(pData2, lenpd2, Gibs[a].MaskID, a*32, 0, 32, 32) then
         begin
-          Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
+          //Gibs[a].Rect := e_GetTextureSize2(Gibs[a].ID);
+          Gibs[a].Rect := g_PlayerModel_CalcGibSize(pData, lenpd, a*32, 0, 32, 32);
           with Gibs[a].Rect do
             if Height > 3 then Height := Height-1-Random(2);
           Gibs[a].OnlyOne := config.ReadInt('Gibs', 'once', -1) = a+1;
@@ -391,23 +552,73 @@ begin
     end;
 
     ok := True;
-    for aa := WEAPON_SAW to WEAPON_SUPERPULEMET do
-      for bb := A_STAND to A_PAIN do
+    for aa := WP_FIRST + 1 to WP_LAST do
+      for bb := A_STAND to A_LAST do
         if not (bb in [A_DIE1, A_DIE2, A_PAIN]) then
         begin
-          ok := ok and GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'_points', ''), aa, bb, D_RIGHT,
-                                     config.ReadInt(AnimNames[bb], 'frames', 0),
-                                     config.ReadBool(AnimNames[bb], 'backanim', False),
-                                     WeaponPoints);
+          chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'_points', ''), aa, bb, TDirection.D_RIGHT,
+                               config.ReadInt(AnimNames[bb], 'frames', 0),
+                               config.ReadBool(AnimNames[bb], 'backanim', False),
+                               WeaponPoints);
+          if ok and (not chk) and (aa = WEAPON_FLAMETHROWER) then
+          begin
+            // workaround for flamethrower
+            chk := GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[WEAPON_PLASMA]+'_points', ''), aa, bb, TDirection.D_RIGHT,
+                                 config.ReadInt(AnimNames[bb], 'frames', 0),
+                                 config.ReadBool(AnimNames[bb], 'backanim', False),
+                                 WeaponPoints);
+            if chk then
+            for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
+            begin
+              case bb of
+                A_STAND, A_PAIN:
+                begin
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
+                end;
+                A_WALKATTACK, A_WALK:
+                begin
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 9);
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 9);
+                end;
+                A_ATTACK:
+                begin
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 8);
+                end;
+                A_WALKSEEUP, A_SEEUP:
+                begin
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
+                end;
+                A_WALKSEEDOWN, A_SEEDOWN:
+                begin
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 5);
+                end;
+                A_WALKATTACKUP, A_ATTACKUP:
+                begin
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 5);
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 16);
+                end;
+                A_WALKATTACKDOWN, A_ATTACKDOWN:
+                begin
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X, 6);
+                  Dec(WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y, 4);
+                end;
+              end;
+            end;
+          end;
+          ok := ok and (chk or (bb > A_LASTBASE));
 
-          if not GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'2_points', ''), aa, bb, D_LEFT,
+          if not GetWeapPoints(config.ReadStr(AnimNames[bb], WeapNames[aa]+'2_points', ''), aa, bb, TDirection.D_LEFT,
                                config.ReadInt(AnimNames[bb], 'frames', 0),
                                config.ReadBool(AnimNames[bb], 'backanim', False),
                                WeaponPoints) then
-            for f := 0 to High(WeaponPoints[aa, bb, D_RIGHT]) do
+            for f := 0 to High(WeaponPoints[aa, bb, TDirection.D_RIGHT]) do
             begin
-              WeaponPoints[aa, bb, D_LEFT, f].X := -WeaponPoints[aa, bb, D_RIGHT, f].X;
-              WeaponPoints[aa, bb, D_LEFT, f].Y := WeaponPoints[aa, bb, D_RIGHT, f].Y;
+              WeaponPoints[aa, bb, TDirection.D_LEFT, f].X := -WeaponPoints[aa, bb, TDirection.D_RIGHT, f].X;
+              WeaponPoints[aa, bb, TDirection.D_LEFT, f].Y := WeaponPoints[aa, bb, TDirection.D_RIGHT, f].Y;
             end;
 
           if not ok then Break;
@@ -446,44 +657,44 @@ begin
       with PlayerModelsArray[a] do
       begin
         Result.FName := Info.Name;
+        Result.FBlood := Blood;
 
-        for b := A_STAND to A_PAIN do
+        for b := A_STAND to A_LAST do
         begin
           if not (g_Frames_Get(ID, Info.Name+'_RIGHTANIM'+IntToStr(b)) and
                   g_Frames_Get(ID2, Info.Name+'_RIGHTANIM'+IntToStr(b)+'_MASK')) then
           begin
             Result.Free();
             Result := nil;
-          Exit;
-        end;
+            Exit;
+          end;
 
-        Result.FAnim[D_RIGHT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
+          Result.FAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
 
-        Result.FMaskAnim[D_RIGHT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
+          Result.FMaskAnim[TDirection.D_RIGHT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
 
-        if g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)) and
-           g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
-        if g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(b)) and
-           g_Frames_Get(ID2, Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
-        begin
-          Result.FAnim[D_LEFT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
+          if g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)) and
+             g_Frames_Exists(Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
+          if g_Frames_Get(ID, Info.Name+'_LEFTANIM'+IntToStr(b)) and
+             g_Frames_Get(ID2, Info.Name+'_LEFTANIM'+IntToStr(b)+'_MASK') then
+          begin
+            Result.FAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID, b in [A_STAND, A_WALK], ModelSpeed[b]);
 
-          Result.FMaskAnim[D_LEFT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
+            Result.FMaskAnim[TDirection.D_LEFT][b] := TAnimation.Create(ID2, b in [A_STAND, A_WALK], ModelSpeed[b]);
+          end;
         end;
 
         Result.FPainSounds := PainSounds;
         Result.FDieSounds := DieSounds;
         Result.FSlopSound := SlopSound;
-      end;
-
-      Result.FDrawWeapon := Info.HaveWeapon;
-      Result.FWeaponPoints := WeaponPoints;
+        Result.FDrawWeapon := Info.HaveWeapon;
+        Result.FWeaponPoints := WeaponPoints;
 
-      Result.FFlagPoint := FlagPoint;
-      Result.FFlagAngle := FlagAngle;
+        Result.FFlagPoint := FlagPoint;
+        Result.FFlagAngle := FlagAngle;
 
-      Break;
-    end;
+        Break;
+      end;
   end;
 end;
 
@@ -559,7 +770,7 @@ begin
     end;
 end;
 
-function g_PlayerModel_GetNames(): SArray;
+function g_PlayerModel_GetNames(): SSArray;
 var
   i: DWORD;
 begin
@@ -589,24 +800,37 @@ begin
     end;
 end;
 
+function g_PlayerModel_GetBlood(ModelName: string): TModelBlood;
+var
+  a: Integer;
+begin
+  Result.R := 150;
+  Result.G := 0;
+  Result.B := 0;
+  Result.Kind := BLOOD_NORMAL;
+  if PlayerModelsArray = nil then Exit;
+
+  for a := 0 to High(PlayerModelsArray) do
+    if PlayerModelsArray[a].Info.Name = ModelName then
+    begin
+      Result := PlayerModelsArray[a].Blood;
+      Break;
+    end;
+end;
+
 procedure g_PlayerModel_FreeData();
 var
   i: DWORD;
-  a, b, c: Integer;
+  a, b: Integer;
 begin
-  for a := WEAPON_SAW to WEAPON_SUPERPULEMET do
-    for b := W_POS_NORMAL to W_POS_DOWN do
-      for c := W_ACT_NORMAL to W_ACT_FIRE do
-        e_DeleteTexture(WeaponID[a][b][c]);
-
-  e_WriteLog('Releasing models...', MSG_NOTIFY);
+  e_WriteLog('Releasing models...', TMsgType.Notify);
 
   if PlayerModelsArray = nil then Exit;
 
   for i := 0 to High(PlayerModelsArray) do
     with PlayerModelsArray[i] do
     begin
-      for a := A_STAND to A_PAIN do
+      for a := A_STAND to A_LAST do
       begin
         g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a));
         g_Frames_DeleteByName(Info.Name+'_LEFTANIM'+IntToStr(a)+'_MASK');
@@ -641,17 +865,17 @@ begin
 
   FCurrentAnimation := Animation;
 
-  if (FDirection = D_LEFT) and
-     (FAnim[D_LEFT][FCurrentAnimation] <> nil) and
-     (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
+  if (FDirection = TDirection.D_LEFT) and
+     (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) and
+     (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
   begin
-    FAnim[D_LEFT][FCurrentAnimation].Reset;
-    FMaskAnim[D_LEFT][FCurrentAnimation].Reset;
+    FAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
+    FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Reset;
   end
   else
   begin
-    FAnim[D_RIGHT][FCurrentAnimation].Reset;
-    FMaskAnim[D_RIGHT][FCurrentAnimation].Reset;
+    FAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
+    FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Reset;
   end;
 end;
 
@@ -659,122 +883,31 @@ destructor TPlayerModel.Destroy();
 var
   a: Byte;
 begin
-  for a := A_STAND to A_PAIN do
+  for a := A_STAND to A_LAST do
   begin
-    FAnim[D_LEFT][a].Free();
-    FMaskAnim[D_LEFT][a].Free();
-    FAnim[D_RIGHT][a].Free();
-    FMaskAnim[D_RIGHT][a].Free();
+    FAnim[TDirection.D_LEFT][a].Free();
+    FMaskAnim[TDirection.D_LEFT][a].Free();
+    FAnim[TDirection.D_RIGHT][a].Free();
+    FMaskAnim[TDirection.D_RIGHT][a].Free();
   end;
 
   inherited;
 end;
 
-procedure TPlayerModel.Draw(X, Y: Integer; Alpha: Byte = 0);
-var
-  Mirror: TMirrorType;
-  pos, act: Byte;
-  p: TPoint;
-begin
-// Ôëàãè:
-  if Direction = D_LEFT then
-    Mirror := M_NONE
-  else
-    Mirror := M_HORIZONTAL;
-
-  if (FFlag <> FLAG_NONE) and (FFlagAnim <> nil) and
-     (not (FCurrentAnimation in [A_DIE1, A_DIE2])) then
-  begin
-    p.X := IfThen(Direction = D_LEFT,
-                  FLAG_BASEPOINT.X,
-                  64-FLAG_BASEPOINT.X);
-    p.Y := FLAG_BASEPOINT.Y;
-
-    FFlagAnim.DrawEx(X+IfThen(Direction = D_LEFT, FFlagPoint.X-1, 2*FLAG_BASEPOINT.X-FFlagPoint.X+1)-FLAG_BASEPOINT.X,
-                     Y+FFlagPoint.Y-FLAG_BASEPOINT.Y+1, Mirror, p,
-                     IfThen(FDirection = D_RIGHT, FFlagAngle, -FFlagAngle));
-  end;
-
-// Îðóæèå:
-  if Direction = D_RIGHT then
-    Mirror := M_NONE
-  else
-    Mirror := M_HORIZONTAL;
-
-  if FDrawWeapon and
-    (not (FCurrentAnimation in [A_DIE1, A_DIE2, A_PAIN])) and
-    (FCurrentWeapon in [WEAPON_SAW..WEAPON_SUPERPULEMET]) then
-  begin
-    if FCurrentAnimation in [A_SEEUP, A_ATTACKUP] then
-      pos := W_POS_UP
-    else
-      if FCurrentAnimation in [A_SEEDOWN, A_ATTACKDOWN] then
-        pos := W_POS_DOWN
-      else
-        pos := W_POS_NORMAL;
-
-    if (FCurrentAnimation in [A_ATTACK, A_ATTACKUP, A_ATTACKDOWN]) or
-       FFire then
-      act := W_ACT_FIRE
-    else
-      act := W_ACT_NORMAL;
-
-    if Alpha < 201 then
-      e_Draw(WeaponID[FCurrentWeapon][pos][act],
-             X+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
-                             FAnim[D_RIGHT][FCurrentAnimation].CurrentFrame].X,
-             Y+FWeaponPoints[FCurrentWeapon, FCurrentAnimation, FDirection,
-                             FAnim[D_RIGHT][FCurrentAnimation].CurrentFrame].Y,
-             0, True, False, Mirror);
-  end;
-
-// Ìîäåëü:
-  if (FDirection = D_LEFT) and
-     (FAnim[D_LEFT][FCurrentAnimation] <> nil) then
-  begin
-    FAnim[D_LEFT][FCurrentAnimation].Alpha := Alpha;
-    FAnim[D_LEFT][FCurrentAnimation].Draw(X, Y, M_NONE);
-  end
-  else
-  begin
-    FAnim[D_RIGHT][FCurrentAnimation].Alpha := Alpha;
-    FAnim[D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
-  end;
-
-// Ìàñêà ìîäåëè:
-  e_Colors := FColor;
-
-  if (FDirection = D_LEFT) and
-     (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
-  begin
-    FMaskAnim[D_LEFT][FCurrentAnimation].Alpha := Alpha;
-    FMaskAnim[D_LEFT][FCurrentAnimation].Draw(X, Y, M_NONE);
-  end
-  else
-  begin
-    FMaskAnim[D_RIGHT][FCurrentAnimation].Alpha := Alpha;
-    FMaskAnim[D_RIGHT][FCurrentAnimation].Draw(X, Y, Mirror);
-  end;
-
-  e_Colors.R := 255;
-  e_Colors.G := 255;
-  e_Colors.B := 255;
-end;
-
 function TPlayerModel.GetCurrentAnimation: TAnimation;
 begin
-  if (FDirection = D_LEFT) and (FAnim[D_LEFT][FCurrentAnimation] <> nil) then
-    Result := FAnim[D_LEFT][FCurrentAnimation]
+  if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
+    Result := FAnim[TDirection.D_LEFT][FCurrentAnimation]
   else
-    Result := FAnim[D_RIGHT][FCurrentAnimation];
+    Result := FAnim[TDirection.D_RIGHT][FCurrentAnimation];
 end;
 
 function TPlayerModel.GetCurrentAnimationMask: TAnimation;
 begin
-  if (FDirection = D_LEFT) and (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
-    Result := FMaskAnim[D_LEFT][FCurrentAnimation]
+  if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
+    Result := FMaskAnim[TDirection.D_LEFT][FCurrentAnimation]
   else
-    Result := FMaskAnim[D_RIGHT][FCurrentAnimation];
+    Result := FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation];
 end;
 
 function TPlayerModel.PlaySound(SoundType, Level: Byte; X, Y: Integer): Boolean;
@@ -798,7 +931,7 @@ begin
   end
   else
   begin
-    if (Level in [2, 3]) and (FSlopSound > 0) then
+    if (Level in [2, 3, 5]) and (FSlopSound > 0) then
     begin
       g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
       if FSlopSound = 1 then
@@ -815,6 +948,12 @@ begin
         SetLength(TempArray, Length(TempArray)+1);
         TempArray[High(TempArray)] := FDieSounds[a].ID;
       end;
+    if (TempArray = nil) and (Level = 5) then
+    begin
+      g_Sound_PlayExAt('SOUND_MONSTER_SLOP', X, Y);
+      Result := True;
+      Exit;
+    end;
   end;
 
   if TempArray = nil then Exit;
@@ -835,7 +974,7 @@ procedure TPlayerModel.SetFire(Fire: Boolean);
 begin
   FFire := Fire;
 
-  if FFire then FFireCounter := FAnim[D_RIGHT, A_ATTACK].Speed*FAnim[D_RIGHT, A_ATTACK].TotalFrames
+  if FFire then FFireCounter := FAnim[TDirection.D_RIGHT, A_ATTACK].Speed*FAnim[TDirection.D_RIGHT, A_ATTACK].TotalFrames
   else FFireCounter := 0;
 end;
 
@@ -864,11 +1003,11 @@ end;
 
 procedure TPlayerModel.Update();
 begin
-  if (FDirection = D_LEFT) and (FAnim[D_LEFT][FCurrentAnimation] <> nil) then
-    FAnim[D_LEFT][FCurrentAnimation].Update else FAnim[D_RIGHT][FCurrentAnimation].Update;
+  if (FDirection = TDirection.D_LEFT) and (FAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
+    FAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
 
-  if (FDirection = D_LEFT) and (FMaskAnim[D_LEFT][FCurrentAnimation] <> nil) then
-    FMaskAnim[D_LEFT][FCurrentAnimation].Update else FMaskAnim[D_RIGHT][FCurrentAnimation].Update;
+  if (FDirection = TDirection.D_LEFT) and (FMaskAnim[TDirection.D_LEFT][FCurrentAnimation] <> nil) then
+    FMaskAnim[TDirection.D_LEFT][FCurrentAnimation].Update else FMaskAnim[TDirection.D_RIGHT][FCurrentAnimation].Update;
 
   if FFlagAnim <> nil then FFlagAnim.Update;