unit Level.Map;

interface

uses
    QuadEngine, Vec2f, GlobalTypes, QuadEngine.Color, CustomObject, IniFiles, System.SysUtils, VCL.Imaging.pngimage, ObjectStartPoint;

type
  TMapRange = record
    A, B: TVec2i;
  end;

  TLevelMap = class
  private
    FFields: array of array of TField;
    FWidth: Integer;
    FHeight: Integer;
    FAngle: Single;
    FStartPosition: TVec2f;
    FStartObj: TObjectStartPoint;
    FWorldType: TWorldType;
    function GetField(X, Y: Integer): PField;
    procedure CreateObject(AObjectType: TObjectType; APosition: TVec2i; AID: Integer = 0);
  public
    class var FieldSize: Integer;
    constructor Create(AFileName: WideString; AWorldType: TWorldType);
    destructor Destroy; override;

    procedure Draw;
    procedure DrawTop;
    procedure DrawLightsBarriers(APosition: TVec2f);
    function GetMapRange(APoint, BPoint:  TVec2f): TMapRange;
    function GetFieldToPosition(APosition: TVec2f): PField;
    property StartPosition: TVec2f read FStartPosition;
    property StartObj: TObjectStartPoint read FStartObj;
    property Fields[X, Y: Integer]: PField read GetField;
    property Width: Integer read FWidth;
    property Height: Integer read FHeight;
    property Angle: Single read FAngle;
    property WorldType: TWorldType read FWorldType;
  end;

implementation

uses
  Resources, Math, ObjectButton, ObjectDoor, ObjectSwitch, ObjectTimer, CustomSubject, ObjectNot, ObjectTrap,
  ObjectConsole, ObjectLihgt, ObjectActionRegion, ObjectIPADConsole, ObjectMonster, ObjectCapacitor, ObjectOr,
  ObjectDamageRegion;

procedure TLevelMap.CreateObject(AObjectType: TObjectType; APosition: TVec2i;  AID: Integer = 0);
begin
  case AObjectType of
    otSrartPoint: FFields[APosition.X, APosition.Y].Obj := TObjectStartPoint.Create(@FFields[APosition.X, APosition.Y], AID);
    otButton: FFields[APosition.X, APosition.Y].Obj := TObjectButtom.Create(@FFields[APosition.X, APosition.Y], AID);
    otSwitch: FFields[APosition.X, APosition.Y].Obj := TObjectSwitch.Create(@FFields[APosition.X, APosition.Y], AID);
    otDoorClose:
      begin
        FFields[APosition.X, APosition.Y].Obj := TObjectDoor.Create(@FFields[APosition.X, APosition.Y], AID);
        TObjectDoor(FFields[APosition.X, APosition.Y].Obj).IsOpened := False;
      end;
    otDoorOpen:
      begin
        FFields[APosition.X, APosition.Y].Obj := TObjectDoor.Create(@FFields[APosition.X, APosition.Y], AID);
        TObjectDoor(FFields[APosition.X, APosition.Y].Obj).IsOpened := True;
      end;
    otSpaceSuit: FFields[APosition.X, APosition.Y].Obj := TCustomSubject.Create(@FFields[APosition.X, APosition.Y], AID);
    otTimer: FFields[APosition.X, APosition.Y].Obj := TObjectTimer.Create(@FFields[APosition.X, APosition.Y], AID);
    otNot: FFields[APosition.X, APosition.Y].Obj := TObjectNot.Create(@FFields[APosition.X, APosition.Y], AID);
    otTrap: FFields[APosition.X, APosition.Y].Obj := TObjectTrap.Create(@FFields[APosition.X, APosition.Y], AID);
    otConsole: FFields[APosition.X, APosition.Y].Obj := TObjectConsole.Create(@FFields[APosition.X, APosition.Y], AID);
    otLight: FFields[APosition.X, APosition.Y].Obj := TObjectLihgt.Create(@FFields[APosition.X, APosition.Y], AID);
    otActionRegion: FFields[APosition.X, APosition.Y].Obj := TObjectActionRegion.Create(@FFields[APosition.X, APosition.Y], AID);
    otIPADConsole: FFields[APosition.X, APosition.Y].Obj := TObjectIPADConsole.Create(@FFields[APosition.X, APosition.Y], AID);
    otMonster: FFields[APosition.X, APosition.Y].Obj := TObjectMonster.Create(@FFields[APosition.X, APosition.Y], AID);
    otCapacitor: FFields[APosition.X, APosition.Y].Obj := TObjectCapacitor.Create(@FFields[APosition.X, APosition.Y], AID);
    otOr: FFields[APosition.X, APosition.Y].Obj := TObjectOr.Create(@FFields[APosition.X, APosition.Y], AID);
    otDamageRegion: FFields[APosition.X, APosition.Y].Obj := TObjectDamageRegion.Create(@FFields[APosition.X, APosition.Y], AID);
  end;
end;

constructor TLevelMap.Create(AFileName: WideString; AWorldType: TWorldType);
var
  X, Y, i, j: Integer;
  ObjectType: TObjectType;
  png: TPngImage;
  ColorLine: PRGBLine;
  AlphaLine: PByteArray;
  ini: TIniFile;
begin
  FieldSize := 24;
  FAngle := 0;//Random(MaxInt) / MaxInt * 360;
  FWorldType := AWorldType;

  png := TPngImage.Create;
  try
    png.LoadFromFile(AFileName + '.png');

    FWidth := png.Width;
    FHeight := png.Height;

    SetLength(FFields, FWidth);
    for i := 0 to FWidth - 1 do
      SetLength(FFields[i], FHeight);

    for Y := 0 to FHeight - 1 do
    begin
      ColorLine := PRGBLine(png.Scanline[Y]);
      AlphaLine := PByteArray(png.AlphaScanline[Y]);
      for X := 0 to FWidth - 1 do
      begin
        Fields[X, Y].Position := TVec2i.Create(X, Y);
        Fields[X, Y].FieldType := TFieldType(ColorLine[X].rgbtGreen);
        Fields[X, Y].Angle := Random(MaxInt) / MaxInt * 360;
        Fields[X, Y].Damage := 0;
        if Fields[X, Y].FieldType = ftTree then
          Fields[X, Y].Frame := Random(7);
        ObjectType := TObjectType(ColorLine[X].rgbtBlue);
        if ObjectType <> otNone then
        begin
          CreateObject(ObjectType, TVec2i.Create(X, Y), AlphaLine[X] * 256 + ColorLine[X].rgbtRed);
          case ObjectType of
            otSrartPoint:
              begin
                FStartPosition := TVec2f.Create(X * FieldSize + FieldSize div 2, Y * FieldSize + FieldSize div 2);
                FStartObj := TObjectStartPoint( Fields[X, Y].Obj);
              end;
          end;
        end;
      end;
    end;
  finally
    png.Free;
  end;

  if FileExists(AFileName + '.ini') then
  begin
    ini := TIniFile.Create(AFileName + '.ini');
    try
      for i := 0 to TCustomObject.ObjectList.Count - 1 do
        TCustomObject.ObjectList[i].Load(ini);
    finally
      ini.Free;
    end;
  end;

  for i := 0 to TCustomObject.ObjectList.Count - 1 do
    if TCustomObject.ObjectList[i].Level = 0 then
      TCustomObject.ObjectList[i].Setlevel(1);

  for i := 0 to TCustomObject.ObjectList.Count - 1 do
    for j := 1 to TCustomObject.ObjectList.Count - 1 do
      if TCustomObject.ObjectList[j - 1].Level > TCustomObject.ObjectList[j].Level then
        TCustomObject.ObjectList.Move(j - 1, j);
      {
  for i := 0 to TCustomObject.ObjectList.Count - 1 do
    TGlobals.QuadLog.Write(PWideChar(IntToStr(TCustomObject.ObjectList[i].Level) + '   ' + IntToStr(TCustomObject.ObjectList[i].ID)));
      }
end;

destructor TLevelMap.Destroy;
var
  X, Y: Integer;
begin
  for Y := 0 to FHeight - 1 do
    for X := 0 to FWidth - 1 do
      if Assigned(FFields[X, Y].Obj) then
        FFields[X, Y].Obj.Free;
end;

procedure TLevelMap.DrawTop;
var
  X, Y: Integer;
  Position: TVec2f;
  Range: TMapRange;
begin
  Position := TGlobals.QuadCamera.GetPosition + TVec2f.Create(TGlobals.GWidth, TGlobals.GHeight) / 2 - TVec2f.Create(TGlobals.GWidth + 200, TGlobals.GWidth + 200) / 2;
  Range := GetMapRange(Position, Position + TVec2f.Create(TGlobals.GWidth + 200, TGlobals.GWidth + 200));
  for Y := Range.A.Y to Range.B.Y do
    for X := Range.A.X to Range.B.X do
    if Fields[X, Y].FieldType <> ftNone then
    begin
      Position := TVec2f.Create(X, Y) * FieldSize;
      case Fields[X, Y].FieldType of
        ftTree: TTextures.Trees.DrawRotFrame(Position + TVec2f.Create(FieldSize div 2, FieldSize div 2), Fields[X, Y].Angle, 0.8, Fields[X, Y].Frame{, $FF555555});
      end;
    end;
end;

procedure TLevelMap.Draw;

  procedure DrawField(AField: PField; APosition: TVec2f);
  const
    TileCount: integer = 10;
  var
    PosUV: TVec2f;
  begin
    PosUV := TVec2f.Create(AField.Position.X mod TileCount / TileCount, AField.Position.Y mod TileCount / TileCount);

    case AField.FieldType of
      ftNone, ftTree, ftStone, ftGlass:
        if Assigned(TTextures.WorldTexture[WorldType].Ground) then
          TTextures.WorldTexture[WorldType].Ground.DrawMap(
            APosition, APosition + TVec2f.Create(1, 1) * FieldSize,
            PosUV, PosUV + TVec2f.Create(1 / TileCount, 1 / TileCount)
          );
      ftFloor:
        if Assigned(TTextures.WorldTexture[WorldType].Floor) then
          TTextures.WorldTexture[WorldType].Floor.DrawMap(
            APosition, APosition + TVec2f.Create(1, 1) * FieldSize,
            PosUV, PosUV + TVec2f.Create(1 / TileCount, 1 / TileCount)
          );
      ftWall: TTextures.Mask.DrawFrame(APosition, 0);
    end;

    TGlobals.QuadRender.SetBlendMode(qbmSrcAlphaAdd);
    if AField.FieldType = ftGlass then
      TTextures.Mask.DrawFrame(APosition, 2);
    TGlobals.QuadRender.SetBlendMode(qbmSrcAlpha);
  end;

var
  X, Y: Integer;
  Position: TVec2f;
  Range: TMapRange;
begin
  Position := TGlobals.QuadCamera.GetPosition + TVec2f.Create(TGlobals.GWidth, TGlobals.GHeight) / 2 - TVec2f.Create(TGlobals.GWidth + 200, TGlobals.GWidth + 200) / 2;
  Range := GetMapRange(Position, Position + TVec2f.Create(TGlobals.GWidth + 200, TGlobals.GWidth + 200));
  for Y := Range.A.Y to Range.B.Y do
    for X := Range.A.X to Range.B.X do
    begin
      Position := TVec2f.Create(X, Y) * FieldSize;
      DrawField(Fields[X, Y], Position);
    end;
  for Y := Range.A.Y to Range.B.Y do
    for X := Range.A.X to Range.B.X do
    begin
      Position := TVec2f.Create(X, Y) * FieldSize;
      case Fields[X, Y].FieldType of
        ftStone:
          if Assigned(TTextures.WorldTexture[WorldType].Rock3) then
            TTextures.WorldTexture[WorldType].Rock3.DrawRot(Position + TVec2f.Create(FieldSize div 2, FieldSize div 2), Fields[X, Y].Angle, 0.8);
      end;
    end;

end;

procedure TLevelMap.DrawLightsBarriers(APosition: TVec2f);
var
  X, Y: Integer;
  Position: TVec2f;
  Range: TMapRange;
  j: Integer;
begin
  TGlobals.QuadRender.SetBlendMode(qbmSrcAlpha);
  TGlobals.QuadCameraLights.SetPosition(APosition - TVec2f.Create(TGlobals.GWidth, TGlobals.GWidth) / 2);
  Range := GetMapRange(
    APosition - TVec2f.Create(TGlobals.GWidth, TGlobals.GWidth) / 2,
    APosition + TVec2f.Create(TGlobals.GWidth, TGlobals.GWidth) / 2
  );
  TGlobals.QuadRender.RenderToTexture(True, TTextures.LightsBarriers, 0, 0, True);
  TGlobals.QuadRender.Rectangle(TVec2f.Zero, TVec2f.Create(TGlobals.LightSize, TGlobals.LightSize), $FFFFFFFF);
  TGlobals.QuadCameraLights.Enable;
  for Y := Range.A.Y to Range.B.Y do
    for X := Range.A.X to Range.B.X do
      if Fields[X, Y].FieldType <> ftNone then
      begin
        Position := TVec2f.Create(X, Y) * FieldSize - 256;  // компенсация РТ
        case Fields[X, Y].FieldType of
          ftStone: TTextures.Mask.DrawRotFrame(Position + TVec2f.Create(FieldSize div 2, FieldSize div 2), 0, 1, 1, $FF000000);
          ftWall: TTextures.Mask.DrawFrame(Position, 0);
          ftTree: TTextures.Mask.DrawRotFrame(Position + TVec2f.Create(FieldSize div 2, FieldSize div 2), 0, 0.7, 1, $FF000000);
        end;
    end;

  TGlobals.QuadCameraLights.Disable;
  TGlobals.QuadRender.RenderToTexture(False);
end;

function TLevelMap.GetMapRange(APoint, BPoint:  TVec2f): TMapRange;
var
  P1, P2: TVec2i;
begin
  P1 := TVec2i.Create(Round(APoint.X) div FieldSize, Round(APoint.Y) div FieldSize);
  P2 := TVec2i.Create(Round(BPoint.X) div FieldSize, Round(BPoint.Y) div FieldSize);
  Result.A.X := Max(0, Min(P1.X, P2.X));
  Result.A.Y := Max(0, Min(P1.Y, P2.Y));
  Result.B.X := Min(FHeight, Max(P1.X, P2.X));
  Result.B.Y := Min(FHeight, Max(P1.Y, P2.Y));
end;

function TLevelMap.GetFieldToPosition(APosition: TVec2f): PField;
var
  p: TVec2i;
begin
  p := TVec2i.Create(Round(APosition.X) div FieldSize, Round(APosition.Y) div FieldSize);
  if (p.X >= 0) and (p.Y >= 0) and (p.X < FWidth) and(p.Y < FHeight) then
    Result := Fields[p.X, p.Y]
  else
    Result := nil;
end;

function TLevelMap.GetField(X, Y: Integer): PField;
begin
  Result := @FFields[X, Y];
end;

end.
