DEADSOFTWARE

purge cl_interp from the code
[d2df-sdl.git] / src / game / g_player.pas
index 0cde3c089c1dfe4d0a5d9098721c4e4717d07e4e..362c2833ecbe9a7393caf11622c1ccb5bbfdb67e 100644 (file)
@@ -130,7 +130,7 @@ type
     MaxAmmo:    Array [A_BULLETS..A_HIGH] of Word;
     Weapon:     Array [WP_FIRST..WP_LAST] of Boolean;
     Rulez:      Set of R_ITEM_BACKPACK..R_BERSERK;
-    WaitRecall: Boolean;
+    Used:       Boolean;
   end;
 
   TKeyState = record
@@ -171,12 +171,11 @@ type
     FLastSpawnerUID: Word;
     FLastHit:   Byte;
     FObj:       TObj;
-    FXTo, FYTo: Integer;
     FSpectatePlayer: Integer;
     FFirePainTime:   Integer;
     FFireAttacker:   Word;
 
-    FSavedState: TPlayerSavedState;
+    FSavedStateNum:   Integer;
 
     FModel:     TPlayerModel;
     FPunchAnim: TAnimation;
@@ -186,7 +185,9 @@ type
     FActionChanged:  Boolean;
     FAngle:     SmallInt;
     FFireAngle: SmallInt;
+    FIncCamOld:      Integer;
     FIncCam:         Integer;
+    FSlopeOld:       Integer;
     FShellTimer:     Integer;
     FShellType:      Byte;
     FSawSound:       TPlayableSound;
@@ -267,6 +268,7 @@ type
     FReady:     Boolean;
     FDummy:     Boolean;
     FFireTime:  Integer;
+    FSpawnInvul: Integer;
     FHandicap:  Integer;
     FWaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
 
@@ -324,6 +326,7 @@ type
     procedure   DrawIndicator(Color: TRGB);
     procedure   DrawBubble();
     procedure   DrawGUI();
+    procedure   PreUpdate();
     procedure   Update(); virtual;
     procedure   RememberState();
     procedure   RecallState();
@@ -331,8 +334,6 @@ type
     procedure   LoadState (st: TStream); virtual;
     procedure   PauseSounds(Enable: Boolean);
     procedure   NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
-    procedure   DoLerp(Level: Integer = 2);
-    procedure   SetLerp(XTo, YTo: Integer);
     procedure   QueueWeaponSwitch(Weapon: Byte);
     procedure   RealizeCurrentWeapon();
     procedure   FlamerOn;
@@ -378,6 +379,8 @@ type
     property    GameAccelX: Integer read FObj.Accel.X write FObj.Accel.X;
     property    GameAccelY: Integer read FObj.Accel.Y write FObj.Accel.Y;
     property    IncCam: Integer read FIncCam write FIncCam;
+    property    IncCamOld: Integer read FIncCamOld write FIncCamOld;
+    property    SlopeOld: Integer read FSlopeOld write FSlopeOld;
     property    UID: Word read FUID write FUID;
     property    JustTeleported: Boolean read FJustTeleported write FJustTeleported;
     property    NetTime: LongWord read FNetTime write FNetTime;
@@ -559,8 +562,8 @@ var
   gPlayerIndicator: Integer = 1;
   gPlayerIndicatorStyle: Integer = 0;
   gNumBots: Word = 0;
-  gLMSPID1: Word = 0;
-  gLMSPID2: Word = 0;
+  gSpectLatchPID1: Word = 0;
+  gSpectLatchPID2: Word = 0;
   MAX_RUNVEL: Integer = 8;
   VEL_JUMP: Integer = 10;
   SHELL_TIMEOUT: Cardinal = 60000;
@@ -580,6 +583,7 @@ function  g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boole
 function  g_Player_CreateFromState (st: TStream): Word;
 procedure g_Player_Remove(UID: Word);
 procedure g_Player_ResetTeams();
+procedure g_Player_PreUpdate();
 procedure g_Player_UpdateAll();
 procedure g_Player_DrawAll();
 procedure g_Player_DrawDebug(p: TPlayer);
@@ -702,6 +706,7 @@ var
   CurrentShell: Integer = 0;
   BotNames: Array of String;
   BotList: Array of TBotProfile;
+  SavedStates: Array of TPlayerSavedState;
 
 
 function Lerp(X, Y, Factor: Integer): Integer;
@@ -1071,12 +1076,6 @@ begin
         Break;
       end;
 
-// Èìåíè íåò, çàäàåì ñëó÷àéíîå:
-  if _name = '' then
-    repeat
-      _name := Format('DFBOT%.2d', [Random(100)]);
-    until g_Player_ValidName(_name);
-
 // Âûáèðàåì ñëó÷àéíóþ ìîäåëü:
   _model := m[Random(Length(m))];
 
@@ -1087,7 +1086,11 @@ begin
                                          Min(Random(9)*32, 255)),
                                     Team, True)) as TBot do
   begin
-    Name := _name;
+  // Åñëè èìåíè íåò, äåëàåì åãî èç UID áîòà
+    if _name = '' then
+      Name := Format('DFBOT%.5d', [UID])
+    else
+      Name := _name;
 
     case Difficult of
       1: FDifficult := DIFFICULT_EASY;
@@ -1341,6 +1344,7 @@ begin
   end;
 
   config.Free();
+  SetLength(SavedStates, 0);
 end;
 
 procedure g_Player_Free();
@@ -1364,6 +1368,17 @@ begin
 
   gPlayer1 := nil;
   gPlayer2 := nil;
+  SetLength(SavedStates, 0);
+end;
+
+procedure g_Player_PreUpdate();
+var
+  i: Integer;
+begin
+  if gPlayers = nil then Exit;
+  for i := 0 to High(gPlayers) do
+    if gPlayers[i] <> nil then
+      gPlayers[i].PreUpdate();
 end;
 
 procedure g_Player_UpdateAll();
@@ -1711,6 +1726,9 @@ begin
       if gGibs[i].alive then
         with gGibs[i] do
         begin
+          Obj.oldX := Obj.X;
+          Obj.oldY := Obj.Y;
+
           vel := Obj.Vel;
           mr := g_Obj_Move(@Obj, True, False, True);
           positionChanged(); // this updates spatial accelerators
@@ -1761,6 +1779,9 @@ begin
       if gShells[i].alive then
         with gShells[i] do
         begin
+          Obj.oldX := Obj.X;
+          Obj.oldY := Obj.Y;
+
           vel := Obj.Vel;
           mr := g_Obj_Move(@Obj, True, False, True);
           positionChanged(); // this updates spatial accelerators
@@ -1849,7 +1870,7 @@ procedure TShell.positionChanged (); inline; begin end;
 
 procedure g_Player_DrawCorpses();
 var
-  i: Integer;
+  i, fX, fY: Integer;
   a: TDFPoint;
 begin
   if gGibs <> nil then
@@ -1860,13 +1881,15 @@ begin
           if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
             Continue;
 
+          Obj.lerp(gLerpFactor, fX, fY);
+
           a.X := Obj.Rect.X+(Obj.Rect.Width div 2);
           a.y := Obj.Rect.Y+(Obj.Rect.Height div 2);
 
-          e_DrawAdv(ID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
+          e_DrawAdv(ID, fX, fY, 0, True, False, RAngle, @a, TMirrorType.None);
 
           e_Colors := Color;
-          e_DrawAdv(MaskID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
+          e_DrawAdv(MaskID, fX, fY, 0, True, False, RAngle, @a, TMirrorType.None);
           e_Colors.R := 255;
           e_Colors.G := 255;
           e_Colors.B := 255;
@@ -1880,7 +1903,7 @@ end;
 
 procedure g_Player_DrawShells();
 var
-  i: Integer;
+  i, fX, fY: Integer;
   a: TDFPoint;
 begin
   if gShells <> nil then
@@ -1891,10 +1914,12 @@ begin
           if not g_Obj_Collide(sX, sY, sWidth, sHeight, @Obj) then
             Continue;
 
+          Obj.lerp(gLerpFactor, fX, fY);
+
           a.X := CX;
           a.Y := CY;
 
-          e_DrawAdv(SpriteID, Obj.X, Obj.Y, 0, True, False, RAngle, @a, TMirrorType.None);
+          e_DrawAdv(SpriteID, fX, fY, 0, True, False, RAngle, @a, TMirrorType.None);
         end;
 end;
 
@@ -2165,7 +2190,7 @@ begin
   FClientID := -1;
   FPing := 0;
   FLoss := 0;
-  FSavedState.WaitRecall := False;
+  FSavedStateNum := -1;
   FShellTimer := -1;
   FFireTime := 0;
   FFirePainTime := 0;
@@ -2229,6 +2254,7 @@ begin
     FMegaRulez[MR_SUIT] := 0;
     FMegaRulez[MR_INVUL] := 0;
     FMegaRulez[MR_INVIS] := 0;
+    FSpawnInvul := 0;
     FBerserk := 0;
   end;
 
@@ -2335,7 +2361,7 @@ end;
 
 procedure TPlayer.DrawIndicator(Color: TRGB);
 var
-  indX, indY: Integer;
+  indX, indY, fX, fY, fSlope: Integer;
   indW, indH: Word;
   indA: Single;
   a: TDFPoint;
@@ -2344,6 +2370,10 @@ var
   c: TRGB;
 begin
   if FAlive then
+  begin
+    FObj.lerp(gLerpFactor, fX, fY);
+    fSlope := nlerp(FSlopeOld, FObj.slopeUpLeft, gLerpFactor);
+
     case gPlayerIndicatorStyle of
       0:
         begin
@@ -2356,31 +2386,32 @@ begin
             if (FObj.X + FObj.Rect.X) < 0 then
             begin
               indA := 90;
-              indX := FObj.X + FObj.Rect.X + FObj.Rect.Width;
-              indY := FObj.Y + FObj.Rect.Y + (FObj.Rect.Height - indW) div 2;
+              indX := fX + FObj.Rect.X + FObj.Rect.Width;
+              indY := fY + FObj.Rect.Y + (FObj.Rect.Height - indW) div 2;
             end
 
             else if (FObj.X + FObj.Rect.X + FObj.Rect.Width) > Max(gMapInfo.Width, gPlayerScreenSize.X) then
             begin
               indA := 270;
-              indX := FObj.X + FObj.Rect.X - indH;
-              indY := FObj.Y + FObj.Rect.Y + (FObj.Rect.Height - indW) div 2;
+              indX := fX + FObj.Rect.X - indH;
+              indY := fY + FObj.Rect.Y + (FObj.Rect.Height - indW) div 2;
             end
 
-            else if (fObj.Y - indH) < 0 then
+            else if (FObj.Y - indH) < 0 then
             begin
               indA := 180;
-              indX := FObj.X + FObj.Rect.X + (FObj.Rect.Width - indW) div 2;
-              indY := FObj.Y + FObj.Rect.Y + FObj.Rect.Height;
+              indX := fX + FObj.Rect.X + (FObj.Rect.Width - indW) div 2;
+              indY := fY + FObj.Rect.Y + FObj.Rect.Height;
             end
 
             else
             begin
               indA := 0;
-              indX := FObj.X + FObj.Rect.X + (FObj.Rect.Width - indW) div 2;
-              indY := FObj.Y - indH;
+              indX := fX + FObj.Rect.X + (FObj.Rect.Width - indW) div 2;
+              indY := fY - indH;
             end;
 
+            indY := indY + fSlope;
             indX := EnsureRange(indX, 0, Max(gMapInfo.Width, gPlayerScreenSize.X) - indW);
             indY := EnsureRange(indY, 0, Max(gMapInfo.Height, gPlayerScreenSize.Y) - indH);
 
@@ -2394,23 +2425,25 @@ begin
       1:
         begin
           e_TextureFontGetSize(gStdFont, nW, nH);
-          indX := FObj.X + FObj.Rect.X + (FObj.Rect.Width - Length(FName) * nW) div 2;
-          indY := FObj.Y - nH;
+          indX := fX + FObj.Rect.X + (FObj.Rect.Width - Length(FName) * nW) div 2;
+          indY := fY - nH + fSlope;
           e_TextureFontPrintEx(indX, indY, FName, gStdFont, Color.R, Color.G, Color.B, 1.0, True);
         end;
     end;
+  end;
 end;
 
 procedure TPlayer.DrawBubble();
 var
-  bubX, bubY: Integer;
+  bubX, bubY, fX, fY: Integer;
   ID: LongWord;
   Rb, Gb, Bb,
   Rw, Gw, Bw: SmallInt;
   Dot: Byte;
 begin
-  bubX := FObj.X+FObj.Rect.X + IfThen(FDirection = TDirection.D_LEFT, -4, 18);
-  bubY := FObj.Y+FObj.Rect.Y - 18;
+  FObj.lerp(gLerpFactor, fX, fY);
+  bubX := fX+FObj.Rect.X + IfThen(FDirection = TDirection.D_LEFT, -4, 18);
+  bubY := fY+FObj.Rect.Y - 18;
   Rb := 64;
   Gb := 64;
   Bb := 64;
@@ -2420,8 +2453,8 @@ begin
   case gChatBubble of
     1: // simple textual non-bubble
     begin
-      bubX := FObj.X+FObj.Rect.X - 11;
-      bubY := FObj.Y+FObj.Rect.Y - 17;
+      bubX := fX+FObj.Rect.X - 11;
+      bubY := fY+FObj.Rect.Y - 17;
       e_TextureFontPrint(bubX, bubY, '[...]', gStdFont);
       Exit;
     end;
@@ -2488,7 +2521,11 @@ var
   w, h: Word;
   dr: Boolean;
   Mirror: TMirrorType;
+  fX, fY, fSlope: Integer;
 begin
+  FObj.lerp(gLerpFactor, fX, fY);
+  fSlope := nlerp(FSlopeOld, FObj.slopeUpLeft, gLerpFactor);
+
   if FAlive then
   begin
     if Direction = TDirection.D_RIGHT then
@@ -2498,8 +2535,8 @@ begin
 
     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);
+      FPunchAnim.Draw(fX+IfThen(Direction = TDirection.D_LEFT, 15-FObj.Rect.X, FObj.Rect.X-15),
+                      fY+fSlope+FObj.Rect.Y-11, Mirror);
       if FPunchAnim.played then
       begin
         FPunchAnim.Free;
@@ -2507,16 +2544,16 @@ begin
       end;
     end;
 
-    if (FMegaRulez[MR_INVUL] > gTime) and (gPlayerDrawn <> Self) then
+    if (FMegaRulez[MR_INVUL] > gTime) and ((gPlayerDrawn <> Self) or (FSpawnInvul >= gTime)) then
       if g_Texture_Get('TEXTURE_PLAYER_INVULPENTA', ID) then
       begin
         e_GetTextureSize(ID, @w, @h);
         if FDirection = TDirection.D_LEFT then
-          e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)+4,
-                     FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+FObj.slopeUpLeft, 0, True, False)
+          e_Draw(ID, fX+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)+4,
+                     fY+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+fSlope, 0, True, False)
         else
-          e_Draw(ID, FObj.X+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)-2,
-                     FObj.Y+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+FObj.slopeUpLeft, 0, True, False);
+          e_Draw(ID, fX+FObj.Rect.X+(FObj.Rect.Width div 2)-(w div 2)-2,
+                     fY+FObj.Rect.Y+(FObj.Rect.Height div 2)-(h div 2)-7+fSlope, 0, True, False);
       end;
 
     if FMegaRulez[MR_INVIS] > gTime then
@@ -2529,15 +2566,15 @@ begin
         else
           dr := True;
         if dr then
-          FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft, 200)
+          FModel.Draw(fX, fY+fSlope, 200)
         else
-          FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft);
+          FModel.Draw(fX, fY+fSlope);
       end
       else
-        FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft, 254);
+        FModel.Draw(fX, fY+fSlope, 254);
     end
     else
-      FModel.Draw(FObj.X, FObj.Y+FObj.slopeUpLeft);
+      FModel.Draw(fX, fY+fSlope);
   end;
 
   if g_debug_Frames then
@@ -2766,7 +2803,14 @@ begin
       e_CharFont_PrintEx(gMenuSmallFont, X-16-tw, Y+32, s, _RGB(255, 0, 0));
     end;
 
-    if gShowLives and (gGameSettings.MaxLives > 0) then
+    if gLMSRespawn > LMS_RESPAWN_NONE then
+    begin
+      s := _lc[I_GAME_WARMUP];
+      e_CharFont_GetSize(gMenuFont, s, tw, th);
+      s := s + ': ' + IntToStr((gLMSRespawnTime - gTime) div 1000);
+      e_CharFont_PrintEx(gMenuFont, X-64-tw, SY-32, s, _RGB(0, 255, 0));
+    end
+    else if gShowLives and (gGameSettings.MaxLives > 0) then
     begin
       s := IntToStr(Lives);
       e_CharFont_GetSize(gMenuFont, s, tw, th);
@@ -2875,7 +2919,7 @@ var
   dr: Boolean;
 begin
   // Ïðè âçÿòèè íåóÿçâèìîñòè ðèñóåòñÿ èíâåðñèîííûé áåëûé ôîí
-  if FMegaRulez[MR_INVUL] >= gTime then
+  if (FMegaRulez[MR_INVUL] >= gTime) and (FSpawnInvul < gTime) then
   begin
     if (FMegaRulez[MR_INVUL]-gTime) <= 2100 then
       dr := not Odd((FMegaRulez[MR_INVUL]-gTime) div 300)
@@ -3539,7 +3583,8 @@ begin
       PushItem(ITEM_JETPACK);
 
 // Âûáðîñ êëþ÷åé:
-    if not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) then
+    if (not (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF])) or
+       (not LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS)) then
     begin
       if R_KEY_RED in FRulez then
         PushItem(ITEM_KEY_RED);
@@ -3659,7 +3704,7 @@ begin
     if srv and (OldLR = LMS_RESPAWN_NONE) and (gLMSRespawn > LMS_RESPAWN_NONE) then
     begin
       if NetMode = NET_SERVER then
-        MH_SEND_GameEvent(NET_EV_LMS_WARMUP, (gLMSRespawnTime - gTime) div 1000)
+        MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime)
       else
         g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [(gLMSRespawnTime - gTime) div 1000]), True);
     end;
@@ -4279,6 +4324,7 @@ begin
       if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
       begin
         FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
+        FSpawnInvul := 0;
         Result := True;
         remove := True;
         if gFlash = 2 then Inc(FPickup, 5);
@@ -4364,6 +4410,7 @@ begin
   FMonsterKills := 0;
   FDeath := 0;
   FSecrets := 0;
+  FSpawnInvul := 0;
   FReady := False;
   if FNoRespawn then
   begin
@@ -4383,6 +4430,8 @@ begin
   ReleaseKeys();
 
   FDamageBuffer := 0;
+  FSlopeOld := 0;
+  FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
   FShellTimer := -1;
@@ -4551,11 +4600,14 @@ var
   Anim: TAnimation;
   ID: DWORD;
 begin
+  FSlopeOld := 0;
+  FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
   FShellTimer := -1;
   FPain := 0;
   FLastHit := 0;
+  FSpawnInvul := 0;
 
   if not g_Game_IsServer then
     Exit;
@@ -4634,7 +4686,8 @@ begin
     FMaxAmmo[A_CELLS] := AmmoLimits[0, A_CELLS];
     FMaxAmmo[A_FUEL] := AmmoLimits[0, A_FUEL];
 
-    if gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF] then
+    if (gGameSettings.GameMode in [GM_DM, GM_TDM, GM_CTF]) and
+       LongBool(gGameSettings.Options and GAME_OPTION_DMKEYS) then
       FRulez := [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE]
     else
       FRulez := [];
@@ -4650,6 +4703,8 @@ begin
 // Óñòàíîâêà êîîðäèíàò è ñáðîñ âñåõ ïàðàìåòðîâ:
   FObj.X := RespawnPoint.X-PLAYER_RECT.X;
   FObj.Y := RespawnPoint.Y-PLAYER_RECT.Y;
+  FObj.oldX := FObj.X; // don't interpolate after respawn
+  FObj.oldY := FObj.Y;
   FObj.Vel.X := 0;
   FObj.Vel.Y := 0;
   FObj.Accel.X := 0;
@@ -4670,6 +4725,13 @@ begin
   for a := Low(FMegaRulez) to High(FMegaRulez) do
     FMegaRulez[a] := 0;
 
+// Respawn invulnerability
+  if (gGameSettings.GameType <> GT_SINGLE) and (gGameSettings.SpawnInvul > 0) then
+  begin
+    FMegaRulez[MR_INVUL] := gTime + gGameSettings.SpawnInvul * 1000;
+    FSpawnInvul := FMegaRulez[MR_INVUL];
+  end;
+
   FDamageBuffer := 0;
   FJetpack := False;
   FCanJetpack := False;
@@ -4694,9 +4756,9 @@ begin
   FSpectatePlayer := -1;
   FSpawned := True;
 
-  if (gPlayer1 = nil) and (gLMSPID1 = FUID) then
+  if (gPlayer1 = nil) and (gSpectLatchPID1 = FUID) then
     gPlayer1 := self;
-  if (gPlayer2 = nil) and (gLMSPID2 = FUID) then
+  if (gPlayer2 = nil) and (gSpectLatchPID2 = FUID) then
     gPlayer2 := self;
 
   if g_Game_IsNet then
@@ -4719,8 +4781,6 @@ begin
     GameX := gMapInfo.Width div 2;
     GameY := gMapInfo.Height div 2;
   end;
-  FXTo := GameX;
-  FYTo := GameY;
 
   FAlive := False;
   FSpectator := True;
@@ -4733,12 +4793,12 @@ begin
   begin
     if Self = gPlayer1 then
     begin
-      gLMSPID1 := FUID;
+      gSpectLatchPID1 := FUID;
       gPlayer1 := nil;
-    end;
-    if Self = gPlayer2 then
+    end
+    else if Self = gPlayer2 then
     begin
-      gLMSPID2 := FUID;
+      gSpectLatchPID2 := FUID;
       gPlayer2 := nil;
     end;
   end;
@@ -4753,11 +4813,7 @@ begin
     Exit;
   FGhost := not FGhost;
   FPhysics := not FGhost;
-  if FGhost then
-  begin
-    FXTo := FObj.X;
-    FYTo := FObj.Y;
-  end else
+  if not FGhost then
   begin
     FObj.Accel.X := 0;
     FObj.Accel.Y := 0;
@@ -4900,11 +4956,8 @@ begin
 
   FObj.X := X-PLAYER_RECT.X;
   FObj.Y := Y-PLAYER_RECT.Y;
-  if FAlive and FGhost then
-  begin
-    FXTo := FObj.X;
-    FYTo := FObj.Y;
-  end;
+  FObj.oldX := FObj.X; // don't interpolate after respawn
+  FObj.oldY := FObj.Y;
 
   if not g_Game_IsNet then
   begin
@@ -4982,6 +5035,14 @@ begin
       end;
 end;
 
+procedure TPlayer.PreUpdate();
+begin
+  FSlopeOld := FObj.slopeUpLeft;
+  FIncCamOld := FIncCam;
+  FObj.oldX := FObj.X;
+  FObj.oldY := FObj.Y;
+end;
+
 procedure TPlayer.Update();
 var
   b: Byte;
@@ -4994,12 +5055,6 @@ begin
   NetServer := g_Game_IsNet and g_Game_IsServer;
   AnyServer := g_Game_IsServer;
 
-  if g_Game_IsClient and (NetInterpLevel > 0) then
-    DoLerp(NetInterpLevel + 1)
-  else
-    if FGhost then
-      DoLerp(4);
-
   if NetServer then
     if FClientID >= 0 then
     begin
@@ -5162,33 +5217,33 @@ begin
   begin
     if FKeys[KEY_UP].Pressed or FKeys[KEY_JUMP].Pressed then
     begin
-      FYTo := FObj.Y - 32;
+      FObj.Y := FObj.Y - 32;
       FSpectatePlayer := -1;
     end;
     if FKeys[KEY_DOWN].Pressed then
     begin
-      FYTo := FObj.Y + 32;
+      FObj.Y := FObj.Y + 32;
       FSpectatePlayer := -1;
     end;
     if FKeys[KEY_LEFT].Pressed then
     begin
-      FXTo := FObj.X - 32;
+      FObj.X := FObj.X - 32;
       FSpectatePlayer := -1;
     end;
     if FKeys[KEY_RIGHT].Pressed then
     begin
-      FXTo := FObj.X + 32;
+      FObj.X := FObj.X + 32;
       FSpectatePlayer := -1;
     end;
 
-    if (FXTo < -64) then
-      FXTo := -64
-    else if (FXTo > gMapInfo.Width + 32) then
-      FXTo := gMapInfo.Width + 32;
-    if (FYTo < -72) then
-      FYTo := -72
-    else if (FYTo > gMapInfo.Height + 32) then
-      FYTo := gMapInfo.Height + 32;
+    if (FObj.X < -64) then
+      FObj.X := -64
+    else if (FObj.X > gMapInfo.Width + 32) then
+      FObj.X := gMapInfo.Width + 32;
+    if (FObj.Y < -72) then
+      FObj.Y := -72
+    else if (FObj.Y > gMapInfo.Height + 32) then
+      FObj.Y := gMapInfo.Height + 32;
   end;
 
   if FPhysics then
@@ -5206,8 +5261,8 @@ begin
         if gPlayers[FSpectatePlayer] <> nil then
           if gPlayers[FSpectatePlayer].alive then
           begin
-            FXTo := gPlayers[FSpectatePlayer].GameX;
-            FYTo := gPlayers[FSpectatePlayer].GameY;
+            FObj.X := gPlayers[FSpectatePlayer].GameX;
+            FObj.Y := gPlayers[FSpectatePlayer].GameY;
           end;
   end;
 
@@ -5666,35 +5721,6 @@ begin
       else if (FAngle = ANGLE_LEFTUP) or (FAngle = ANGLE_RIGHTUP) then SetAction(A_ATTACKUP);
 end;
 
-procedure TPlayer.DoLerp(Level: Integer = 2);
-begin
-  if FObj.X <> FXTo then FObj.X := Lerp(FObj.X, FXTo, Level);
-  if FObj.Y <> FYTo then FObj.Y := Lerp(FObj.Y, FYTo, Level);
-end;
-
-procedure TPlayer.SetLerp(XTo, YTo: Integer);
-var
-  AX, AY: Integer;
-begin
-  if NetInterpLevel < 1 then
-  begin
-    FObj.X := XTo;
-    FObj.Y := YTo;
-  end
-  else
-  begin
-    FXTo := XTo;
-    FYTo := YTo;
-
-    AX := Abs(FXTo - FObj.X);
-    AY := Abs(FYTo - FObj.Y);
-    if (AX > 32) or (AX <= NetInterpLevel) then
-      FObj.X := FXTo;
-    if (AY > 32) or (AY <= NetInterpLevel) then
-      FObj.Y := FYTo;
-  end;
-end;
-
 function TPlayer.FullInLift(XInc, YInc: Integer): Integer;
 begin
   if g_Map_CollidePanel(FObj.X+PLAYER_RECT.X+XInc, FObj.Y+PLAYER_RECT.Y+YInc,
@@ -5935,45 +5961,66 @@ end;
 procedure TPlayer.RememberState();
 var
   i: Integer;
-begin
-  FSavedState.Health := FHealth;
-  FSavedState.Armor := FArmor;
-  FSavedState.Air := FAir;
-  FSavedState.JetFuel := FJetFuel;
-  FSavedState.CurrWeap := FCurrWeap;
-  FSavedState.NextWeap := FNextWeap;
-  FSavedState.NextWeapDelay := FNextWeapDelay;
-
-  for i := 0 to 3 do
-    FSavedState.Ammo[i] := FAmmo[i];
-  for i := 0 to 3 do
-    FSavedState.MaxAmmo[i] := FMaxAmmo[i];
+  SavedState: TPlayerSavedState;
+begin
+  SavedState.Health := FHealth;
+  SavedState.Armor := FArmor;
+  SavedState.Air := FAir;
+  SavedState.JetFuel := FJetFuel;
+  SavedState.CurrWeap := FCurrWeap;
+  SavedState.NextWeap := FNextWeap;
+  SavedState.NextWeapDelay := FNextWeapDelay;
+  for i := Low(FWeapon) to High(FWeapon) do
+    SavedState.Weapon[i] := FWeapon[i];
+  for i := Low(FAmmo) to High(FAmmo) do
+    SavedState.Ammo[i] := FAmmo[i];
+  for i := Low(FMaxAmmo) to High(FMaxAmmo) do
+    SavedState.MaxAmmo[i] := FMaxAmmo[i];
+  SavedState.Rulez := FRulez - [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
+
+  FSavedStateNum := -1;
+  for i := Low(SavedStates) to High(SavedStates) do
+    if not SavedStates[i].Used then
+    begin
+      FSavedStateNum := i;
+      break;
+    end;
+  if FSavedStateNum < 0 then
+  begin
+    SetLength(SavedStates, Length(SavedStates) + 1);
+    FSavedStateNum := High(SavedStates);
+  end;
 
-  FSavedState.Rulez := FRulez;
-  FSavedState.WaitRecall := True;
+  SavedState.Used := True;
+  SavedStates[FSavedStateNum] := SavedState;
 end;
 
 procedure TPlayer.RecallState();
 var
   i: Integer;
+  SavedState: TPlayerSavedState;
 begin
-  if not FSavedState.WaitRecall then Exit;
-
-  FHealth := FSavedState.Health;
-  FArmor := FSavedState.Armor;
-  FAir := FSavedState.Air;
-  FJetFuel := FSavedState.JetFuel;
-  FCurrWeap := FSavedState.CurrWeap;
-  FNextWeap := FSavedState.NextWeap;
-  FNextWeapDelay := FSavedState.NextWeapDelay;
-
-  for i := 0 to 3 do
-    FAmmo[i] := FSavedState.Ammo[i];
-  for i := 0 to 3 do
-    FMaxAmmo[i] := FSavedState.MaxAmmo[i];
+  if(FSavedStateNum < 0) or (FSavedStateNum > High(SavedStates)) then
+    Exit;
 
-  FRulez := FSavedState.Rulez;
-  FSavedState.WaitRecall := False;
+  SavedState := SavedStates[FSavedStateNum];
+  SavedStates[FSavedStateNum].Used := False;
+  FSavedStateNum := -1;
+
+  FHealth := SavedState.Health;
+  FArmor := SavedState.Armor;
+  FAir := SavedState.Air;
+  FJetFuel := SavedState.JetFuel;
+  FCurrWeap := SavedState.CurrWeap;
+  FNextWeap := SavedState.NextWeap;
+  FNextWeapDelay := SavedState.NextWeapDelay;
+  for i := Low(FWeapon) to High(FWeapon) do
+    FWeapon[i] := SavedState.Weapon[i];
+  for i := Low(FAmmo) to High(FAmmo) do
+    FAmmo[i] := SavedState.Ammo[i];
+  for i := Low(FMaxAmmo) to High(FMaxAmmo) do
+    FMaxAmmo[i] := SavedState.MaxAmmo[i];
+  FRulez := SavedState.Rulez;
 
   if gGameSettings.GameType = GT_SERVER then
     MH_SEND_PlayerStats(FUID);
@@ -6291,6 +6338,7 @@ begin
       if FMegaRulez[MR_INVUL] < gTime+PLAYER_INVUL_TIME then
       begin
         FMegaRulez[MR_INVUL] := gTime+PLAYER_INVUL_TIME;
+        FSpawnInvul := 0;
       end;
 
     ITEM_INVIS:
@@ -6542,17 +6590,21 @@ begin
 end;
 
 procedure TCorpse.Draw();
+var
+  fX, fY: Integer;
 begin
   if FState = CORPSE_STATE_REMOVEME then
     Exit;
 
+  FObj.lerp(gLerpFactor, fX, fY);
+
   if FAnimation <> nil then
-    FAnimation.Draw(FObj.X, FObj.Y, TMirrorType.None);
+    FAnimation.Draw(fX, fY, TMirrorType.None);
 
   if FAnimationMask <> nil then
   begin
     e_Colors := FColor;
-    FAnimationMask.Draw(FObj.X, FObj.Y, TMirrorType.None);
+    FAnimationMask.Draw(fX, fY, TMirrorType.None);
     e_Colors.R := 255;
     e_Colors.G := 255;
     e_Colors.B := 255;
@@ -6566,6 +6618,9 @@ begin
   if FState = CORPSE_STATE_REMOVEME then
     Exit;
 
+  FObj.oldX := FObj.X;
+  FObj.oldY := FObj.Y;
+
   if gTime mod (GAME_TICK*2) <> 0 then
   begin
     g_Obj_Move(@FObj, True, True, True);