DEADSOFTWARE

Revert "fixed flawed "weapon switch key release" detection logic (flags should change...
[d2df-sdl.git] / src / game / g_player.pas
index a088f2851d2b03c2c3e702f2b83feeb0d6e99cd1..fa2dcfce5243696c55b09cc25aee27d9c44229f9 100644 (file)
@@ -163,7 +163,7 @@ type
     FSecrets:   Integer;
     FCurrWeap:  Byte;
     FNextWeap:  WORD;
-    FNextWeapDelay: Byte; // frames
+    FNextWeapDelay: Byte; // frames (unused)
     FBFGFireCounter: SmallInt;
     FLastSpawnerUID: Word;
     FLastHit:   Byte;
@@ -200,6 +200,9 @@ type
     FNetTime: LongWord;
     mEDamageType: Integer;
 
+    // client-side only
+    weaponSwitchKeyReleased: array[0..16] of Boolean; // true: was release
+
 
     function CollideLevel(XInc, YInc: Integer): Boolean;
     function StayOnStep(XInc, YInc: Integer): Boolean;
@@ -231,6 +234,8 @@ type
 
     procedure doDamage (v: Integer);
 
+    function followCorpse(): Boolean;
+
   public
     FDamageBuffer:   Integer;
 
@@ -331,6 +336,10 @@ type
     procedure getMapBox (out x, y, w, h: Integer); inline;
     procedure moveBy (dx, dy: Integer); inline;
 
+    procedure releaseAllWeaponSwitchKeys ();
+    procedure weaponSwitchKeysStateChange (index: Integer; pressed: Boolean);
+    function isWeaponSwitchKeyReleased (index: Integer): Boolean;
+
   public
     property    Vel: TPoint2i read FObj.Vel;
     property    Obj: TObj read FObj;
@@ -463,7 +472,7 @@ type
 
   PGib = ^TGib;
   TGib = record
-    alive:     Boolean;
+    alive:    Boolean;
     ID:       DWORD;
     MaskID:   DWORD;
     RAngle:   Integer;
@@ -501,6 +510,7 @@ type
     FDamage:        Byte;
     FColor:         TRGB;
     FObj:           TObj;
+    FPlayerUID:     Word;
     FAnimation:     TAnimation;
     FAnimationMask: TAnimation;
 
@@ -588,10 +598,14 @@ procedure g_Bot_RemoveAll();
 implementation
 
 uses
+{$INCLUDE ../nogl/noGLuses.inc}
+{$IFDEF ENABLE_HOLMES}
+  g_holmes,
+{$ENDIF}
   e_log, g_map, g_items, g_console, g_gfx, Math,
   g_options, g_triggers, g_menu, g_game, g_grid,
   wadreader, g_main, g_monsters, CONFIG, g_language,
-  g_net, g_netmsg, g_window, GL, g_holmes,
+  g_net, g_netmsg, g_window,
   utils, xstreams;
 
 const PLR_SAVE_VERSION = 0;
@@ -1505,11 +1519,20 @@ end;
 
 procedure g_Player_CreateCorpse(Player: TPlayer);
 var
+  i: Integer;
   find_id: DWORD;
   ok: Boolean;
 begin
   if Player.alive then
     Exit;
+
+// Ðàçðûâàåì ñâÿçü ñ ïðåæíèì òðóïîì:
+  if gCorpses <> nil then
+    for i := 0 to High(gCorpses) do
+      if gCorpses[i] <> nil then
+        if gCorpses[i].FPlayerUID = Player.FUID then
+          gCorpses[i].FPlayerUID := 0;
+
   if Player.FObj.Y >= gMapInfo.Height+128 then
     Exit;
 
@@ -1535,6 +1558,7 @@ begin
         gCorpses[find_id].FColor := FModel.Color;
         gCorpses[find_id].FObj.Vel := FObj.Vel;
         gCorpses[find_id].FObj.Accel := FObj.Accel;
+        gCorpses[find_id].FPlayerUID := FUID;
       end
     else
       g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX,
@@ -1594,11 +1618,13 @@ procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: string; fColor: TRGB);
 var
   a: Integer;
   GibsArray: TGibsArray;
+  Blood: TModelBlood;
 begin
   if (gGibs = nil) or (Length(gGibs) = 0) then
     Exit;
   if not g_PlayerModel_GetGibs(ModelName, GibsArray) then
     Exit;
+  Blood := g_PlayerModel_GetBlood(ModelName);
 
   for a := 0 to High(GibsArray) do
     with gGibs[CurrentGib] do
@@ -1617,7 +1643,7 @@ begin
 
       if gBloodCount > 0 then
         g_GFX_Blood(fX, fY, 16*gBloodCount+Random(5*gBloodCount), -16+Random(33), -16+Random(33),
-                    Random(48), Random(48), 150, 0, 0);
+                    Random(48), Random(48), Blood.R, Blood.G, Blood.B, Blood.Kind);
 
       if CurrentGib >= High(gGibs) then
         CurrentGib := 0
@@ -2109,8 +2135,38 @@ begin
   FNetTime := 0;
 
   resetWeaponQueue();
+  releaseAllWeaponSwitchKeys();
+end;
+
+
+procedure TPlayer.releaseAllWeaponSwitchKeys ();
+var
+  f: Integer;
+begin
+  for f := 0 to High(weaponSwitchKeyReleased) do weaponSwitchKeyReleased[f] := true;
+end;
+
+procedure TPlayer.weaponSwitchKeysStateChange (index: Integer; pressed: Boolean);
+begin
+  Inc(index, 2); // -2: prev; -1: next
+  if (index < 0) or (index > High(weaponSwitchKeyReleased)) then exit;
+  weaponSwitchKeyReleased[index] := not pressed;
+end;
+
+function TPlayer.isWeaponSwitchKeyReleased (index: Integer): Boolean;
+begin
+  Inc(index, 2); // -2: prev; -1: next
+  if (index < 0) or (index > High(weaponSwitchKeyReleased)) then
+  begin
+    result := true;
+  end
+  else
+  begin
+    result := weaponSwitchKeyReleased[index];
+  end;
 end;
 
+
 procedure TPlayer.positionChanged (); inline;
 begin
 end;
@@ -2350,8 +2406,15 @@ begin
       Mirror := TMirrorType.Horizontal;
 
     if FPunchAnim <> nil then
+    begin
       FPunchAnim.Draw(FObj.X+IfThen(Direction = TDirection.D_LEFT, 15-FObj.Rect.X, FObj.Rect.X-15),
                       FObj.Y+FObj.Rect.Y-11, Mirror);
+      if FPunchAnim.played then
+      begin
+        FPunchAnim.Free;
+        FPunchAnim := nil;
+      end;
+    end;
 
     if (FMegaRulez[MR_INVUL] > gTime) and (gPlayerDrawn <> Self) then
       if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
@@ -2409,10 +2472,13 @@ procedure TPlayer.DrawAim();
   var
     ex, ey: Integer;
   begin
+
+{$IFDEF ENABLE_HOLMES}
     if isValidViewPort and (self = gPlayer1) then
     begin
       g_Holmes_plrLaser(ax0, ay0, ax1, ay1);
     end;
+{$ENDIF}
 
     e_DrawLine(sz, ax0, ay0, ax1, ay1, 255, 0, 0, 96);
     if (g_Map_traceToNearestWall(ax0, ay0, ax1, ay1, @ex, @ey) <> nil) then
@@ -2790,12 +2856,22 @@ end;
 procedure TPlayer.DoPunch();
 var
   id: DWORD;
+  st: String;
 begin
-  if FPunchAnim = nil then begin
-    g_Frames_Get(id, 'FRAMES_PUNCH');
-    FPunchAnim := TAnimation.Create(id, False, 1);
-  end else
+  if FPunchAnim <> nil then begin
     FPunchAnim.reset();
+    FPunchAnim.Free;
+    FPunchAnim := nil;
+  end;
+  st := 'FRAMES_PUNCH';
+  if R_BERSERK in FRulez then
+    st := st + '_BERSERK';
+  if FKeys[KEY_UP].Pressed then
+    st := st + '_UP'
+  else if FKeys[KEY_DOWN].Pressed then
+    st := st + '_DN';
+  g_Frames_Get(id, st);
+  FPunchAnim := TAnimation.Create(id, False, 1);
 end;
 
 procedure TPlayer.Fire();
@@ -2827,6 +2903,7 @@ begin
   case FCurrWeap of
     WEAPON_KASTET:
     begin
+      DoPunch();
       if R_BERSERK in FRulez then
       begin
         //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
@@ -2841,8 +2918,6 @@ begin
         locobj.Accel.X := xd-wx;
         locobj.Accel.y := yd-wy;
 
-        DoPunch();
-
         if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
           g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
         else
@@ -3482,11 +3557,11 @@ begin
   g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)+8,
               FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
               Count div 2, 3, -1, 16, (PLAYER_RECT.Height*2 div 3),
-              150, 0, 0);
+              FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
   g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2)-8,
               FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
               Count div 2, -3, -1, 16, (PLAYER_RECT.Height*2) div 3,
-              150, 0, 0);
+              FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
 end;
 
 procedure TPlayer.MakeBloodVector(Count: Word; VelX, VelY: Integer);
@@ -3494,7 +3569,7 @@ begin
   g_GFX_Blood(FObj.X+PLAYER_RECT.X+(PLAYER_RECT.Width div 2),
               FObj.Y+PLAYER_RECT.Y+(PLAYER_RECT.Height div 2),
               Count, VelX, VelY, 16, (PLAYER_RECT.Height*2) div 3,
-              150, 0, 0);
+              FModel.Blood.R, FModel.Blood.G, FModel.Blood.B, FModel.Blood.Kind);
 end;
 
 procedure TPlayer.QueueWeaponSwitch(Weapon: Byte);
@@ -3515,8 +3590,8 @@ begin
   result := false;
   case weapon of
     WEAPON_KASTET, WEAPON_SAW: result := true;
-    WEAPON_SHOTGUN1, WEAPON_SHOTGUN2: result := (FAmmo[A_SHELLS] > 0);
-    WEAPON_PISTOL, WEAPON_CHAINGUN, WEAPON_SUPERPULEMET: result := (FAmmo[A_BULLETS] > 0);
+    WEAPON_SHOTGUN1, WEAPON_SHOTGUN2, WEAPON_SUPERPULEMET: result := (FAmmo[A_SHELLS] > 0);
+    WEAPON_PISTOL, WEAPON_CHAINGUN: result := (FAmmo[A_BULLETS] > 0);
     WEAPON_ROCKETLAUNCHER: result := (FAmmo[A_ROCKETS] > 0);
     WEAPON_PLASMA, WEAPON_BFG: result := (FAmmo[A_CELLS] > 0);
     WEAPON_FLAMETHROWER: result := (FAmmo[A_FUEL] > 0);
@@ -3529,83 +3604,170 @@ function TPlayer.getNextWeaponIndex (): Byte;
 var
   i: Word;
   wantThisWeapon: array[0..64] of Boolean;
-  wwc: Integer = 0; //HACK!
-  dir, cwi: Integer;
+  weaponOrder: array[0..16] of Integer; // value: index in `FWeapon`
+  wwc: Integer;
+  f, dir, cwi, rwidx, curlidx: Integer;
+
+  function real2log (ridx: Integer): Integer;
+  var
+    f: Integer;
+  begin
+    if (ridx >= 0) then
+    begin
+      for f := 0 to High(weaponOrder) do if (weaponOrder[f] = ridx) then begin result := f; exit; end;
+    end;
+    result := -1;
+  end;
+
 begin
   result := 255; // default result: "no switch"
+
   // had weapon cycling on previous frame? remove that flag
   if (FNextWeap and $2000) <> 0 then
   begin
     FNextWeap := FNextWeap and $1FFF;
     FNextWeapDelay := 0;
   end;
+
+  for f := 0 to High(weaponOrder) do weaponOrder[f] := -1;
+
+  // build weapon order (k8: i know, i know, learn how to do constants and such... gtfo, please!)
+  // two knuckles are for "normal" and "berserk" (see `if` below -- it removes the one that is not needed)
+  // priorities:
+  //   bfg, launcher, plasma, flamethrower, ssg, minigun, sg, pistol, berserk, chainsaw, fist
+  weaponOrder[0] := WEAPON_SUPERPULEMET;
+  weaponOrder[1] := WEAPON_BFG;
+  weaponOrder[2] := WEAPON_ROCKETLAUNCHER;
+  weaponOrder[3] := WEAPON_PLASMA;
+  weaponOrder[4] := WEAPON_FLAMETHROWER;
+  weaponOrder[5] := WEAPON_SHOTGUN2;
+  weaponOrder[6] := WEAPON_CHAINGUN;
+  weaponOrder[7] := WEAPON_SHOTGUN1;
+  weaponOrder[8] := WEAPON_PISTOL;
+  weaponOrder[9] := WEAPON_KASTET+666; // berserk fist
+  weaponOrder[10] := WEAPON_SAW;
+  weaponOrder[11] := WEAPON_KASTET; // normal fist
+
+  for f := 0 to High(weaponOrder) do
+  begin
+    if (weaponOrder[f] = WEAPON_KASTET) then
+    begin
+      // normal fist: remove if we have a berserk pack
+      if (R_BERSERK in FRulez) then weaponOrder[f] := -1;
+    end
+    else
+    if (weaponOrder[f] = WEAPON_KASTET+666) then
+    begin
+      // berserk fist: remove if we don't have a berserk pack
+      if (R_BERSERK in FRulez) then weaponOrder[f] := WEAPON_KASTET else weaponOrder[f] := -1;
+    end;
+  end;
+
+  (*
+  WEAPON_KASTET         = 0;
+  WEAPON_SAW            = 1;
+  WEAPON_PISTOL         = 2;
+  WEAPON_SHOTGUN1       = 3;
+  WEAPON_SHOTGUN2       = 4;
+  WEAPON_CHAINGUN       = 5;
+  WEAPON_ROCKETLAUNCHER = 6;
+  WEAPON_PLASMA         = 7;
+  WEAPON_BFG            = 8;
+  WEAPON_SUPERPULEMET   = 9;
+  WEAPON_FLAMETHROWER   = 10;
+  *)
+
   // cycling has priority
   if (FNextWeap and $C000) <> 0 then
   begin
-    if (FNextWeap and $8000) <> 0 then
-      dir := 1
-    else
-      dir := -1;
+    if (FNextWeap and $8000) <> 0 then dir := 1 else dir := -1; // should be reversed if we want "priority-driven cycling"
     FNextWeap := FNextWeap or $2000; // we need this
-    if FNextWeapDelay > 0 then
-      exit; // cooldown time
+    if FNextWeapDelay > 0 then exit; // cooldown time
+    //cwi := real2log(FCurrWeap);
+    //if (cwi < 0) then cwi := 0;
     cwi := FCurrWeap;
     for i := 0 to High(FWeapon) do
     begin
       cwi := (cwi+length(FWeapon)+dir) mod length(FWeapon);
-      if FWeapon[cwi] then
-      begin
-        //e_WriteLog(Format(' SWITCH: cur=%d; new=%d', [FCurrWeap, cwi]), MSG_WARNING);
-        result := Byte(cwi);
-        FNextWeapDelay := 10;
+      //rwidx := weaponOrder[cwi];
+      rwidx := cwi; // sorry
+      if (rwidx < 0) then continue;
+      if FWeapon[rwidx] then
+      begin
+        //e_WriteLog(Format(' SWITCH: cur=%d; new=%d (dir=%d; log=%d)', [FCurrWeap, rwidx, dir, cwi]), TMsgType.Warning);
+        result := Byte(rwidx);
+        //FNextWeapDelay := 10; //k8: not needed anymore
         exit;
       end;
     end;
     resetWeaponQueue();
     exit;
   end;
+
   // no cycling
-  for i := 0 to High(wantThisWeapon) do
-    wantThisWeapon[i] := false;
+  for i := 0 to High(wantThisWeapon) do wantThisWeapon[i] := false;
+  wwc := 0;
+
+  curlidx := -1; // start from a weapon with a highest priority (-1, 'cause loop will immediately increment this index)
+
   for i := 0 to High(FWeapon) do
+  begin
     if (FNextWeap and (1 shl i)) <> 0 then
     begin
-      wantThisWeapon[i] := true;
-      Inc(wwc);
+      cwi := real2log(i);
+      if (cwi >= 0) then
+      begin
+        wantThisWeapon[cwi] := true;
+        Inc(wwc);
+        // if we hit currently selected weapon, start seachring from it, so we'll get "next weaker" weapon
+        if (i = FCurrWeap) then curlidx := cwi; // compare real, start from logical
+      end;
     end;
-  // exclude currently selected weapon from the set
-  wantThisWeapon[FCurrWeap] := false;
+  end;
+
   // slow down alterations a little
-  if wwc > 1 then
+  if (wwc > 1) then
   begin
-    //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
-    // more than one weapon requested, assume "alteration" and check alteration delay
+    // more than one weapon requested, assume "alteration", and check alteration delay
     if FNextWeapDelay > 0 then
     begin
+      //e_WriteLog(Format(' FNextWeap=%x; delay=%d', [FNextWeap, FNextWeapDelay]), TMsgType.Warning);
       FNextWeap := 0;
       exit;
     end; // yeah
   end;
+
   // do not reset weapon queue, it will be done in `RealizeCurrentWeapon()`
   // but clear all counters if no weapon should be switched
-  if wwc < 1 then
+  if (wwc < 1) then
   begin
     resetWeaponQueue();
     exit;
   end;
-  //e_WriteLog(Format('wwc=%d', [wwc]), MSG_WARNING);
-  // try weapons in descending order
-  for i := High(FWeapon) downto 0 do
+
+  //e_WriteLog(Format('*** wwc=%d; currweap=%d; logweap=%d', [wwc, FCurrWeap, curlidx]), TMsgType.Warning);
+
+  // find next weapon to switch onto
+  cwi := curlidx;
+  for i := 0 to High(weaponOrder) do
   begin
-    if wantThisWeapon[i] and FWeapon[i] and ((wwc = 1) or hasAmmoForWeapon(i)) then
+    cwi := (cwi+length(weaponOrder)+1) mod length(weaponOrder);
+    if (cwi = curlidx) then continue; // skip current weapon
+    if not wantThisWeapon[cwi] then continue;
+    rwidx := weaponOrder[cwi];
+    if (rwidx < 0) then continue;
+    //e_WriteLog(Format('  trying logical %d (real %d); has=%d, hasammo=%d', [cwi, rwidx, Integer(FWeapon[rwidx]), Integer(hasAmmoForWeapon(rwidx))]), TMsgType.Warning);
+    if FWeapon[rwidx] and ((wwc = 1) or hasAmmoForWeapon(rwidx)) then
     begin
+      //e_WriteLog('    I FOUND HER!', TMsgType.Warning);
       // i found her!
-      result := Byte(i);
+      result := Byte(rwidx);
       resetWeaponQueue();
-      FNextWeapDelay := 10; // anyway, 'cause why not
+      //FNextWeapDelay := 10; // anyway, 'cause why not; k8: not needed anymore
       exit;
     end;
   end;
+
   // no suitable weapon found, so reset the queue, to avoid accidental "queuing" of weapon w/o ammo
   resetWeaponQueue();
 end;
@@ -3631,12 +3793,14 @@ var
 begin
   //e_WriteLog(Format('***RealizeCurrentWeapon: FNextWeap=%x; FNextWeapDelay=%d', [FNextWeap, FNextWeapDelay]), MSG_WARNING);
   //FNextWeap := FNextWeap and $1FFF;
-  if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
+  //HACK: alteration delay will be reset when player released any weapon switch key
+  FNextWeapDelay := 0; //k8: just in case
+  //if FNextWeapDelay > 0 then Dec(FNextWeapDelay); // "alteration delay"
 
   if not switchAllowed then
   begin
     //HACK for weapon cycling
-    if (FNextWeap and $7000) <> 0 then FNextWeap := 0;
+    if (FNextWeap and $E000) <> 0 then FNextWeap := 0;
     exit;
   end;
 
@@ -3645,7 +3809,7 @@ begin
   if nw > High(FWeapon) then
   begin
     // don't forget to reset queue here!
-    //e_WriteLog(' RealizeCurrentWeapon: WUTAFUUUU', MSG_WARNING);
+    //e_WriteLog(Format(' RealizeCurrentWeapon: WUTAFUUUU?! (%d)', [nw]), TMsgType.Warning);
     resetWeaponQueue();
     exit;
   end;
@@ -4051,6 +4215,7 @@ begin
           Result := True;
           remove := True;
           FFireTime := 0;
+          //k8:do we need it? if g_Game_IsNet and g_Game_IsServer then MH_SEND_PlayerStats(FUID);
         end;
         if FHealth < PLAYER_HP_SOFT then
         begin
@@ -4337,6 +4502,12 @@ var
   Anim: TAnimation;
   ID: DWORD;
 begin
+  FIncCam := 0;
+  FBFGFireCounter := -1;
+  FShellTimer := -1;
+  FPain := 0;
+  FLastHit := 0;
+
   if not g_Game_IsServer then
     Exit;
   if FDummy then
@@ -4441,12 +4612,6 @@ begin
   else
     FAngle := 0;
 
-  FIncCam := 0;
-  FBFGFireCounter := -1;
-  FShellTimer := -1;
-  FPain := 0;
-  FLastHit := 0;
-
   SetAction(A_STAND, True);
   FModel.Direction := FDirection;
 
@@ -4479,6 +4644,11 @@ begin
   FSpectatePlayer := -1;
   FSpawned := True;
 
+  if (gPlayer1 = nil) and (gLMSPID1 = FUID) then
+    gPlayer1 := self;
+  if (gPlayer2 = nil) and (gLMSPID2 = FUID) then
+    gPlayer2 := self;
+
   if g_Game_IsNet then
   begin
     MH_SEND_PlayerPos(True, FUID, NET_EVERYONE);
@@ -4738,6 +4908,30 @@ begin
     Result := 1;
 end;
 
+function TPlayer.followCorpse(): Boolean;
+var
+  i: Integer;
+begin
+  Result := False;
+  if FAlive or FSpectator then
+    Exit;
+  if (gCorpses = nil) or (Length(gCorpses) = 0) then
+    Exit;
+  for i := 0 to High(gCorpses) do
+    if gCorpses[i] <> nil then
+      if gCorpses[i].FPlayerUID = FUID then
+      begin
+        Result := True;
+        FObj.X := gCorpses[i].FObj.X;
+        FObj.Y := gCorpses[i].FObj.Y;
+        FObj.Vel.X := gCorpses[i].FObj.Vel.X;
+        FObj.Vel.Y := gCorpses[i].FObj.Vel.Y;
+        FObj.Accel.X := gCorpses[i].FObj.Accel.X;
+        FObj.Accel.Y := gCorpses[i].FObj.Accel.Y;
+        break;
+      end;
+end;
+
 procedure TPlayer.Update();
 var
   b: Byte;
@@ -4814,7 +5008,8 @@ begin
 
     if FPhysics then
     begin
-      g_Obj_Move(@FObj, True, True, True);
+      if not followCorpse() then
+        g_Obj_Move(@FObj, True, True, True);
       positionChanged(); // this updates spatial accelerators
     end;
 
@@ -4940,7 +5135,8 @@ begin
 
   if FPhysics then
   begin
-    g_Obj_Move(@FObj, True, True, True);
+    if not followCorpse() then
+      g_Obj_Move(@FObj, True, True, True);
     positionChanged(); // this updates spatial accelerators
   end
   else
@@ -5284,6 +5480,7 @@ begin
   case FCurrWeap of
     WEAPON_KASTET:
     begin
+      DoPunch();
       if R_BERSERK in FRulez then
       begin
         //g_Weapon_punch(FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, 75, FUID);
@@ -5298,8 +5495,6 @@ begin
         locobj.Accel.X := xd-wx;
         locobj.Accel.y := yd-wy;
 
-        DoPunch();
-
         if g_Weapon_Hit(@locobj, 50, FUID, HIT_SOME) <> 0 then
           g_Sound_PlayExAt('SOUND_WEAPON_HITBERSERK', FObj.X, FObj.Y)
         else
@@ -6186,6 +6381,7 @@ end;
 procedure TCorpse.Damage(Value: Word; vx, vy: Integer);
 var
   pm: TPlayerModel;
+  Blood: TModelBlood;
 begin
   if FState = CORPSE_STATE_REMOVEME then
     Exit;
@@ -6208,16 +6404,23 @@ begin
         pm := g_PlayerModel_Get(FModelName);
         pm.PlaySound(MODELSOUND_DIE, 5, FObj.X, FObj.Y);
         pm.Free;
+
+        // Çëîâåùèé ñìåõ:
+        if (gBodyKillEvent <> -1)
+        and gDelayedEvents[gBodyKillEvent].Pending then
+          gDelayedEvents[gBodyKillEvent].Pending := False;
+        gBodyKillEvent := g_Game_DelayEvent(DE_BODYKILL, 1050, 0);
       end;
     end
   else
     begin
+      Blood := g_PlayerModel_GetBlood(FModelName);
       FObj.Vel.X := FObj.Vel.X + vx;
       FObj.Vel.Y := FObj.Vel.Y + vy;
       g_GFX_Blood(FObj.X+PLAYER_CORPSERECT.X+(PLAYER_CORPSERECT.Width div 2),
                   FObj.Y+PLAYER_CORPSERECT.Y+(PLAYER_CORPSERECT.Height div 2),
                   Value, vx, vy, 16, (PLAYER_CORPSERECT.Height*2) div 3,
-                  150, 0, 0);
+                  Blood.R, Blood.G, Blood.B, Blood.Kind);
     end;
 end;
 
@@ -6291,6 +6494,7 @@ begin
   utils.writeInt(st, Byte(FColor.B));
   // Îáúåêò òðóïà
   Obj_SaveState(st, @FObj);
+  utils.writeInt(st, Word(FPlayerUID));
   // Åñòü ëè àíèìàöèÿ
   anim := (FAnimation <> nil);
   utils.writeBool(st, anim);
@@ -6323,6 +6527,7 @@ begin
   FColor.B := utils.readByte(st);
   // Îáúåêò òðóïà
   Obj_LoadState(@FObj, st);
+  FPlayerUID := utils.readWord(st);
   // Åñòü ëè àíèìàöèÿ
   anim := utils.readBool(st);
   // Åñëè åñòü - çàãðóæàåì