unit u_map;

interface

uses zglHeader, Graphics, u_const, zglParticleEngine, Classes,
    zglGui;

type

  TStage = class
    Name, ImageName, Data, Music: PAnsiString;
    Image: zglPTexture;
  end;

  TBlock = (blNone, blStatic, blPortable, blGlass,
    blBG, blField, blButton, blWire, blDoor, blGun, blReceiver,
    blInfo);
  TBulletClass = (bcBluePortal, bcOrangePortal, bcEnergyBall);

  PMapItem = ^TMapItem;
  TMapItem = record
    Block: TBlock;
    Tile, SpTile, SpLastTile: Byte;
    Tag: integer;
    Time: integer;
    Data, Nearbors, Wire, Fields: byte;
    Wall, BWall, Finish: boolean;
    X, Y: integer;
  end;

  TMap = class;


  TBullet = class
    private
      ClassOf: TBulletClass;
      X, Y, Angle: single;
      Counter, Speed: Single;
    public
      constructor Create(pX, pY, pAngle, pSpeed: Single;
         ClassOf_: TBulletClass);
      procedure Update(dt: double);
      destructor Destroy; override;
  end;

  TMap = class(TObject)
    public
      fWidth, fHeight: word;
      fPlayer: TObject;
      fLoaded: Boolean;
      fData: array of array of TMapItem;
      fBodies, fBullets, fPortals: TList;
      property Player: TObject read fPlayer;
      property Loaded: boolean read fLoaded;
      property Portals: TList read fPortals;
      property Width: word read fWidth;
      property Height: word read fHeight;
      procedure Unload;
      procedure CreateBullet(X, Y, Angle, Speed: Single; ClassOf: TBulletClass);
      procedure Draw(dX, dY, dAngle: Single; Zoom: Byte; DrawPortals: boolean);
      procedure Update(dt: Double);
      procedure LoadFromFile(Stage: TStage);
      constructor Create;
      destructor Destroy; override;
  end;

var Map: TMap;
    Angles: array[0..3] of record
                             X, Y: Integer;
                             ax, ay, bx, by: single;
                             angle, invert, code: integer;
                           end = (
      (X: 1; Y: 0; ax: 0.5; ay: -0.5; bx: 0.5; by: 0.5;
        angle: 0; invert: 2; code: 1),
      (X: 0; Y: 1; ax: -0.5; ay: 0.5; bx: 0.5; by: 0.5;
        angle: 90; invert: 3; code: 2),
      (X: -1; Y: 0; ax: -0.5; ay: -0.5; bx: -0.5; by: 0.5;
        angle: 180; invert: 0; code: 4),
      (X: 0; Y: -1; ax: -0.5; ay: -0.5; bx: 0.5; by: -0.5;
        angle: 270; invert: 1; code: 8)
    );
    blEffects: array[bcBluePortal .. bcEnergyBall] of record
      update, destroy, peffect: peTPaticleSet;
      portal: TObject;
      exit: TBulletClass;
      sndShoot, sndExit: zglPSound;
    end;
    sndField, sndReceiverGet, dooropen, doorclose: zglPSound;
    portalOpen: array[0..2] of zglPSound;
    portalsInit, portalsField, portalsWire, receiver: peTPaticleSet;

implementation

uses u_game, u_portal, u_player, u_body;

constructor TBullet.Create;
begin
  ClassOf := ClassOf_;
  Angle := pAngle;
  X := pX;
  Y := pY;
  Speed := pSpeed;
  Map.fBullets.Add(self);
end;

procedure TBullet.Update;
var pX, pY, i, r: integer;
    oX, oY: Single;
begin

  oX := cos(Angle * deg2rad) * Speed * dt;
  oY := sin(Angle * deg2rad) * Speed * dt;

  Counter := Counter + dt;
  if Counter > 4 then begin
    Counter := 0;
    blEffects[ClassOf].update.AddEffect(
      X * BLOCKSIZE, Y * BLOCKSIZE, round(Angle - 180));
  end;
  if (X < 0) or (Y < 0) or (X > Map.fWidth - 1) or (Y > Map.fHeight - 1) then begin
    Map.fBullets.Remove(self);
    free;
    exit;
  end;
  pX := Trunc(X + oX); pY := Trunc(Y + oY);

  case ClassOf of
    bcEnergyBall: begin
      if Map.fData[pY][pX].Block = blReceiver then begin
        if Map.fData[pY][pX].Tile = 1 then begin
          snd_Play(sndReceiverGet);
          Map.fData[pY][pX].Tile := 2;
        end;
      end;
    end;
  end;

  if Map.fData[pY][pX].BWall then begin
    if Map.fData[pY][pX].Block = blPortable then begin
      if Assigned(blEffects[ClassOf].portal) then begin
        blEffects[ClassOf].portal.Free;
        Map.fPortals.Remove(blEffects[ClassOf].portal);
        blEffects[ClassOf].portal := nil;
      end;
      i := round(m_Angle(X, Y, pX + 0.5, pY + 0.5));
      if i > 360 then i := i - 360;
      if i < 0 then i := i + 360;

      if (i < 45) or (i > 315) then
        r := 0
      else if (i > 45) and (i < 135) then
        r := 1
      else if (i > 135) and (i < 225) then
        r := 2
      else
        r := 3;
      blEffects[ClassOf].portal := TPortal.Create(Map, X + oX, Y + oY,
        r, ClassOf);
      Map.fPortals.Add(blEffects[ClassOf].portal);
    end;
    Map.fBullets.Remove(self);
    free;
    exit;
  end;

  X := X + oX;
  Y := Y + oY;

end;

destructor TBullet.Destroy;
var i: Integer;
begin
  blEffects[ClassOf].destroy.AddEffect(
      X * BLOCKSIZE, Y * BLOCKSIZE, round(Angle - 180));

  inherited;
end;



procedure TMap.CreateBullet(X, Y, Angle, Speed: Single; ClassOf: TBulletClass);
var i: integer;
begin
  snd_Play(blEffects[ClassOf].sndShoot);
  TBullet.Create(X, Y, Angle, Speed, ClassOf);
  case ClassOf of
    bcBluePortal, bcOrangePortal: begin
      for i := 0 to PLAYER_UPDATES - 1 do begin
        TPlayer(fPlayer).fLastPos[i].X :=
          TPlayer(fPlayer).fLastPos[i].X + m_Cos(Round(Angle)) * 2 * (1 - i / PLAYER_UPDATES);
        TPlayer(fPlayer).fLastPos[i].Y :=
          TPlayer(fPlayer).fLastPos[i].Y + m_Sin(Round(Angle)) * 2 * (1 - i / PLAYER_UPDATES);
      end;
    end;
    bcEnergyBall: begin
    end;
  end;
end;

procedure TMap.LoadFromFile(Stage: TStage);
var bmp: TBitmap;
    i, j, k: integer;
begin
  fLoaded := true;
  snd_StopFile(mscMainMenu);
  mscMainMenu := snd_PlayFile(Stage.Music^, true);
  bmp := TBitmap.Create;
  bmp.LoadFromFile(Stage.Data^);
  fWidth := bmp.Width;
  fHeight := bmp.Height;
  SetLength(fData, fHeight, fWidth);
  for j := 0 to fHeight - 1 do begin
    for i := 0 to fWidth - 1 do begin
      fData[j][i].Tile := Random(4) + 1;
      fData[j][i].Time := 0;
      fData[j][i].Block := blNone;
      fData[j][i].Finish := false;
      fData[j][i].X := i;
      fData[j][i].Y := j;
      case bmp.Canvas.Pixels[i, j] of
        $000000: begin
          fData[j][i].Block := blStatic;
        end;
        $404040: begin
          fData[j][i].Block := blPortable;
        end;
        $006AFF: begin
          fData[j][i].Block := blGlass;
        end;
        $DC00FF: begin
          fData[j][i].Block := blBG;
        end;
        $FF2600: begin
          fData[j][i].Block := blField;
        end;
        $37007F: begin
          fData[j][i].Finish := true;
          fData[j][i].Block := blBG;
        end;
        $FF9400: begin
          TPlayerBestFriend.Create(i, j);
          fData[j][i].Block := blBG;
        end;
        $7F0021: begin
          fData[j][i].Block := blGun;
        end;
        $7FFFA5: begin
          fData[j][i].Block := blReceiver;
          fData[j][i].Tile := 1;
        end;
        $5B3F7F: begin
          if chHelp.Checked then begin
            fData[j][i].Block := blInfo;
            fData[j][i].Tile := 1;
          end else
            fData[j][i].Block := blBG;
        end;
        $763F7F: begin
          if chHelp.Checked then begin
            fData[j][i].Block := blInfo;
            fData[j][i].Tile := 2;
          end else
            fData[j][i].Block := blBG;
        end;
        $7F3F6B: begin
          if chHelp.Checked then begin
            fData[j][i].Block := blInfo;
            fData[j][i].Tile := 3;
          end else
            fData[j][i].Block := blBG;
        end;
        $7F3F50: begin
          if chHelp.Checked then begin
            fData[j][i].Block := blInfo;
            fData[j][i].Tile := 4;
          end else
            fData[j][i].Block := blBG;
        end;
        $FF0048: begin
          fData[j][i].Block := blButton;
          fData[j][i].Tile := 1;
        end;
        $007F26: begin
          fData[j][i].Block := blWire;
        end;
        $00D8FF: begin
          fData[j][i].Block := blDoor;
          fData[j][i].SpTile := 8;
          fData[j][i].Tag := 1;
        end;
        $006A7F: begin
          fData[j][i].Block := blDoor;
          fData[j][i].SpTile := 8;
          fData[j][i].Tag := -1;
        end;
        $0000FF: begin
          fPlayer := TPlayer.Create(i, j);
          fData[j][i].Block := blBG;
        end;
      end;
      fData[j][i].Wall := fData[j][i].Block in [blStatic, blPortable, blGlass, blWire];
      fData[j][i].BWall := fData[j][i].Block in [blStatic, blPortable, blField, blWire];
    end;
  end;
  for j := 1 to fHeight - 2 do begin
    for i := 1 to fWidth - 2 do begin
     fData[j][i].Data :=
       (Byte(not fData[j][i + 1].Wall) +
       Byte(not fData[j + 1][i].Wall) * 2 +
       Byte(not fData[j][i - 1].Wall) * 4 +
       Byte(not fData[j - 1][i].Wall) * 8);
     fData[j][i].Fields :=
       (Byte(fData[j][i + 1].Block = blBG) +
       Byte(fData[j + 1][i].Block = blBG) * 2 +
       Byte(fData[j][i - 1].Block = blBG) * 4 +
       Byte(fData[j - 1][i].Block = blBG) * 8);
     fData[j][i].Wire := 15 -
       (Byte(fData[j][i + 1].Block in [blButton, blWire, blDoor]) +
       Byte(fData[j + 1][i].Block in [blButton, blWire, blDoor]) * 2 +
       Byte(fData[j][i - 1].Block in [blButton, blWire, blDoor]) * 4 +
       Byte(fData[j - 1][i].Block in [blButton, blWire, blDoor]) * 8);
     fData[j][i].Nearbors := fData[j][i].Data;
      case fData[j][i].Block of
        blDoor: begin
          for k := 0 to DOOR_HEIGHT - 1 do begin
            fData[j - k * fData[j][i].Tag][i].Wall := true;
            fData[j - k * fData[j][i].Tag][i].BWall := true;
          end;
        end;
      end;
    end;
  end;
  bmp.Free;
end;

procedure TMap.Draw;
var i, j, jb, je, ib, ie: integer;
    a, flip: integer;
    bc: TBulletClass;
    p: TPortal;
    Cam: zglTCamera2D;
begin
  if not fLoaded then
    Exit;
  Cam.Angle := 0;
  cam2d_init(Cam);
  Cam.Zoom.X := Zoom;
  Cam.Zoom.Y := Zoom;

  Cam.X := dX;
  Cam.Y := dY;
  Cam.Angle := dAngle;
  cam2d_Set(@Cam);

  jb := Round(Max(dY / BLOCKSIZE, 0));
  ib := Round(Max(dX / BLOCKSIZE, 0));
  je := Round(Min(dY / BLOCKSIZE + sY div (BLOCKSIZE), fHeight - 1));
  ie := Round(Min(dX / BLOCKSIZE + sX div (BLOCKSIZE), fWidth - 1));

  for j := jb to je do
    for i := ib to ie do begin
      if Byte(fData[j][i].Block) > 0 then begin
        asprite2d_Draw(txTiles, i  * BLOCKSIZE, j * BLOCKSIZE,
          BLOCKSIZE, BLOCKSIZE, 0,
          (Byte(fData[j][i].Block) - 1) * 4 + fData[j][i].Tile);
      end;
    end;
 for j := jb to je do
    for i := ib to ie do begin
      case fData[j][i].Block of
          blDoor: begin
            case fData[j][i].Tag of
              1: begin
                asprite2d_Draw(txDoor,
                  i  * BLOCKSIZE, (j - DOOR_HEIGHT + 1) * BLOCKSIZE,
                  16, 64, 0, fData[j][i].SpTile);
              end;
              else begin
                asprite2d_Draw(txDoor,
                  i  * BLOCKSIZE, j  * BLOCKSIZE,
                  16, 64, 0, fData[j][i].SpTile);
              end;
            end;

          end;
        end;
    end;

  for i := fBodies.Count - 1 downto 0 do
    TBody(fBodies.Items[i]).Draw;
  for i := fPortals.Count - 1 downto 0 do
    TPortal(fPortals.Items[i]).Draw(DrawPortals);
  PE.Draw;
  if DrawPortals then

  for i := fPortals.Count - 1 downto 0 do begin
    p :=  fPortals.Items[i];
    with p do begin
      if fExit = nil then
        Continue;
      if (fAngle = fExit.fAngle) then begin
        case fAngle of
          0, 2: begin
            flip := FX2D_FLIPY;
          end;
          else begin
            flip := FX2D_FLIPX;
          end;
        end;
      end else begin
        flip := 0;
      end;
      a := fAngle * 90 - round(m_Angle(fX, fY,
                     TPlayer(fMap.fPlayer).fPosition.X,
                     TPlayer(fMap.fPlayer).fPosition.Y));
      if a > 360 then a := a - 360;
      if a < 0 then a := a + 360;
      if (a > 135) and (a < 225) then
        ssprite2d_Draw(fRTarget.Surface,
          Round(fX * BLOCKSIZE - PREDRAW_SIZE div 2) ,
          Round(fY * BLOCKSIZE - PREDRAW_SIZE div 2),
          PREDRAW_SIZE, PREDRAW_SIZE, 0, 255, FX_BLEND or flip);
    end;
  end;

  cam2d_Set(nil);
end;

procedure TMap.Update;
var i, j, k, l, jb, je, ib, ie: integer;
    dX, dY: single;
begin
  if not fLoaded then
    Exit;
  dX := Round((TPlayer(Map.Player).Position.X + TPlayer(Map.Player).Position.W / 2)
    * BLOCKSIZE - sX / 2);
  dY := Round((TPlayer(Map.Player).Position.Y + TPlayer(Map.Player).Position.H / 2)
    * BLOCKSIZE - sY / 2);
  //TPlayer(fPlayer).Update(dt);

  jb := Round(Max(dY / BLOCKSIZE, 0));
  ib := Round(Max(dX / BLOCKSIZE, 0));
  je := Round(Min(dY / BLOCKSIZE + sY div BLOCKSIZE, fHeight - 1));
  ie := Round(Min(dX / BLOCKSIZE + sX div BLOCKSIZE, fWidth - 1));

  cnt_ := cnt_ + 1;

  for j := jb to je do
    for i := ib to ie do begin
      if fData[j][i].Time > 0 then begin
        fData[j][i].Time := fData[j][i].Time - 1;
        if fData[j][i].Time <= 0 then begin
          case fData[j][i].Block of
            blButton: begin
              fData[j][i].Tile := 1;
            end;
            blDoor: begin
              for k := 0 to DOOR_HEIGHT - 1 do begin
                fData[j - k * fData[j][i].Tag][i].Wall := false;
                fData[j - k * fData[j][i].Tag][i].BWall := false;
              end;
            end;
          end;
        end;
      end;
      case fData[j][i].Block of
        blGun: begin
          if cnt_ mod 8000 = 0 then
            Map.CreateBullet(
              i + 0.5, j + 0.5,
              0,
              ENBALL_SPEED, bcEnergyBall);
        end;
        blReceiver: begin
          if fData[j][i].Tile = 1 then begin
            if cnt_ mod 100 = 0 then
              receiver.AddEffect((i + 0.5) * BLOCKSIZE,
                  (j + 0.5) * BLOCKSIZE, 180);
            for k := 0 to 3 do
              if fData[j][i].Wire and Angles[k].code = 0 then begin
                fData[j + Angles[k].Y][i + Angles[k].X].Time :=
                  BLOCK_TIME;
              end;
          end;
        end;
        blField: begin
          if cnt_ mod 100 = 0 then
            for k := 0 to 3 do
              if fData[j][i].Fields and Angles[k].code = 0 then
                portalsField.AddEffect(
                  (i + 0.5) * BLOCKSIZE,
                  (j + 0.5) * BLOCKSIZE,
                  Angles[k].angle
                );
        end;
        blButton: begin
          if fData[j][i].Time <= 0 then begin
            for k := 0 to 3 do
              if fData[j][i].Wire and Angles[k].code = 0 then begin
                fData[j + Angles[k].Y][i + Angles[k].X].Time := BLOCK_TIME;
              end;
          end;
        end;
        blDoor: begin
          if fData[j][i].Time > 0 then
            for l := 0 to DOOR_HEIGHT - 1 do begin
              fData[j - l * fData[j][i].Tag][i].Wall := true;
              fData[j - l * fData[j][i].Tag][i].BWall := true;
            end;
          if cnt_ mod 50 = 0 then begin
            if fData[j][i].Time > 0 then begin   
              if fData[j][i].SpTile < 8 then begin
                Inc(fData[j][i].SpTile);
              end;
            end else begin              
              if fData[j][i].SpTile > 1 then begin
                Dec(fData[j][i].SpTile);
              end;
            end;

            if m_Distance(i, j, TPlayer(fPlayer).Position.X,
              TPlayer(fPlayer).Position.Y) < 16 then begin
                if (fData[j][i].SpLastTile = 1) and
                    (fData[j][i].SpTile = 2) then begin
                  snd_Play(doorclose);
                end;
                if (fData[j][i].SpLastTile = 8) and
                    (fData[j][i].SpTile = 7) then begin
                  snd_Play(dooropen);
                end;
            end;
            fData[j][i].SpLastTile :=
              fData[j][i].SpTile;
          end;
        end;
        blWire: begin
          if fData[j][i].Time > 0 then begin
            for k := 0 to 3 do
              if fData[j][i].Wire and Angles[k].code = 0 then begin
                if fData[j + Angles[k].Y][i + Angles[k].X].Time <
                  fData[j][i].Time then begin
                    fData[j + Angles[k].Y][i + Angles[k].X].Time :=
                      fData[j][i].Time;
                  end;
              end;
          end;
          if cnt_ mod 100 = 0 then
            if fData[j][i].Time = 0 then begin
              for k := 0 to 3 do begin
                if fData[j][i].Wire and Angles[k].code = 0 then
                  portalsWire.AddEffect(
                    (i + 0.5) * BLOCKSIZE,
                    (j + 0.5) * BLOCKSIZE,
                    Angles[k].angle
                  );
              end;
            end;
        end;
      end;
    end;
  for i := fBodies.Count - 1 downto 0 do begin
    TBody(fBodies.Items[i]).Update(dt);
    TBody(fBodies.Items[i]).Update(dt);
  end;
  for i := fBullets.Count - 1 downto 0 do
    TBullet(fBullets.Items[i]).Update(dt);
  for i := fPortals.Count - 1 downto 0 do
    TPortal(fPortals.Items[i]).Update(dt);
end;

procedure TMap.Unload;
var i: integer;
begin
  fLoaded := False;
  for i := 0 to fBodies.Count - 1 do
    TBody(fBodies.Items[i]).Free;
  for i := 0 to fBullets.Count - 1 do
    TBullet(fBullets.Items[i]).Free;
  for i := 0 to fPortals.Count - 1 do
    TPortal(fPortals.Items[i]).Free;
  blEffects[bcBluePortal].portal:= nil;
  blEffects[bcOrangePortal].portal := nil;
  fWidth := 0;
  fHeight := 0;
  SetLength(fData, 0, 0);
  fBodies.Clear;
  fBullets.Clear;
  fPortals.Clear;          
end;

constructor TMap.Create;
begin
  fLoaded := false;
  fWidth := 0;
  fHeight := 0;
  fBodies := TList.Create;
  fBullets := TList.Create;
  fPortals := TList.Create;
  with blEffects[bcBluePortal] do begin
    update := peTPaticleSet.Create('blueportal');
    peffect := peTPaticleSet.Create('bluep');
    destroy := peTPaticleSet.Create('blueportal|8');
    exit := bcOrangePortal;
    sndShoot := snd_LoadFromFile('Data/Sound/portalgun_shoot1.wav', 4);
    sndExit := snd_LoadFromFile('Data/Sound/portalexit1.wav', 16);
  end;
  with blEffects[bcOrangePortal] do begin
    update := peTPaticleSet.Create('orangeportal');
    peffect := peTPaticleSet.Create('orangep');
    destroy := peTPaticleSet.Create('orangeportal|8');
    exit := bcBluePortal;
    sndShoot := snd_LoadFromFile('Data/Sound/portalgun_shoot2.wav', 4); ;
    sndExit := snd_LoadFromFile('Data/Sound/portalexit2.wav', 16);
  end;
  with blEffects[bcEnergyBall] do begin
    update := peTPaticleSet.Create('energy');
    peffect := peTPaticleSet.Create('orangep');
    destroy := peTPaticleSet.Create('orangeportal|8');
    sndShoot := snd_LoadFromFile('Data/Sound/starting.wav', 4); ;
  end;

  portalsInit := peTPaticleSet.Create('portalinit|2,portalnew|16');
  portalsField := peTPaticleSet.Create('field');
  portalsWire := peTPaticleSet.Create('wire');
  receiver := peTPaticleSet.Create('receiver');

  portalOpen[0] := snd_LoadFromFile('Data/Sound/portalopen1.wav', 4);
  portalOpen[1] := snd_LoadFromFile('Data/Sound/portalopen2.wav', 4);

  sndField := snd_LoadFromFile('Data/Sound/portalfly.wav', 2);
  sndReceiverGet := snd_LoadFromFile('Data/Sound/receiverget.wav', 2);
  dooropen := snd_LoadFromFile('Data/Sound/dooropen.wav', 2);
  doorclose := snd_LoadFromFile('Data/Sound/doorclose.wav', 2);

end;

destructor TMap.Destroy;
var i: integer;
begin          
  for i := 0 to fBodies.Count - 1 do
    TBody(fBodies.Items[i]).Free;
  fBodies.Free;
  for i := 0 to fBullets.Count - 1 do
    TBullet(fBullets.Items[i]).Free;
  fBullets.Free;
  for i := 0 to fPortals.Count - 1 do
    TPortal(fPortals.Items[i]).Free;
  fPortals.Free;
  inherited;
end;

end.
