DEADSOFTWARE

draw talkbubble above the player corpse when necessary
[d2df-sdl.git] / src / game / g_player.pas
index fe38642843d48311e9169bb13b93973c9fcfa0b5..dd5378b5162ce6e2fbbf3f67bf5eee90cda9efbb 100644 (file)
@@ -186,7 +186,9 @@ type
     FActionChanged:  Boolean;
     FAngle:     SmallInt;
     FFireAngle: SmallInt;
+    FIncCamOld:      Integer;
     FIncCam:         Integer;
+    FSlopeOld:       Integer;
     FShellTimer:     Integer;
     FShellType:      Byte;
     FSawSound:       TPlayableSound;
@@ -237,7 +239,7 @@ type
 
     procedure doDamage (v: Integer);
 
-    function followCorpse(): Boolean;
+    function refreshCorpse(): Boolean;
 
   public
     FDamageBuffer:   Integer;
@@ -270,6 +272,7 @@ type
     FSpawnInvul: Integer;
     FHandicap:  Integer;
     FWaitForFirstSpawn: Boolean; // set to `true` in server, used to spawn a player on first full state request
+    FCorpse:    Integer;
 
     // debug: viewport offset
     viewPortX, viewPortY, viewPortW, viewPortH: Integer;
@@ -325,6 +328,7 @@ type
     procedure   DrawIndicator(Color: TRGB);
     procedure   DrawBubble();
     procedure   DrawGUI();
+    procedure   PreUpdate();
     procedure   Update(); virtual;
     procedure   RememberState();
     procedure   RecallState();
@@ -348,6 +352,8 @@ type
     procedure getMapBox (out x, y, w, h: Integer); inline;
     procedure moveBy (dx, dy: Integer); inline;
 
+    function getCameraObj(): TObj;
+
   public
     property    Vel: TPoint2i read FObj.Vel;
     property    Obj: TObj read FObj;
@@ -379,6 +385,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;
@@ -581,6 +589,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);
@@ -591,7 +600,7 @@ function  g_Player_Get(UID: Word): TPlayer;
 function  g_Player_GetCount(): Byte;
 function  g_Player_GetStats(): TPlayerStatArray;
 function  g_Player_ValidName(Name: String): Boolean;
-procedure g_Player_CreateCorpse(Player: TPlayer);
+function  g_Player_CreateCorpse(Player: TPlayer): Integer;
 procedure g_Player_CreateGibs(fX, fY: Integer; ModelName: String; fColor: TRGB);
 procedure g_Player_CreateShell(fX, fY, dX, dY: Integer; T: Byte);
 procedure g_Player_UpdatePhysicalObjects();
@@ -1368,6 +1377,16 @@ begin
   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();
 var
   i: Integer;
@@ -1421,6 +1440,8 @@ begin
   e_TextureFontPrint(0, fH * 3, 'Vel Y: ' + IntToStr(p.FObj.Vel.Y), gStdFont);
   e_TextureFontPrint(0, fH * 4, 'Acc X: ' + IntToStr(p.FObj.Accel.X), gStdFont);
   e_TextureFontPrint(0, fH * 5, 'Acc Y: ' + IntToStr(p.FObj.Accel.Y), gStdFont);
+  e_TextureFontPrint(0, fH * 6, 'Old X: ' + IntToStr(p.FObj.oldX), gStdFont);
+  e_TextureFontPrint(0, fH * 7, 'Old Y: ' + IntToStr(p.FObj.oldY), gStdFont);
 end;
 
 procedure g_Player_DrawHealth();
@@ -1554,21 +1575,24 @@ begin
       end;
 end;
 
-procedure g_Player_CreateCorpse(Player: TPlayer);
+function  g_Player_CreateCorpse(Player: TPlayer): Integer;
 var
   i: Integer;
   find_id: DWORD;
   ok: Boolean;
 begin
+  Result := -1;
+
   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;
+  i := Player.FCorpse;
+  if (i >= 0) and (i < Length(gCorpses)) then
+  begin
+    if (gCorpses[i] <> nil) and (gCorpses[i].FPlayerUID = Player.FUID) then
+      gCorpses[i].FPlayerUID := 0;
+  end;
 
   if Player.FObj.Y >= gMapInfo.Height+128 then
     Exit;
@@ -1596,6 +1620,8 @@ begin
         gCorpses[find_id].FObj.Vel := FObj.Vel;
         gCorpses[find_id].FObj.Accel := FObj.Accel;
         gCorpses[find_id].FPlayerUID := FUID;
+
+        Result := find_id;
       end
     else
       g_Player_CreateGibs(FObj.X + PLAYER_RECT_CX,
@@ -1713,6 +1739,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
@@ -1763,6 +1792,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
@@ -1851,7 +1883,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
@@ -1862,13 +1894,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;
@@ -1882,7 +1916,7 @@ end;
 
 procedure g_Player_DrawShells();
 var
-  i: Integer;
+  i, fX, fY: Integer;
   a: TDFPoint;
 begin
   if gShells <> nil then
@@ -1893,10 +1927,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;
 
@@ -2173,6 +2209,7 @@ begin
   FFirePainTime := 0;
   FFireAttacker := 0;
   FHandicap := 100;
+  FCorpse := -1;
 
   FActualModelName := 'doomer';
 
@@ -2338,7 +2375,7 @@ end;
 
 procedure TPlayer.DrawIndicator(Color: TRGB);
 var
-  indX, indY: Integer;
+  indX, indY, fX, fY, fSlope: Integer;
   indW, indH: Word;
   indA: Single;
   a: TDFPoint;
@@ -2347,6 +2384,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
@@ -2359,31 +2400,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);
 
@@ -2397,23 +2439,28 @@ 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;
+  CObj: TObj;
 begin
-  bubX := FObj.X+FObj.Rect.X + IfThen(FDirection = TDirection.D_LEFT, -4, 18);
-  bubY := FObj.Y+FObj.Rect.Y - 18;
+  CObj := getCameraObj();
+  CObj.lerp(gLerpFactor, fX, fY);
+  // NB: _F_Obj.Rect is used to keep the bubble higher; this is not a mistake
+  bubX := fX+FObj.Rect.X + IfThen(FDirection = TDirection.D_LEFT, -4, 18);
+  bubY := fY+FObj.Rect.Y - 18;
   Rb := 64;
   Gb := 64;
   Bb := 64;
@@ -2423,8 +2470,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;
@@ -2491,7 +2538,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
@@ -2501,8 +2552,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;
@@ -2515,11 +2566,11 @@ begin
       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
@@ -2532,15 +2583,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
@@ -3566,7 +3617,7 @@ begin
     DropFlag(KillType = K_FALLKILL);
   end;
 
-  g_Player_CreateCorpse(Self);
+  FCorpse := g_Player_CreateCorpse(Self);
 
   if Srv and (gGameSettings.MaxLives > 0) and FNoRespawn and
      (gLMSRespawn = LMS_RESPAWN_NONE) then
@@ -4377,6 +4428,7 @@ begin
   FDeath := 0;
   FSecrets := 0;
   FSpawnInvul := 0;
+  FCorpse := -1;
   FReady := False;
   if FNoRespawn then
   begin
@@ -4396,6 +4448,8 @@ begin
   ReleaseKeys();
 
   FDamageBuffer := 0;
+  FSlopeOld := 0;
+  FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
   FShellTimer := -1;
@@ -4564,12 +4618,15 @@ var
   Anim: TAnimation;
   ID: DWORD;
 begin
+  FSlopeOld := 0;
+  FIncCamOld := 0;
   FIncCam := 0;
   FBFGFireCounter := -1;
   FShellTimer := -1;
   FPain := 0;
   FLastHit := 0;
   FSpawnInvul := 0;
+  FCorpse := -1;
 
   if not g_Game_IsServer then
     Exit;
@@ -4665,6 +4722,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;
@@ -4750,6 +4809,7 @@ begin
   FPhysics := False;
   FWantsInGame := False;
   FSpawned := False;
+  FCorpse := -1;
 
   if FNoRespawn then
   begin
@@ -4922,6 +4982,8 @@ begin
 
   FObj.X := X-PLAYER_RECT.X;
   FObj.Y := Y-PLAYER_RECT.Y;
+  FObj.oldX := FObj.X; // don't interpolate after respawn
+  FObj.oldY := FObj.Y;
   if FAlive and FGhost then
   begin
     FXTo := FObj.X;
@@ -4980,11 +5042,12 @@ begin
     Result := 1;
 end;
 
-function TPlayer.followCorpse(): Boolean;
+function TPlayer.refreshCorpse(): Boolean;
 var
   i: Integer;
 begin
   Result := False;
+  FCorpse := -1;
   if FAlive or FSpectator then
     Exit;
   if (gCorpses = nil) or (Length(gCorpses) = 0) then
@@ -4994,16 +5057,34 @@ begin
       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;
+        FCorpse := i;
         break;
       end;
 end;
 
+function TPlayer.getCameraObj(): TObj;
+begin
+  if (not FAlive) and (not FSpectator) and
+     (FCorpse >= 0) and (FCorpse < Length(gCorpses)) and
+     (gCorpses[FCorpse] <> nil) and (gCorpses[FCorpse].FPlayerUID = FUID) then
+  begin
+    gCorpses[FCorpse].FObj.slopeUpLeft := FObj.slopeUpLeft;
+    Result := gCorpses[FCorpse].FObj;
+  end
+  else
+  begin
+    Result := FObj;
+  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;
@@ -5080,8 +5161,7 @@ begin
 
     if FPhysics then
     begin
-      if not followCorpse() then
-        g_Obj_Move(@FObj, True, True, True);
+      g_Obj_Move(@FObj, True, True, True);
       positionChanged(); // this updates spatial accelerators
     end;
 
@@ -5215,8 +5295,7 @@ begin
 
   if FPhysics then
   begin
-    if not followCorpse() then
-      g_Obj_Move(@FObj, True, True, True);
+    g_Obj_Move(@FObj, True, True, True);
     positionChanged(); // this updates spatial accelerators
   end
   else
@@ -5698,6 +5777,8 @@ procedure TPlayer.SetLerp(XTo, YTo: Integer);
 var
   AX, AY: Integer;
 begin
+  FXTo := XTo;
+  FYTo := YTo;
   if NetInterpLevel < 1 then
   begin
     FObj.X := XTo;
@@ -5705,9 +5786,6 @@ begin
   end
   else
   begin
-    FXTo := XTo;
-    FYTo := YTo;
-
     AX := Abs(FXTo - FObj.X);
     AY := Abs(FYTo - FObj.Y);
     if (AX > 32) or (AX <= NetInterpLevel) then
@@ -5974,24 +6052,21 @@ begin
     SavedState.MaxAmmo[i] := FMaxAmmo[i];
   SavedState.Rulez := FRulez - [R_KEY_RED, R_KEY_GREEN, R_KEY_BLUE];
 
-  if FSavedStateNum < 0 then
-  begin
-    for i := Low(SavedStates) to High(SavedStates) do
-      if not SavedStates[i].Used then
-      begin
-        FSavedStateNum := i;
-        break;
-      end;
-    if FSavedStateNum < 0 then
+  FSavedStateNum := -1;
+  for i := Low(SavedStates) to High(SavedStates) do
+    if not SavedStates[i].Used then
     begin
-      SetLength(SavedStates, Length(SavedStates) + 1);
-      FSavedStateNum := High(SavedStates);
+      FSavedStateNum := i;
+      break;
     end;
+  if FSavedStateNum < 0 then
+  begin
+    SetLength(SavedStates, Length(SavedStates) + 1);
+    FSavedStateNum := High(SavedStates);
   end;
 
-
   SavedState.Used := True;
-  SavedStates[i] := SavedState;
+  SavedStates[FSavedStateNum] := SavedState;
 end;
 
 procedure TPlayer.RecallState();
@@ -6589,17 +6664,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;
@@ -6613,6 +6692,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);