<commit_before>unit KM_Viewport;
{$I KaM_Remake.inc}
interface
uses
  {$IFDEF MSWINDOWS} Windows, {$ENDIF}
  KM_CommonClasses, KM_Points;


type
  TKMViewport = class
  private
    fMapX, fMapY: Word;
    fTopHill: Single;
    fPosition: TKMPointF;
    fScrolling: Boolean;
    fScrollStarted: Cardinal;
    fViewportClip: TPoint;
    fViewRect: TKMRect;
    fZoom: Single;
    fPanTo, fPanFrom: TKMPointF;
    fPanImmidiately : Boolean;
    fPanDuration, fPanProgress: Cardinal;
    function GetPosition: TKMPointF;
    procedure SetPosition(const Value: TKMPointF);
    procedure SetZoom(aZoom: Single);
  public
    ScrollKeyLeft, ScrollKeyRight, ScrollKeyUp, ScrollKeyDown, ZoomKeyIn, ZoomKeyOut: boolean;
    constructor Create(aWidth, aHeight: Integer);

    property Position: TKMPointF read GetPosition write SetPosition;
    property Scrolling: Boolean read fScrolling;
    property ViewportClip: TPoint read fViewportClip;
    property ViewRect: TKMRect read fViewRect;
    property Zoom: Single read fZoom write SetZoom;

    procedure ResetZoom;
    procedure Resize(NewWidth, NewHeight: Integer);
    procedure ResizeMap(aMapX, aMapY: Word; aTopHill: Single);
    function GetClip: TKMRect; //returns visible area dimensions in map space
    function GetMinimapClip: TKMRect;
    procedure ReleaseScrollKeys;
    procedure GameSpeedChanged(aFromSpeed, aToSpeed: Single);
    function MapToScreen(const aMapLoc: TKMPointF): TKMPoint;
    procedure PanTo(const aLoc: TKMPointF; aTicksCnt: Cardinal);
    procedure CinematicReset;

    procedure Save(SaveStream: TKMemoryStream);
    procedure Load(LoadStream: TKMemoryStream);

    procedure UpdateStateIdle(aFrameTime: Cardinal; aAllowMouseScrolling: Boolean; aInCinematic: Boolean);
  end;


implementation
uses
  Math, KromUtils,
  KM_Resource, KM_ResCursors,
  KM_Main, KM_GameApp, KM_Game, KM_Sound,
  KM_Defaults, KM_CommonUtils;


{ TKMViewport }
constructor TKMViewport.Create(aWidth, aHeight: Integer);
begin
  inherited Create;

  fMapX := 1; //Avoid division by 0
  fMapY := 1; //Avoid division by 0

  CinematicReset;

  fZoom := 1;
  ReleaseScrollKeys;
  gSoundPlayer.UpdateListener(fPosition.X, fPosition.Y);
  if gScriptSounds <> nil then
    gScriptSounds.UpdateListener(fPosition.X, fPosition.Y);
  Resize(aWidth, aHeight);
end;


procedure TKMViewport.SetZoom(aZoom: Single);
begin
  fZoom := EnsureRange(aZoom, 0.01, 8);
  //Limit the zoom to within the map boundaries
  if fViewportClip.X/CELL_SIZE_PX/fZoom > fMapX then fZoom := fViewportClip.X/CELL_SIZE_PX/(fMapX-1);
  if fViewportClip.Y/CELL_SIZE_PX/fZoom > fMapY then fZoom := fViewportClip.Y/CELL_SIZE_PX/ fMapY;
  SetPosition(fPosition); //To ensure it sets the limits smoothly
end;


procedure TKMViewport.ResetZoom;
begin
  Zoom := 1;
end;


procedure TKMViewport.Resize(NewWidth, NewHeight: Integer);
begin
  fViewRect.Left   := TOOLBAR_WIDTH;
  fViewRect.Top    := 0;
  fViewRect.Right  := NewWidth;
  fViewRect.Bottom := NewHeight;

  fViewportClip.X := fViewRect.Right-fViewRect.Left;
  fViewportClip.Y := fViewRect.Bottom-fViewRect.Top;

  SetZoom(fZoom); //View size has changed and that affects Zoom restrictions
end;


procedure TKMViewport.ResizeMap(aMapX, aMapY: Word; aTopHill: Single);
begin
  fMapX := aMapX;
  fMapY := aMapY;
  fTopHill := aTopHill;
  SetPosition(fPosition); //EnsureRanges
end;


function TKMViewport.GetPosition: TKMPointF;
begin
  Result.X := EnsureRange(fPosition.X, 1, fMapX);
  Result.Y := EnsureRange(fPosition.Y, 1, fMapY);
end;


procedure TKMViewport.SetPosition(const Value: TKMPointF);
var PadTop, TilesX, TilesY: Single;
begin
  PadTop := fTopHill + 0.75; //Leave place on top for highest hills + 1 unit

  TilesX := fViewportClip.X/2/CELL_SIZE_PX/fZoom;
  TilesY := fViewportClip.Y/2/CELL_SIZE_PX/fZoom;

  fPosition.X := EnsureRange(Value.X, TilesX, fMapX - TilesX - 1);
  fPosition.Y := EnsureRange(Value.Y, TilesY - PadTop, fMapY - TilesY - 1); //Top row should be visible
  gSoundPlayer.UpdateListener(fPosition.X, fPosition.Y);
  if gScriptSounds <> nil then
    gScriptSounds.UpdateListener(fPosition.X, fPosition.Y);
end;


//Acquire boundaries of area visible to user (including mountain tops from the lower tiles)
//TestViewportClipInset is for debug, allows to see if everything gets clipped correct
function TKMViewport.GetClip: TKMRect;
begin
  Result.Left   := Math.max(Round(fPosition.X-(fViewportClip.X/2-fViewRect.Left+TOOLBAR_WIDTH)/CELL_SIZE_PX/fZoom), 1);
  Result.Right  := Math.min(Round(fPosition.X+(fViewportClip.X/2+fViewRect.Left-TOOLBAR_WIDTH)/CELL_SIZE_PX/fZoom)+1, fMapX-1);
  Result.Top    := Math.max(Round(fPosition.Y-fViewportClip.Y/2/CELL_SIZE_PX/fZoom), 1);
  Result.Bottom := Math.min(Round(fPosition.Y+fViewportClip.Y/2/CELL_SIZE_PX/fZoom)+4, fMapY-1);

  if TEST_VIEW_CLIP_INSET then
    Result := KMRectGrow(Result, -5);
end;


//Same as above function but with some values changed to suit minimap
function TKMViewport.GetMinimapClip: TKMRect;
begin
  Result.Left   := Math.max(round(fPosition.X-(fViewportClip.X/2-fViewRect.Left+TOOLBAR_WIDTH)/CELL_SIZE_PX/fZoom)+1, 1);
  Result.Right  := Math.min(round(fPosition.X+(fViewportClip.X/2+fViewRect.Left-TOOLBAR_WIDTH)/CELL_SIZE_PX/fZoom)+1, fMapX);
  Result.Top    := Math.max(round(fPosition.Y-fViewportClip.Y/2/CELL_SIZE_PX/fZoom)+2, 1);
  Result.Bottom := Math.min(round(fPosition.Y+fViewportClip.Y/2/CELL_SIZE_PX/fZoom), fMapY);
end;

<commit_msg>VIewport formatting<commit_after>unit KM_Viewport;
{$I KaM_Remake.inc}
interface
uses
  {$IFDEF MSWINDOWS} Windows, {$ENDIF}
  KM_CommonClasses, KM_Points;


type
  TKMViewport = class
  private
    fMapX, fMapY: Word;
    fTopHill: Single;
    fPosition: TKMPointF;
    fScrolling: Boolean;
    fScrollStarted: Cardinal;
    fViewportClip: TPoint;
    fViewRect: TKMRect;
    fZoom: Single;
    fPanTo, fPanFrom: TKMPointF;
    fPanImmidiately : Boolean;
    fPanDuration, fPanProgress: Cardinal;
    function GetPosition: TKMPointF;
    procedure SetPosition(const Value: TKMPointF);
    procedure SetZoom(aZoom: Single);
  public
    ScrollKeyLeft, ScrollKeyRight, ScrollKeyUp, ScrollKeyDown, ZoomKeyIn, ZoomKeyOut: boolean;
    constructor Create(aWidth, aHeight: Integer);

    property Position: TKMPointF read GetPosition write SetPosition;
    property Scrolling: Boolean read fScrolling;
    property ViewportClip: TPoint read fViewportClip;
    property ViewRect: TKMRect read fViewRect;
    property Zoom: Single read fZoom write SetZoom;

    procedure ResetZoom;
    procedure Resize(NewWidth, NewHeight: Integer);
    procedure ResizeMap(aMapX, aMapY: Word; aTopHill: Single);
    function GetClip: TKMRect; //returns visible area dimensions in map space
    function GetMinimapClip: TKMRect;
    procedure ReleaseScrollKeys;
    procedure GameSpeedChanged(aFromSpeed, aToSpeed: Single);
    function MapToScreen(const aMapLoc: TKMPointF): TKMPoint;
    procedure PanTo(const aLoc: TKMPointF; aTicksCnt: Cardinal);
    procedure CinematicReset;

    procedure Save(SaveStream: TKMemoryStream);
    procedure Load(LoadStream: TKMemoryStream);

    procedure UpdateStateIdle(aFrameTime: Cardinal; aAllowMouseScrolling: Boolean; aInCinematic: Boolean);
  end;


implementation
uses
  Math, KromUtils,
  KM_Resource, KM_ResCursors,
  KM_Main, KM_GameApp, KM_Game, KM_Sound,
  KM_Defaults, KM_CommonUtils;


{ TKMViewport }
constructor TKMViewport.Create(aWidth, aHeight: Integer);
begin
  inherited Create;

  fMapX := 1; //Avoid division by 0
  fMapY := 1; //Avoid division by 0

  CinematicReset;

  fZoom := 1;
  ReleaseScrollKeys;
  gSoundPlayer.UpdateListener(fPosition.X, fPosition.Y);
  if gScriptSounds <> nil then
    gScriptSounds.UpdateListener(fPosition.X, fPosition.Y);
  Resize(aWidth, aHeight);
end;


procedure TKMViewport.SetZoom(aZoom: Single);
begin
  fZoom := EnsureRange(aZoom, 0.01, 8);
  //Limit the zoom to within the map boundaries
  if fViewportClip.X/CELL_SIZE_PX/fZoom > fMapX then fZoom := fViewportClip.X/CELL_SIZE_PX/(fMapX-1);
  if fViewportClip.Y/CELL_SIZE_PX/fZoom > fMapY then fZoom := fViewportClip.Y/CELL_SIZE_PX/ fMapY;
  SetPosition(fPosition); //To ensure it sets the limits smoothly
end;


procedure TKMViewport.ResetZoom;
begin
  Zoom := 1;
end;


procedure TKMViewport.Resize(NewWidth, NewHeight: Integer);
begin
  fViewRect.Left   := TOOLBAR_WIDTH;
  fViewRect.Top    := 0;
  fViewRect.Right  := NewWidth;
  fViewRect.Bottom := NewHeight;

  fViewportClip.X := fViewRect.Right-fViewRect.Left;
  fViewportClip.Y := fViewRect.Bottom-fViewRect.Top;

  SetZoom(fZoom); //View size has changed and that affects Zoom restrictions
end;


procedure TKMViewport.ResizeMap(aMapX, aMapY: Word; aTopHill: Single);
begin
  fMapX := aMapX;
  fMapY := aMapY;
  fTopHill := aTopHill;
  SetPosition(fPosition); //EnsureRanges
end;


function TKMViewport.GetPosition: TKMPointF;
begin
  Result.X := EnsureRange(fPosition.X, 1, fMapX);
  Result.Y := EnsureRange(fPosition.Y, 1, fMapY);
end;


procedure TKMViewport.SetPosition(const Value: TKMPointF);
var PadTop, TilesX, TilesY: Single;
begin
  PadTop := fTopHill + 0.75; //Leave place on top for highest hills + 1 unit

  TilesX := fViewportClip.X/2/CELL_SIZE_PX/fZoom;
  TilesY := fViewportClip.Y/2/CELL_SIZE_PX/fZoom;

  fPosition.X := EnsureRange(Value.X, TilesX, fMapX - TilesX - 1);
  fPosition.Y := EnsureRange(Value.Y, TilesY - PadTop, fMapY - TilesY - 1); //Top row should be visible
  gSoundPlayer.UpdateListener(fPosition.X, fPosition.Y);
  if gScriptSounds <> nil then
    gScriptSounds.UpdateListener(fPosition.X, fPosition.Y);
end;


//Acquire boundaries of area visible to user (including mountain tops from the lower tiles)
//TestViewportClipInset is for debug, allows to see if everything gets clipped correct
function TKMViewport.GetClip: TKMRect;
begin
  Result.Left   := Math.Max(Round(fPosition.X
                         - (fViewportClip.X/2 - fViewRect.Left + TOOLBAR_WIDTH) / CELL_SIZE_PX / fZoom), 1);
  Result.Right  := Math.Min(Round(fPosition.X
                         + (fViewportClip.X/2 + fViewRect.Left - TOOLBAR_WIDTH) / CELL_SIZE_PX / fZoom) + 1, fMapX - 1);
  Result.Top    := Math.Max(Round(fPosition.Y
                         - fViewportClip.Y/2 / CELL_SIZE_PX / fZoom), 1);
  Result.Bottom := Math.Min(Round(fPosition.Y
                         + fViewportClip.Y/2 / CELL_SIZE_PX / fZoom) + 4, fMapY - 1);

  if TEST_VIEW_CLIP_INSET then
    Result := KMRectGrow(Result, -5);
end;


//Same as above function but with some values changed to suit minimap
function TKMViewport.GetMinimapClip: TKMRect;
begin
  Result.Left   := Math.max(round(fPosition.X-(fViewportClip.X/2-fViewRect.Left+TOOLBAR_WIDTH)/CELL_SIZE_PX/fZoom)+1, 1);
  Result.Right  := Math.min(round(fPosition.X+(fViewportClip.X/2+fViewRect.Left-TOOLBAR_WIDTH)/CELL_SIZE_PX/fZoom)+1, fMapX);
  Result.Top    := Math.max(round(fPosition.Y-fViewportClip.Y/2/CELL_SIZE_PX/fZoom)+2, 1);
  Result.Bottom := Math.min(round(fPosition.Y+fViewportClip.Y/2/CELL_SIZE_PX/fZoom), fMapY);
end;


{'fraction_non_alphanumeric': 0.10425764192139737, 'fraction_numerical': 0.007641921397379912, 'mean_word_length': 4.383447600391773, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3784581', 'n_tokens_mistral': 4133, 'n_tokens_neox': 3906, 'n_words': 879}