unit GameEngine;

interface

uses
  Windows,
  Messages,
  SysUtils,
  Variants,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  DJX,
  DJXTimer,
  DJXMaterials,
  DJXLights,
  DJXMeshes,
  DJXTextures,
  DJXFonts,
  DJXTexFonts,
  DJXClasses,
  DJXUtils,
  DJXPrimitives,
  DJXRender,
  DJXShaders,
  DJXLandscape,
  DJXEffects,
  DJXFreeCamera,
  DJXEngine,
  DJXPathFinder,
  DJXJet2D,
  Direct3D9,
  DirectShow9,
  D3DX9,
  CSD,
  Math,
  ActiveX;

type
  TGame = class;
  TShip = class;
  TPlayer = class;
  TLaser = class;
  TQuickPlayer = class;
  TQPGraph = class;

  PBodyData = ^TBodyData;
  TBodyData = record
    IsShip: boolean;
    IsPlayer: boolean;
    IsLaser: boolean;
    Ship: TShip;
    Laser: TLaser;
  end;

  TCellType = (
    ctEmpty,
    ctWall,
    ctEnemySpawn
  );

  TCell = record
    CellType: TCellType;
  end;

  TObjType = (
    otBall,
    otBox
  );

  TObj = record
    Pos: TCSVector;
    Size: TCSVector;
    ObjType: TObjType;
  end;

  TMap = record
    Pos: TCSVector;
    GroundLevel: integer;
    CellSize: integer;
    CellCountX: integer;
    CellCountY: integer;
    Cells: array of array of TCell;
    Colys: array of TCSBody;
    Objects: array of TObj;
    ColyCount: integer;
    ObjCount: integer;
    Start: TPoint;
    Finish: TPoint;
    LevelStartTime: DWORD;
    AllysCreated: DWORD;
    EnemiesCreated: DWORD;
    EnemyPortals: array of TPoint;
    PathMap: PDJXPathMap;
    PlayerCreated: boolean;
    MaxAllys: integer;
    MaxEnemies: integer;
    AllyCreateTime: DWORD;
    EnemyDieTime: DWORD;
  end;

  TGameState = (
    gsInit,
    gsMenu,
    gsGame,
    gsWin,
    gsLost
  );

  TGame = class
  public
    DanJetX1: TDanJetX;
    DJXTimer1: TDJXTimer;
    DJXFontList1: TDJXFontList;
    DJXTexFontList1: TDJXTexFontList;
    DJXMeshList1: TDJXMeshList;
    DJXLightList1: TDJXLightList;
    DJXMaterialList1: TDJXMaterialList;
    DJXShaderList1: TDJXShaderList;
    DJXEffectList1: TDJXEffectList;
    DJXTextureList1: TDJXTextureList;
    /////////////////////////////////
    Res: TDJXRes;
    Map: TMap;
    Jet2D: TDJXJet2D;
    PhysWorld: TCSWorld;
    PathFinder: TDJXPathFinder;
    Keys: array[0..High(WORD)] of boolean;
    MDL: boolean;
    MDR: boolean;
    Ships: TDJXEngine;
    SmokeEn: TDJXEngine;
    FlareEn: TDJXEngine;
    LaserEn: TDJXEngine;
    LaserFlashEn: TDJXEngine;
    ExplosionEn: TDJXEngine;
    GravFlashEn: TDJXEngine;
    PhysObjEn: TDJXEngine;
    FlashEn: TDJXEngine;
    Player: TPlayer;
    GameState: TGameState;
    CurTime: DWORD;
    PlayerDead: boolean;
    Score: integer;
    ResLost: boolean;
    ResWin: boolean;
    ResTime: DWORD;
    CurLevel: integer;
    MenuRects: array[0..1] of TRect;
    ShaderEnable: boolean;
    MusicEnable: boolean;
    QuickPlayer: TQuickPlayer;
    /////////////////////////////////
    tex_Wall1: TDJXTexture;
    tex_Wall1NM: TDJXTexture;
    tex_Start: TDJXTexture;
    tex_Finish: TDJXTexture;
    tex_Ship1: TDJXTexture;
    tex_Space1: TDJXTexture;
    tex_Star: TDJXTexture;
    tex_Space: TDJXTexture;
    tex_Ground: TDJXTexture;
    tex_GroundLevel: TDJXTexture;
    tex_Ground2: TDJXTexture;
    tex_Ground2NM: TDJXTexture;
    tex_Flame: TDJXTexture;
    tex_Smoke: TDJXTexture;
    tex_Flare: TDJXTexture;
    tex_Cursor: TDJXTexture;
    tex_Laser: TDJXTexture;
    tex_EnemySpawn: TDJXTexture;
    tex_Bot1: TDJXTexture;
    tex_Enemy1: TDJXTexture;
    tex_Laser2: TDJXTexture;
    tex_Explosion: TDJXTexture;
    tex_Ball: TDJXTexture;
    tex_Box: TDJXTexture;
    tex_TextGravity: TDJXTexture;
    tex_TextNewGame: TDJXTexture;
    tex_TextExit: TDJXTexture;
    fnt_Font1: TDJXFont;
    fnt_Font2: TDJXFont;
    fx_Background: TDJXEffect;
  published
    constructor Create;
    destructor Destroy; override;
    procedure Initialize;
    procedure Finalize;
    procedure Render;
    procedure Process;
    procedure StartGame;
    procedure KDown(Key: WORD);
    procedure KUp(Key: WORD);
    procedure MDown(X, Y: integer; Button: TMouseButton);
    procedure MUp(X, Y: integer; Button: TMouseButton);
    procedure WDown;
    procedure WUp;
    /////////////////////////////////
    procedure LoadMap(Ind: integer);
    procedure UnLoadMap;
    procedure RenderMap;
    procedure RenderMapPS;
    procedure RenderMenu;
    procedure GravPoint(X, Y: single);
    procedure SetUpLevel;
  end;
  
  TQuickPlayer = class
  private
    m_Graphs: TList;
  protected
    procedure FreeGraphs;
    function AddGraph: TQPGraph;
  public
    constructor Create;
    destructor Destroy; override;
    procedure PlayFile(f: string; Looped: boolean = false);
    procedure Update;
  end;

  TQPGraph = class
  private
    m_Graph: IGraphBuilder;
    m_Control: IMediaControl;
    m_Event: IMediaEventEx;
    m_Position: IMediaPosition;
    m_Playing: boolean;
    m_Looped: boolean;
    m_PlayTime: DWORD;
  public
    constructor Create;
    destructor Destroy; override;
    property Looped: boolean read m_Looped write m_Looped;
    procedure OpenFile(f: WideString);
    procedure Play;    
    procedure Stop;
    procedure Pause;
    function IsPlaying: boolean;
  end;

  TShip = class (TDJXEngineItem)
  private
    Health: integer;
    MaxHealth: integer;
    Damage: integer;
    MaxSpeed: single;
    MoveUp: boolean;
    MoveDown: boolean;
    MoveLeft: boolean;
    MoveRight: boolean;
    Acc: single;
    B: TCSBody;
    SmokeCreateTime: DWORD;
    SmokeCreateLength: DWORD;
    BodyData: TBodyData;
    FlareCreateTime: DWORD;
    FlareCreateLength: DWORD;
    ShotTime: DWORD;
    ShotLength: DWORD;
    ShipTex: TDJXTexture;
    IsDead: boolean;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
    procedure SetPos(x, y: single);
    procedure Shoot(TargetX, TargetY: single);
  end;

  TPlayer = class (TShip)
  private
    GravTime: DWORD;
    GravLength: DWORD;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
  end;

  TBot = class (TShip)
  private
    AITime: DWORD;
    AILength: DWORD;
    TargetPos: TCSVector;
    TargetShip: TShip;
    WayPoints: TDJXPathWayPoints;
    CurWayPoint: integer;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
    procedure DoAI; virtual;
    procedure DoAIGoTo(DestX, DestY: single);
    function DoAIShipVisible(Ship: TShip): boolean;
  end;

  TAlly = class (TBot)
  private
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
    procedure DoAI; override;
  end;

  TEnemy = class (TBot)
  private
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
    procedure DoAI; override;
  end;

  TSmoke = class (TDJXEngineItem)
  private
    TimeCreate: DWORD;
    TimeLive: DWORD;
    Alpha: byte;
    Scale: single;
    Rotation: single;
    RotationSpeed: single;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
  end;

  TExplosion = class (TDJXEngineItem)
  private
    TimeCreate: DWORD;
    TimeLive: DWORD;
    Alpha: byte;
    Scale: single;
    Rotation: single;
    RotationSpeed: single;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
  end;

  TFlare = class (TDJXEngineItem)
  private
    TimeCreate: DWORD;
    TimeLive: DWORD;
    Alpha: byte;
    Scale: single;
    Rotation: single;
    RotationSpeed: single;
    VelX, VelY: single;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
  end;

  TLaser = class (TDJXEngineItem)
  private
    Ang: single;
    B: TCSBody;
    BodyData: TBodyData;
    Speed: single;
    Damage: integer;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
    procedure InitLaser;
  end;

  TLaserFlash = class (TDJXEngineItem)
  private
    TimeCreate: DWORD;
    TimeLive: DWORD;
    Alpha: byte;
    Scale: single;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
  end;

  TGravFlash = class (TDJXEngineItem)
  private
    TimeCreate: DWORD;
    TimeLive: DWORD;
    Alpha: byte;
    Scale: single;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
  end;

  TFlash = class (TDJXEngineItem)
  private
    TimeCreate: DWORD;
    TimeLive: DWORD;
    Alpha: byte;
    Scale: single;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
  end;

  TPhysObj = class (TDJXEngineItem)
  private
    Obj: TObj;
    B: TCSBody;
    W, H: single;
    ObjTex: TDJXTexture;
  public
    constructor Create(Engine: TDJXEngine); override;
    destructor Destroy; override;
    procedure Process(SpeedFactor: single); override;
    procedure Draw; override;
    procedure InitObj;
  end;

  function GetParam(Str: string; SubStr: string; Param: integer): string;

var
  Game: TGame;

const
  Gravity = 0.02;

implementation

uses
  Unit1;

//CSD Callbacks BEGIN
procedure BodyCollide(const Body: TCSBody; const ColBody: TCSBody; const Contacts: TCSChain; const R: TCSResponse);
var
  bd, bd2: PBodyData;
  i: integer;
  c: PCSContact;
  Dam: integer;
begin
  if not Assigned(Body.Data) then Exit;
  bd := PBodyData(Body.Data);
  bd2 := PBodyData(ColBody.Data);
  if bd^.IsLaser then
  begin
    if Assigned(ColBody.Data) and PBodyData(ColBody.Data)^.IsShip then
    PBodyData(ColBody.Data)^.Ship.Health := PBodyData(ColBody.Data)^.Ship.Health - bd^.Laser.Damage;
    Contacts.Reset;
    Contacts.Next;
    c := PCSContact(Contacts.Current);
    for i := 0 to 5 do
    with TFlare.Create(Game.FlareEn) do
    begin
      x := c^.Pos.x;
      y := c^.Pos.y;
    end;
    if Assigned(bd2) and (bd2^.IsShip) and (bd2^.Ship is TEnemy) then
    Game.Score := Game.Score + 1;
    bd^.Laser.Dead;
    with TLaserFlash.Create(Game.LaserFlashEn) do
    begin
      x := c^.Pos.x;
      y := c^.Pos.y;
    end;
  end;
  if bd^.IsShip then
  begin
    if ColBody.Hardness > 0.5 then
    Dam := Round(Abs(Body.VelLinear^ * R.N))
    else
    Dam := 0;
    if not ColBody.IsStatic
    and not Assigned(ColBody.Data) then Dam := 0;
    if ColBody.IsStatic then Dam := Dam * 10;
    bd^.Ship.Health := bd^.Ship.Health - Dam;
    if (Game.CurTime - bd^.Ship.FlareCreateTime > bd^.Ship.FlareCreateLength) then
    begin
      Contacts.Reset;
      while Contacts.Next <> nil do
      begin
        c := PCSContact(Contacts.Current);
        for i := 0 to Dam - 1 do
        with TFlare.Create(Game.FlareEn) do
        begin
          x := c^.Pos.x;
          y := c^.Pos.y;
        end;
      end;
      bd^.Ship.FlareCreateTime := Game.CurTime;
    end;
  end;
end;

procedure PreColyCallback(const Body1, Body2: TCSBody; var Proceed: boolean);
begin
  if Assigned(Body1.Data) and Assigned(Body2.Data)
  and (
    (
      PBodyData(Body1.Data)^.IsLaser
      and
      PBodyData(Body2.Data)^.IsShip
      and
      (PBodyData(Body1.Data)^.Ship = PBodyData(Body2.Data)^.Ship)
    )
    or
    (
      PBodyData(Body2.Data)^.IsLaser
      and
      PBodyData(Body1.Data)^.IsShip
      and
      (PBodyData(Body1.Data)^.Ship = PBodyData(Body2.Data)^.Ship)
    )
  ) then
  Proceed := false
  else
  Proceed := true;
end;

procedure ColyCallback(const Body1, Body2: TCSBody; const Contacts: TCSChain; const R: TCSResponse);
begin
  BodyCollide(Body1, Body2, Contacts, R);
  BodyCollide(Body2, Body1, Contacts, R);
end;
//CSD Callbacks END

//TGame BEGIN
constructor TGame.Create;
begin
  inherited Create;
  ZeroMemory(@Keys, Length(Keys));
  Ships := TDJXEngine.Create;
  SmokeEn := TDJXEngine.Create;
  FlareEn := TDJXEngine.Create;
  LaserEn := TDJXEngine.Create;
  LaserFlashEn := TDJXEngine.Create;
  ExplosionEn := TDJXEngine.Create;
  PhysObjEn := TDJXEngine.Create;
  GravFlashEn := TDJXEngine.Create;
  FlashEn := TDJXEngine.Create;
  GameState := gsInit;
  CurTime := 0;
end;

destructor TGame.Destroy;
begin
  Ships.FreeItems;
  Ships.Free;
  SmokeEn.FreeItems;
  SmokeEn.Free;
  FlareEn.FreeItems;
  FlareEn.Free;
  LaserEn.FreeItems;
  LaserEn.Free;
  LaserFlashEn.FreeItems;
  LaserFlashEn.Free;
  ExplosionEn.FreeItems;
  ExplosionEn.Free;
  PhysObjEn.FreeItems;
  PhysObjEn.Free;
  GravFlashEn.FreeItems;
  GravFlashEn.Free;
  FlashEn.FreeItems;
  FlashEn.Free;
  inherited Destroy;
end;

procedure TGame.Initialize;
var
  i, x, y: integer;
  sc: single;
begin
  Randomize;
  PathFinder := TDJXPathFinder.Create;
  PathFinder.WallBend := true;
  Jet2D := TDJXJet2D.Create(DanJetX1);
  PhysWorld := TCSWorld.Create;
  PhysWorld.ColyCallback := ColyCallback;
  PhysWorld.PreColyCallback := PreColyCallback;

  Res := DanJetX1.Resources.LinkResource(
    'Gravity.djx', 'Gravity', 'DJX ENCRYPTION KEY'
  );
  tex_Wall1 := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Wall1', 'Wall1'
  );
  tex_Wall1NM := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Wall1NM', 'Wall1NM'
  );
  tex_Start := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Start', 'Start'
  );
  tex_Finish := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Finish', 'Finish'
  );
  tex_Ship1 := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Ship1', 'Ship1'
  );
  tex_Space1 := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Space', 'Space1'
  );
  tex_Star := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Star', 'Star'
  );
  tex_Ground := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Ground', 'Ground', 8
  );
  tex_Ground.GenerateMipMaps;
  tex_GroundLevel := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'GroundLevel', 'GroundLevel'
  );
  tex_Ground2 := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Ground2', 'Ground2'
  );
  tex_Ground2NM := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Ground2NM', 'Ground2NM'
  );
  tex_Flame := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Flame', 'Flame'
  );
  tex_Smoke := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Smoke', 'Smoke'
  );
  tex_Flare := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Flare', 'Flare'
  );
  tex_Cursor := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Cursor', 'Cursor'
  );
  tex_Laser := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Laser', 'Laser'
  );
  tex_EnemySpawn := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'EnemySpawn', 'EnemySpawn'
  );
  tex_Bot1 := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Bot1', 'Bot1'
  );
  tex_Enemy1 := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Enemy1', 'Enemy1'
  );
  tex_Laser2 := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Laser2', 'Laser2'
  );
  tex_Explosion := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Explosion', 'Explosion'
  );
  tex_Ball := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Ball', 'Ball'
  );
  tex_Box := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'Box', 'Box'
  );
  tex_TextGravity := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'TextGravity', 'TextGravity'
  );
  tex_TextNewGame := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'TextNewGame', 'TextNewGame'
  );
  tex_TextExit := DJXTextureList1.CreateTextureFromRes(
    'Gravity', 'Textures', 'TextExit', 'TextExit'
  );
  
  tex_Space := DJXTextureList1.CreateRenderTargetTexture(
    512, 512, 'Space'
  );

  fnt_Font1 := DJXFontList1.CreateFont('Font1', 'Arial', 32);
  fnt_Font2 := DJXFontList1.CreateFont('Font2', 'Times New Roman', 16);

  fx_Background := DJXEffectList1.LoadEffectFromFile('bg.fx', 'bg');

  DanJetX1.BeginRender;
  DanJetX1.RenderToTexture(tex_Space);
  DanJetX1.ClearScreen($ff000000);
  DanJetX1.Filtering := true;
  tex_Space1.Draw(0, 0, $ffffffff, 0, 0, 0, 2, 2);
  DanJetX1.BlendMode := bmSrcColAdd;
  for i := 0 to 40 do
  begin
    x := Random(512);
    y := Random(512);
    sc := (Random(11) + 5) * 0.1;
    tex_Star.Draw(
      x, y, $ffffffff, 0, 0, 0, sc, sc
    );
    if x > 512 - 32 then x := x - 512;
    if y > 512 - 32 then y := y - 512;
    if (x < 0) or (y < 0) then
    tex_Star.Draw(
      x, y, $ffffffff, 0, 0, 0, sc, sc
    );
  end;
  DanJetX1.BlendMode := bmNormal;
  DanJetX1.Filtering := false;
  DanJetX1.RenderToBackBuffer;
  DanJetX1.EndRender;
  tex_Space.ReloadFromRenderTarget;

  MenuRects[0] := Rect(
    DanJetX1.Width div 2 - 128, 400,
    DanJetX1.Width div 2 + 128, 400 + 64
  );
  MenuRects[1] := Rect(
    DanJetX1.Width div 2 - 128, 480,
    DanJetX1.Width div 2 + 128, 480 + 64
  );

  QuickPlayer := TQuickPlayer.Create;
  if MusicEnable then
  QuickPlayer.PlayFile('Music.wav', true);
end;

procedure TGame.Finalize;
begin
  QuickPlayer.Free;
  PathFinder.Free;
  PhysWorld.Free;
  Jet2D.Free;
end;

procedure TGame.Render;
var
  s: string;
  g: single;
begin
  if (GameState = gsGame)
  or (GameState = gsMenu)
  or (GameState = gsWin)
  or (GameState = gsLost) then
  QuickPlayer.Update;
  if not DanJetX1.CanRender then Exit;
  DanJetX1.BeginRender;
  DanJetX1.ClearScreen($888888);

  if GameState = gsGame then Form1.Cursor := crNone
  else Form1.Cursor := crDefault;

  case GameState of
    gsMenu:
    begin
      RenderMenu;
    end;
    gsGame:
    begin
      if ShaderEnable then
      RenderMapPS
      else
      RenderMap;
      DanJetX1.Filtering := true;
      PhysObjEn.Draw;
      SmokeEn.Draw;
      LaserEn.Draw;
      Ships.Draw;
      DanJetX1.BlendMode := bmSrcColAdd;
      ExplosionEn.Draw;
      LaserFlashEn.Draw;
      GravFlashEn.Draw;
      FlareEn.Draw;
      FlashEn.Draw;
      DanJetX1.BlendMode := bmNormal;
      tex_Cursor.Draw(
        DanJetX1.MousePos.x - tex_Cursor.Width * 0.5,
        DanJetX1.MousePos.Y - tex_Cursor.Height * 0.5,
        $ffffffff, 0.5, 0.5,
        GetPiTime, 1, 1
      );
      DanJetX1.Filtering := false;
      s := 'Score: ' + IntToStr(Score);
      fnt_Font1.Print(
        DanJetX1.Width - fnt_Font1.GetTextWidth(s) - 10,
        10, s
      );
      if (ResWin or ResLost) and (Game.CurTime - ResTime < 3000) then
      begin
        g := (Game.CurTime - ResTime) / 3000;
        DanJetX1.Primitives2D.Rectangle(
          0, 0, DanJetX1.Width, DanJetX1.Height,
          (Trunc(255 * g) shl 24) or $ffffff
        );
      end;
    end;
    gsWin:
    begin
      DanJetX1.Primitives2D.Rectangle(
        0, 0, DanJetX1.Width, DanJetX1.Height,
        $ffffffff
      );
      DanJetX1.Primitives2D.Rectangle(
        100, 100, DanJetX1.Width - 200, DanJetX1.Height - 200,
        $88888888
      );
      DanJetX1.Primitives2D.Filled := false;
      DanJetX1.Primitives2D.Rectangle(
        100, 100, DanJetX1.Width - 200, DanJetX1.Height - 200,
        $ff000000
      );
      DanJetX1.Primitives2D.Filled := true;
      s := 'Level complete';
      fnt_Font1.Print(
        (DanJetX1.Width - fnt_Font1.GetTextWidth(s)) div 2,
        200,
        s, $ff000000
      );
      s := 'Score: ' + IntToStr(Score);
      fnt_Font1.Print(
        (DanJetX1.Width - fnt_Font1.GetTextWidth(s)) div 2,
        400,
        s, $ff000088
      );
      s := 'Press any key to continue';
      g := Sin(GetPiTime) * 0.5 + 0.5;
      fnt_Font1.Print(
        (DanJetX1.Width - fnt_Font1.GetTextWidth(s)) div 2,
        600,
        s, DJXColor(Trunc(g * 255), 0, Trunc((1 - g) * 255))
      );
    end;
    gsLost:
    begin
      DanJetX1.Primitives2D.Rectangle(
        0, 0, DanJetX1.Width, DanJetX1.Height,
        $ffffffff
      );
      DanJetX1.Primitives2D.Rectangle(
        100, 100, DanJetX1.Width - 200, DanJetX1.Height - 200,
        $88888888
      );
      DanJetX1.Primitives2D.Filled := false;
      DanJetX1.Primitives2D.Rectangle(
        100, 100, DanJetX1.Width - 200, DanJetX1.Height - 200,
        $ff000000
      );
      DanJetX1.Primitives2D.Filled := true;
      s := 'You have lost';
      fnt_Font1.Print(
        (DanJetX1.Width - fnt_Font1.GetTextWidth(s)) div 2,
        200,
        s, $ff000000
      );
      s := 'Score: ' + IntToStr(Score);
      fnt_Font1.Print(
        (DanJetX1.Width - fnt_Font1.GetTextWidth(s)) div 2,
        400,
        s, $ff000088
      );
      s := 'Press any key to continue';
      g := Sin(GetPiTime) * 0.5 + 0.5;
      fnt_Font1.Print(
        (DanJetX1.Width - fnt_Font1.GetTextWidth(s)) div 2,
        600,
        s, DJXColor(Trunc(g * 255), 0, Trunc((1 - g) * 255))
      );
    end;
  end;

  DanJetX1.EndRender;
end;

procedure TGame.Process;
var
  i: integer;
  Enemy, Ally: TShip;
const
  Iterations = 2;
begin
  CurTime := CurTime + (1000 div DJXTimer1.TargetFPS);
  case GameState of
    gsInit:
    begin
      DJXTimer1.Enabled := false;
      Game.Initialize;
      GameState := gsMenu;
      DJXTimer1.Enabled := true;
    end;
    gsGame:
    begin
      if not Game.PlayerDead and Game.Map.PlayerCreated then
      Map.Pos := Player.B.Pos^ - CSVector(DanJetX1.Width * 0.5, DanJetX1.Height * 0.5);

      if Map.Pos.x < 0 then Map.Pos.x := 0;
      if Map.Pos.y < 0 then Map.Pos.y := 0;
      if Map.Pos.x > Map.CellCountX * Map.CellSize - DanJetX1.Width then
      Map.Pos.x := Map.CellCountX * Map.CellSize - DanJetX1.Width;
      if Map.Pos.y > Map.CellCountY * Map.CellSize - DanJetX1.Height then
      Map.Pos.y := Map.CellCountY * Map.CellSize - DanJetX1.Height;

      Ships.Process;
      SmokeEn.Process;
      FlareEn.Process;
      LaserEn.Process;
      LaserFlashEn.Process;        
      ExplosionEn.Process;
      PhysObjEn.Process;
      GravFlashEn.Process;
      FlashEn.Process;

      for i := 0 to Iterations - 1 do
      PhysWorld.Step(1 / Iterations);

      if CurTime - Map.LevelStartTime > 3000 then
      begin

        if (Map.AllysCreated < Map.MaxAllys)
        and (Game.CurTime - Map.AllyCreateTime > 3000) then
        begin
          Map.AllyCreateTime := Game.CurTime;
          Ally := TAlly.Create(Ships);
          with TFlash.Create(FlashEn) do
          begin
            X := Ally.B.Pos^.x;
            Y := Ally.B.Pos^.y;
          end;
        end;

        if (Map.EnemiesCreated < Map.MaxEnemies)
        and (Game.CurTime - Map.EnemyDieTime > 10000) then
        begin
          Map.EnemyDieTime := Game.CurTime;
          Enemy := TEnemy.Create(Ships);
          with TFlash.Create(FlashEn) do
          begin
            X := Enemy.B.Pos^.x;
            Y := Enemy.B.Pos^.y;
          end;
        end;

      end;

      if (CurTime - Map.LevelStartTime > 1000) and not Map.PlayerCreated then
      begin
        Map.PlayerCreated := true;
        Player := TPlayer.Create(Ships);
        Player.SetPos(
          Map.Start.x * Map.CellSize + Map.CellSize div 2,
          Map.Start.y * Map.CellSize + Map.CellSize div 2
        );
        with TFlash.Create(FlashEn) do
        begin
          X := Player.B.Pos^.x;
          Y := Player.B.Pos^.y;
        end;
      end;

      if (ResWin or ResLost) and (Game.CurTime - ResTime >= 3000) then
      begin
        if ResWin then
        GameState := gsWin
        else
        GameState := gsLost;
        UnLoadMap;
      end;

    end;
  end;
end;

procedure TGame.StartGame;
begin
  CurLevel := 1;
  Score := 0;
  LoadMap(CurLevel);  
end;

procedure TGame.KDown(Key: WORD);
begin
  Keys[Key] := true;
  case GameState of
    gsWin:
    begin
      CurLevel := CurLevel + 1;
      if Res.ResourceExists('Maps', 'Map' + IntToStr(CurLevel)) then
      begin
        LoadMap(CurLevel);
        GameState := gsGame;
      end
      else
      GameState := gsMenu;
    end;
    gsLost:
    begin
      GameState := gsMenu;
    end;
  end;  
  if Key = VK_ESCAPE then Form1.Close;
end;

procedure TGame.KUp(Key: WORD);
begin
  Keys[Key] := false;
end;

procedure TGame.MDown(X, Y: integer; Button: TMouseButton);
var
  i: integer;
begin
  if Button = mbLeft then MDL := true;
  if Button = mbRight then MDR := true;
  if GameState = gsMenu then
  begin
    for i := 0 to High(MenuRects) do
    if PtInRect(
      MenuRects[i],
      DanJetX1.MousePos
    ) then
    begin
      case i of
        0:
        begin
          StartGame;
          GameState := gsGame;
        end;
        1:
        begin
          Form1.Close;
        end;
      end;
    end;
  end;
end;

procedure TGame.MUp(X, Y: integer; Button: TMouseButton);
begin
  if Button = mbLeft then MDL := false;
  if Button = mbRight then MDR := false;
end;

procedure TGame.WDown;
begin

end;

procedure TGame.WUp;  
begin

end;

procedure TGame.LoadMap(Ind: integer);
var
  MName: string;
  Buffer: PByte;
  i, j: integer;
  B: TCSBody;
  S: TCSShape;
  P: TCSPolygon;
  Pos: TCSVector;
begin
  MName := 'Map' + IntToStr(Ind);
  if Res.ResourceExists('Maps', MName) then
  begin
    Buffer := Res.GetResBuffer('Maps', MName).Data;
    Move(Buffer^, Map.CellCountX, SizeOf(integer));
    Inc(Buffer, SizeOf(integer));
    Move(Buffer^, Map.CellCountY, SizeOf(integer));
    Inc(Buffer, SizeOf(integer));
    Move(Buffer^, Map.CellSize, SizeOf(integer));
    Inc(Buffer, SizeOf(integer));
    Move(Buffer^, Map.GroundLevel, SizeOf(integer));
    Inc(Buffer, SizeOf(integer));
    Move(Buffer^, Map.Start, SizeOf(TPoint));
    Inc(Buffer, SizeOf(TPoint));
    Move(Buffer^, Map.Finish, SizeOf(TPoint));
    Inc(Buffer, SizeOf(TPoint));
    Move(Buffer^, Map.ColyCount, SizeOf(integer));
    Inc(Buffer, SizeOf(integer));
    Move(Buffer^, Map.ObjCount, SizeOf(integer));
    Inc(Buffer, SizeOf(integer));
    SetLength(Map.Cells, Map.CellCountX, Map.CellCountY);
    Map.PathMap := PathFinder.CreateMap(Map.CellCountX, Map.CellCountY);
    PhysWorld.InitWorld(
      -10, -10,
      Map.CellCountX * Map.CellSize + 10,
      Map.CellCountY * Map.CellSize + 10
    );
    S := TCSShape.Create;
    SetLength(Map.EnemyPortals, 0);
    for j := 0 to Map.CellCountY - 1 do
    for i := 0 to Map.CellCountX - 1 do
    begin
      Move(Buffer^, Map.Cells[i, j], SizeOf(TCell));
      Inc(Buffer, SizeOf(TCell));
      if Map.Cells[i, j].CellType = ctWall then
      Map.PathMap.Nodes[i, j].Cost := 0;
      if Map.Cells[i, j].CellType = ctEnemySpawn then
      begin
        SetLength(Map.EnemyPortals, Length(Map.EnemyPortals) + 1);
        Map.EnemyPortals[High(Map.EnemyPortals)] := Point(i, j);
      end;
    end;
    SetLength(Map.Colys, Map.ColyCount);
    for i := 0 to Map.ColyCount - 1 do
    begin
      Move(Buffer^, j, SizeOf(integer));
      Inc(Buffer, SizeOf(integer));
      SetLength(P, j);
      for j := 0 to High(P) do
      begin
        Move(Buffer^, P[j], SizeOf(TCSVector));
        Inc(Buffer, SizeOf(TCSVector));
      end;
      S.Clear;
      S.AddPolygon(P);
      Pos := CSPolyCenter(P);
      S.AutoCM;
      B := PhysWorld.CreateBody;
      B.Shape := S;
      B.Pos^ := Pos;
      B.IsStatic := true;
      B.Hardness := 1.4;
      B.Friction := 0.5;
      Map.Colys[i] := B;
    end;
    SetLength(Map.Objects, Map.ObjCount);
    for i := 0 to Map.ObjCount - 1 do
    begin
      Move(Buffer^, Map.Objects[i], SizeOf(TObj));
      Inc(Buffer, SizeOf(TObj));
      with TPhysObj.Create(PhysObjEn) do
      begin
        Obj.Pos := Map.Objects[i].Pos;
        Obj.Size := Map.Objects[i].Size;
        Obj.ObjType := Map.Objects[i].ObjType;
        InitObj;
      end;      
    end;
    S.Clear;
    SetLength(P, 2);
    P[0] := CSVector((-Map.CellCountX * Map.CellSize) div 2, 0);
    P[1] := CSVector((Map.CellCountX * Map.CellSize) div 2, 0);
    S.AddPolygon(P);
    B := PhysWorld.CreateBody;
    B.Shape := S;
    B.Pos^ := CSVector((Map.CellCountX * Map.CellSize) div 2, 0);
    B.IsStatic := true;
    B.Friction := 0;
    B.Hardness := 0.2;
    B := PhysWorld.CreateBody;
    B.Shape := S;
    B.Pos^ := CSVector((Map.CellCountX * Map.CellSize) div 2, Map.CellCountY * Map.CellSize);
    B.IsStatic := true;
    B.Friction := 0;
    B.Hardness := 0.2;
    S.Clear;
    P[0] := CSVector(0, -(Map.CellCountY * Map.CellSize) div 2);
    P[1] := CSVector(0, (Map.CellCountY * Map.CellSize) div 2);
    S.AddPolygon(P);
    B := PhysWorld.CreateBody;
    B.Shape := S;
    B.Pos^ := CSVector(0, (Map.CellCountY * Map.CellSize) div 2);
    B.IsStatic := true;
    B.Friction := 0;
    B.Hardness := 0.2;
    B := PhysWorld.CreateBody;
    B.Shape := S;
    B.Pos^ := CSVector(Map.CellCountX * Map.CellSize, (Map.CellCountY * Map.CellSize) div 2);
    B.IsStatic := true;
    B.Friction := 0;
    B.Hardness := 0.2;
    S.Free;
    Map.Pos := CSVector(0, 0);
  end;
  SetUpLevel;
end;

procedure TGame.UnLoadMap;
begin
  Ships.FreeItems;
  SmokeEn.FreeItems;
  LaserFlashEn.FreeItems;
  LaserEn.FreeItems;
  FlareEn.FreeItems;
  ExplosionEn.FreeItems;
  FlashEn.FreeItems;
  PhysObjEn.FreeItems;
  PhysWorld.FreeContactGroups;
  PhysWorld.FreeBodies;
  PathFinder.FreeMap(Map.PathMap);
end;

procedure TGame.RenderMap;
var
  cx1, cy1, cx2, cy2, i, j: integer;
  tx1, ty1, tx2, ty2, sx, sy: single;
  w, h, x, y: single;
  Tex: TDJXTexture;
begin
  DanJetX1.Filtering := true;
  if Map.Pos.y < Map.GroundLevel * Map.CellSize then
  begin
    if Map.Pos.y + DanJetX1.Height * 0.5 < Map.GroundLevel * Map.CellSize then
    h := Map.GroundLevel * Map.CellSize - (Map.Pos.y + DanJetX1.Height * 0.5);
    sx := (Map.Pos.x / 512) * 0.5;
    sy := (Map.Pos.y / 512) + h / 512;
    tx1 := 0 + sx;
    ty1 := 0 + sy;
    tx2 := DanJetX1.Width / 512 + sx;
    ty2 := (Map.GroundLevel * Map.CellSize - Map.Pos.y) / 512 + sy;
    DanJetX1.Primitives2D.RectangleMT(
      0, 0, DanJetX1.Width, Map.GroundLevel * Map.CellSize - Map.Pos.y,
      $ffffffff, tex_Space,
      tx1, ty1, tx2, ty1, tx2, ty2, tx1, ty2
    );
    if Map.Pos.y + DanJetX1.Height * 0.5 < Map.GroundLevel * Map.CellSize then
    begin
      //if h > DanJetX1.Height * 0.5 then h := DanJetX1.Height * 0.5;
      sx := (Map.Pos.x / 512);
      sy := 0;
      tx1 := 0 + sx;
      ty1 := 0;
      tx2 := DanJetX1.Width / 512 + sx;
      ty2 := 1;
      DanJetX1.MipMaps := true;
      DanJetX1.Primitives2D.RectangleMT(
        0, Map.GroundLevel * Map.CellSize - Map.Pos.y - h,
        DanJetX1.Width, h,
        $ffffffff, tex_Ground,
        tx1, ty1, tx2, ty1, tx2, ty2, tx1, ty2
      );
      DanJetX1.MipMaps := false;
      DanJetX1.Primitives2D.Rectangle4Col(
        0, Map.GroundLevel * Map.CellSize - Map.Pos.y - h,
        DanJetX1.Width, h * 0.7,
        $ffffffff, $ffffffff, $00ffffff, $00ffffff,
      );
    end
    else
    h := 0;
    DanJetX1.Primitives2D.Rectangle4Col(
      0, Map.GroundLevel * Map.CellSize - Map.Pos.y - 100 - h,
      DanJetX1.Width, 100,
      $000000ff, $000000ff, $ffffffff, $ffffffff,
    );
  end;
  if Map.Pos.y + DanJetX1.Height > Map.GroundLevel * Map.CellSize then
  begin
    sx := (Map.Pos.x / 1024);
    sy := (Map.Pos.y / 1024);
    x := Map.Pos.x;
    y := Map.GroundLevel * Map.CellSize;
    w := DanJetX1.Width;
    h := (Map.Pos.y + DanJetX1.Height) - (Map.GroundLevel * Map.CellSize);
    tx1 := (x - Map.Pos.x) / 1024 + sx;
    ty1 := (y - Map.Pos.y) / 1024 + sy;
    tx2 := (x + w - Map.Pos.x) / 1024 + sx;
    ty2 := (y + h - Map.Pos.y) / 1024 + sy;
    DanJetX1.Primitives2D.RectangleMT(
      x - Map.Pos.x, y - Map.Pos.y,
      w, h,
      $ffffffff,
      tex_Ground2,
      tx1, ty1, tx2, ty1, tx2, ty2, tx1, ty2
    );
  end;
  
  sx := (Map.Pos.x / 512);
  tx1 := 0 + sx;
  tx2 := DanJetX1.Width / 512 + sx;
  //DanJetX1.States.SamplerStates.AddressU[0] := D3DTADDRESS_CLAMP;
  DanJetX1.States.SamplerStates.AddressV[0] := D3DTADDRESS_CLAMP;
  DanJetX1.Primitives2D.RectangleMT(
    0, Map.GroundLevel * Map.CellSize - Map.Pos.y - 16,
    DanJetX1.Width, 32,
    $ffffffff,
    tex_GroundLevel,
    tx1, 0, tx2, 0, tx2, 1, tx1, 1
  );
  //DanJetX1.States.SamplerStates.AddressU[0] := D3DTADDRESS_WRAP;
  DanJetX1.States.SamplerStates.AddressV[0] := D3DTADDRESS_WRAP;
    
  cx1 := Trunc(Map.Pos.x / Map.CellSize) - 1;
  if cx1 < 0 then cx1 := 0;
  cy1 := Trunc(Map.Pos.y / Map.CellSize) - 1;
  if cy1 < 0 then cy1 := 0;
  cx2 := cx1 + Trunc(DanJetX1.Width / Map.CellSize) + 1;
  if cx2 > Map.CellCountX - 1 then cx2 := Map.CellCountX - 1;
  cy2 := cy1 + Trunc(DanJetX1.Height / Map.CellSize) + 1;
  if cy2 > Map.CellCountY - 1 then cy2 := Map.CellCountY - 1;
  Jet2D.BeginRender;
  for j := cy1 to cy2 do
  for i := cx1 to cx2 do
  begin
    Tex := nil;
    case Map.Cells[i, j].CellType of
      ctWall: Tex := tex_Wall1;
      ctEnemySpawn: Tex := tex_EnemySpawn;
    end;
    if Assigned(Tex) then
    Jet2D.Draw4Col(
      i * Map.CellSize - Map.Pos.x, j * Map.CellSize - Map.Pos.y,
      (i + 1) * Map.CellSize - Map.Pos.x, j * Map.CellSize - Map.Pos.y,
      (i + 1) * Map.CellSize - Map.Pos.x, (j + 1) * Map.CellSize - Map.Pos.y,
      i * Map.CellSize - Map.Pos.x, (j + 1) * Map.CellSize - Map.Pos.y,
      $ffffffff, $ffffffff, $ffffffff, $ffffffff, Tex,
      0, 0, 1, 0, 1, 1, 0, 1
    );
  end;
  Jet2D.EndRender;
  DanJetX1.BlendMode := bmSrcColAdd;
  tex_Start.Draw4col(
    Map.Start.x * Map.CellSize - Map.Pos.x, Map.Start.y * Map.CellSize - Map.Pos.y,
    (Map.Start.x + 1) * Map.CellSize - Map.Pos.x, Map.Start.y * Map.CellSize - Map.Pos.y,
    (Map.Start.x + 1) * Map.CellSize - Map.Pos.x, (Map.Start.y + 1) * Map.CellSize - Map.Pos.y,
    Map.Start.x * Map.CellSize - Map.Pos.x, (Map.Start.y + 1) * Map.CellSize - Map.Pos.y,
    $ffffffff, $ffffffff, $ffffffff, $ffffffff,
    0.5, 0.5, GetPiTime, 1, 1
  );
  tex_Finish.Draw4col(
    Map.Finish.x * Map.CellSize - Map.Pos.x, Map.Finish.y * Map.CellSize - Map.Pos.y,
    (Map.Finish.x + 1) * Map.CellSize - Map.Pos.x, Map.Finish.y * Map.CellSize - Map.Pos.y,
    (Map.Finish.x + 1) * Map.CellSize - Map.Pos.x, (Map.Finish.y + 1) * Map.CellSize - Map.Pos.y,
    Map.Finish.x * Map.CellSize - Map.Pos.x, (Map.Finish.y + 1) * Map.CellSize - Map.Pos.y,
    $ffffffff, $ffffffff, $ffffffff, $ffffffff,
    0.5, 0.5, GetPiTime, 1, 1
  );
  DanJetX1.BlendMode := bmNormal;
  DanJetX1.Filtering := false;
end;

procedure TGame.RenderMapPS;
var
  cx1, cy1, cx2, cy2, i, j, l, sc: integer;
  tx1, ty1, tx2, ty2, sx, sy: single;
  w, h, x, y: single;
  Tex: TDJXTexture;
  Pass, PassCount: integer;
  v4: TD3DXVector4;
  v2: TD3DXVector2;
  PosArr: array[0..4] of TD3DXVector4;
  ColArr: array[0..4] of TD3DXVector4;
  LCount: integer;
  procedure GetLightsInRect(R: TRect);
  var
    ShipsInRect: TList;
    i: integer;
  begin
    ShipsInRect := TList.Create;
    if Map.PlayerCreated and not PlayerDead then
    ShipsInRect.Add(Player);
    for i := 0 to Ships.Count - 1 do
    if (Map.PlayerCreated and not PlayerDead and (TShip(Ships.Items[i]) <> Player))
    and
    RectInRect(
      R,
      Rect(
        Round((TShip(Ships.Items[i]).B.Pos^.x - Map.Pos.x) - 300),
        Round((TShip(Ships.Items[i]).B.Pos^.y - Map.Pos.y) - 300),
        Round((TShip(Ships.Items[i]).B.Pos^.x - Map.Pos.x) + 300),
        Round((TShip(Ships.Items[i]).B.Pos^.y - Map.Pos.y) + 300)
      )
    ) then
    ShipsInRect.Add(TShip(Ships.Items[i]));

    LCount := Min(ShipsInRect.Count, 5);
    for i := 0 to LCount - 1 do
    begin
      PosArr[i] := D3DXVector4(
        TShip(ShipsInRect.Items[i]).B.Pos^.x - Map.Pos.x,
        TShip(ShipsInRect.Items[i]).B.Pos^.y - Map.Pos.y,
        100,
        300 * 300
      );
      ColArr[i] := D3DXVector4(1, 1, 1, 1);
    end;

    ShipsInRect.Free;
  end;
begin
  DanJetX1.Filtering := true;
  if Map.Pos.y < Map.GroundLevel * Map.CellSize then
  begin
    if Map.Pos.y + DanJetX1.Height * 0.5 < Map.GroundLevel * Map.CellSize then
    h := Map.GroundLevel * Map.CellSize - (Map.Pos.y + DanJetX1.Height * 0.5);
    sx := (Map.Pos.x / 512) * 0.5;
    sy := (Map.Pos.y / 512) + h / 512;
    tx1 := 0 + sx;
    ty1 := 0 + sy;
    tx2 := DanJetX1.Width / 512 + sx;
    ty2 := (Map.GroundLevel * Map.CellSize - Map.Pos.y) / 512 + sy;
    DanJetX1.Primitives2D.RectangleMT(
      0, 0, DanJetX1.Width, Map.GroundLevel * Map.CellSize - Map.Pos.y,
      $ffffffff, tex_Space,
      tx1, ty1, tx2, ty1, tx2, ty2, tx1, ty2
    );
    if Map.Pos.y + DanJetX1.Height * 0.5 < Map.GroundLevel * Map.CellSize then
    begin
      //if h > DanJetX1.Height * 0.5 then h := DanJetX1.Height * 0.5;
      sx := (Map.Pos.x / 512);
      sy := 0;
      tx1 := 0 + sx;
      ty1 := 0;
      tx2 := DanJetX1.Width / 512 + sx;
      ty2 := 1;
      DanJetX1.MipMaps := true;
      DanJetX1.Primitives2D.RectangleMT(
        0, Map.GroundLevel * Map.CellSize - Map.Pos.y - h,
        DanJetX1.Width, h,
        $ffffffff, tex_Ground,
        tx1, ty1, tx2, ty1, tx2, ty2, tx1, ty2
      );
      DanJetX1.MipMaps := false;
      DanJetX1.Primitives2D.Rectangle4Col(
        0, Map.GroundLevel * Map.CellSize - Map.Pos.y - h,
        DanJetX1.Width, h * 0.7,
        $ffffffff, $ffffffff, $00ffffff, $00ffffff,
      );
    end
    else
    h := 0;
    DanJetX1.Primitives2D.Rectangle4Col(
      0, Map.GroundLevel * Map.CellSize - Map.Pos.y - 100 - h,
      DanJetX1.Width, 100,
      $000000ff, $000000ff, $ffffffff, $ffffffff,
    ); 
  end;
  if Map.Pos.y + DanJetX1.Height > Map.GroundLevel * Map.CellSize then
  begin
    sx := (Map.Pos.x / 1024);
    sy := (Map.Pos.y / 1024);
    x := Map.Pos.x;
    y := Map.GroundLevel * Map.CellSize;
    w := DanJetX1.Width;
    h := (Map.Pos.y + DanJetX1.Height) - (Map.GroundLevel * Map.CellSize);
    tx1 := (x - Map.Pos.x) / 1024 + sx;
    ty1 := (y - Map.Pos.y) / 1024 + sy;
    tx2 := (x + w - Map.Pos.x) / 1024 + sx;
    ty2 := (y + h - Map.Pos.y) / 1024 + sy;

    x := x - Map.Pos.x;
    y := y - Map.Pos.y;

    GetLightsInRect(
      Rect(
        0, Max(Round(Map.GroundLevel * Map.CellSize - Map.Pos.y), 0),
        DanJetX1.Width, DanJetX1.Height
      )
    );

    fx_Background.Effect.SetTechnique('RenderBG');
    fx_Background.Effect.SetTexture('Tex0', tex_Ground2.Texture);
    fx_Background.Effect.SetTexture('Tex0NM', tex_Ground2NM.Texture);
    fx_Background.Effect.SetVectorArray('PosArr', @PosArr, LCount);
    fx_Background.Effect.SetVectorArray('ColArr', @ColArr, LCount);
    fx_Background.Effect.SetInt('LCount', LCount);
    fx_Background.Effect._Begin(@PassCount, 0);
    fx_Background.Effect.BeginPass(0);
    DanJetX1.Primitives2D.RectangleRaw(
      x, y, w, h,
      $ffffffff, $ffffffff, $ffffffff, $ffffffff,
      tx1, ty1, tx2, ty1, tx2, ty2, tx1, ty2,
      x, y, x + w, y, x + w, y + h, x, y + h
    );
    fx_Background.Effect.EndPass;
    fx_Background.Effect._End;
  end;
  
  sx := (Map.Pos.x / 512);
  tx1 := 0 + sx;
  tx2 := DanJetX1.Width / 512 + sx;
  DanJetX1.States.SamplerStates.AddressV[0] := D3DTADDRESS_CLAMP;
  DanJetX1.Primitives2D.RectangleMT(
    0, Map.GroundLevel * Map.CellSize - Map.Pos.y - 16,
    DanJetX1.Width, 32,
    $ffffffff,
    tex_GroundLevel,
    tx1, 0, tx2, 0, tx2, 1, tx1, 1
  );
  DanJetX1.States.SamplerStates.AddressV[0] := D3DTADDRESS_WRAP;
    
  cx1 := Trunc(Map.Pos.x / Map.CellSize) - 1;
  if cx1 < 0 then cx1 := 0;
  cy1 := Trunc(Map.Pos.y / Map.CellSize) - 1;
  if cy1 < 0 then cy1 := 0;
  cx2 := cx1 + Trunc(DanJetX1.Width / Map.CellSize) + 1;
  if cx2 > Map.CellCountX - 1 then cx2 := Map.CellCountX - 1;
  cy2 := cy1 + Trunc(DanJetX1.Height / Map.CellSize) + 1;
  if cy2 > Map.CellCountY - 1 then cy2 := Map.CellCountY - 1;
  DanJetX1.States.SamplerStates.AddressU[0] := D3DTADDRESS_CLAMP;
  DanJetX1.States.SamplerStates.AddressV[0] := D3DTADDRESS_CLAMP;

  for j := cy1 to cy2 do
  for i := cx1 to cx2 do
  begin
    Tex := nil;
    case Map.Cells[i, j].CellType of
      ctWall: Tex := tex_Wall1;
      ctEnemySpawn: Tex := tex_EnemySpawn;
    end;
    if Assigned(Tex) then
    begin
      if j * Map.CellSize >= Map.GroundLevel * Map.CellSize then
      begin 
        fx_Background.Effect.SetTechnique('RenderBG');
        fx_Background.Effect.SetTexture('Tex0', Tex.Texture);
        fx_Background.Effect.SetTexture('Tex0NM', tex_Wall1NM.Texture);
        fx_Background.Effect.SetVectorArray('PosArr', @PosArr, LCount);
        fx_Background.Effect.SetVectorArray('ColArr', @ColArr, LCount);
        fx_Background.Effect.SetFloat('Mag', 2);
        fx_Background.Effect.SetInt('LCount', LCount);
        fx_Background.Effect._Begin(@PassCount, 0);
        fx_Background.Effect.BeginPass(0);
        DanJetX1.Primitives2D.QuadrangleRaw(
          i * Map.CellSize - Map.Pos.x, j * Map.CellSize - Map.Pos.y,
          (i + 1) * Map.CellSize - Map.Pos.x, j * Map.CellSize - Map.Pos.y,
          (i + 1) * Map.CellSize - Map.Pos.x, (j + 1) * Map.CellSize - Map.Pos.y,
          i * Map.CellSize - Map.Pos.x, (j + 1) * Map.CellSize - Map.Pos.y,
          $ffffffff, $ffffffff, $ffffffff, $ffffffff,
          0, 0, 1, 0, 1, 1, 0, 1,
          i * Map.CellSize - Map.Pos.x, j * Map.CellSize - Map.Pos.y,
          (i + 1) * Map.CellSize - Map.Pos.x, j * Map.CellSize - Map.Pos.y,
          (i + 1) * Map.CellSize - Map.Pos.x, (j + 1) * Map.CellSize - Map.Pos.y,
          i * Map.CellSize - Map.Pos.x, (j + 1) * Map.CellSize - Map.Pos.y
        );
        fx_Background.Effect.EndPass;
        fx_Background.Effect._End;
      end
      else
      Tex.Draw4col(
        i * Map.CellSize - Map.Pos.x, j * Map.CellSize - Map.Pos.y,
          (i + 1) * Map.CellSize - Map.Pos.x, j * Map.CellSize - Map.Pos.y,
          (i + 1) * Map.CellSize - Map.Pos.x, (j + 1) * Map.CellSize - Map.Pos.y,
          i * Map.CellSize - Map.Pos.x, (j + 1) * Map.CellSize - Map.Pos.y,
          $ffffffff, $ffffffff, $ffffffff, $ffffffff
      );
    end;
  end;
  
  DanJetX1.States.SamplerStates.AddressU[0] := D3DTADDRESS_WRAP;
  DanJetX1.States.SamplerStates.AddressV[0] := D3DTADDRESS_WRAP;
  DanJetX1.BlendMode := bmSrcColAdd;
  tex_Start.Draw4col(
    Map.Start.x * Map.CellSize - Map.Pos.x, Map.Start.y * Map.CellSize - Map.Pos.y,
    (Map.Start.x + 1) * Map.CellSize - Map.Pos.x, Map.Start.y * Map.CellSize - Map.Pos.y,
    (Map.Start.x + 1) * Map.CellSize - Map.Pos.x, (Map.Start.y + 1) * Map.CellSize - Map.Pos.y,
    Map.Start.x * Map.CellSize - Map.Pos.x, (Map.Start.y + 1) * Map.CellSize - Map.Pos.y,
    $ffffffff, $ffffffff, $ffffffff, $ffffffff,
    0.5, 0.5, GetPiTime, 2, 2
  );
  tex_Finish.Draw4col(
    Map.Finish.x * Map.CellSize - Map.Pos.x, Map.Finish.y * Map.CellSize - Map.Pos.y,
    (Map.Finish.x + 1) * Map.CellSize - Map.Pos.x, Map.Finish.y * Map.CellSize - Map.Pos.y,
    (Map.Finish.x + 1) * Map.CellSize - Map.Pos.x, (Map.Finish.y + 1) * Map.CellSize - Map.Pos.y,
    Map.Finish.x * Map.CellSize - Map.Pos.x, (Map.Finish.y + 1) * Map.CellSize - Map.Pos.y,
    $ffffffff, $ffffffff, $ffffffff, $ffffffff,
    0.5, 0.5, GetPiTime, 2, 2
  );
  DanJetX1.BlendMode := bmNormal;
  DanJetX1.Filtering := false;
end;

procedure TGame.RenderMenu;
var
  x, y: single;
  Ang: single;
  i, w, h: integer;
  Btns: array[0..1] of TDJXTexture;
  R: TRect;
  s: string;
const
  BoxDepth = 200;
begin
  w := DanJetX1.Width;
  h := DanJetX1.Height;
  Btns[0] := tex_TextNewGame;
  Btns[1] := tex_TextExit;
  DanJetX1.Primitives2D.Rectangle(
    0, 0, w, h,
    $ffffffff
  );
  DanJetX1.Primitives2D.Quadrangle4col(
    0, 0,
    w, 0,
    w - BoxDepth, BoxDepth,
    BoxDepth, BoxDepth,
    $ff000000, $ff000000, $0, $0
  );
  DanJetX1.Primitives2D.Quadrangle4col(
    0, 0,
    BoxDepth, BoxDepth,
    BoxDepth, h - BoxDepth,
    0, h,
    $ff000000, $0, $0, $ff000000
  );
  DanJetX1.Primitives2D.Quadrangle4col(
    0, h,
    BoxDepth, h - BoxDepth,
    w - BoxDepth, h - BoxDepth,
    w, h,
    $ff000000, $0, $0, $ff000000
  );
  DanJetX1.Primitives2D.Quadrangle4col(
    w, h,
    w - BoxDepth, h - BoxDepth,
    w - BoxDepth, BoxDepth,
    w, 0,
    $ff000000, $0, $0, $ff000000
  );
  DanJetX1.Filtering := true;
  tex_TextGravity.Draw(
    (w - tex_TextGravity.Width) div 2,
    200, $ffffffff, 0.5, 0.5, 0, 2, 2
  );
  Ang := GetPiTime(200);
  x := Cos(Ang) * 4;
  y := Sin(Ang) * 4;
  tex_TextGravity.Draw(
    (w - tex_TextGravity.Width) div 2 + x,
    200 + y, $44ffffff, 0.5, 0.5, 0, 2, 2
  );
  x := Cos(Ang + Pi) * 4;
  y := Sin(Ang + Pi) * 4;
  tex_TextGravity.Draw(
    (w - tex_TextGravity.Width) div 2 + x,
    200 + y, $44ffffff, 0.5, 0.5, 0, 2, 2
  );
  s := 'DAN SOFT STUDIO';
  fnt_Font2.Print(
    w - fnt_Font2.GetTextWidth(s) - 10,
    h - fnt_Font2.GetTextHeight(s) - 10,
    s
  );
  for i := 0 to High(Btns) do
  begin
    Btns[i].Draw(
      MenuRects[i].Left, MenuRects[i].Top - Btns[i].Height div 4,
      $ffffffff 
    );
    if PtInRect(
      MenuRects[i],
      DanJetX1.MousePos
    ) then
    begin
      Ang := GetPiTime(200);
      x := Cos(Ang) * 3;
      y := Sin(Ang) * 3;
      Btns[i].Draw(
        MenuRects[i].Left + x, MenuRects[i].Top - Btns[i].Height div 4 + y,
        $44ffffff
      );
      x := Cos(Ang + Pi) * 3;
      y := Sin(Ang + Pi) * 3;
      Btns[i].Draw(
        MenuRects[i].Left + x, MenuRects[i].Top - Btns[i].Height div 4 + y,
        $44ffffff
      );
    end;
  end;
  DanJetX1.Filtering := false;
end;

procedure TGame.GravPoint(X, Y: single);
const
  GravDist = 100;
  GravDistInv = 1 / GravDist;
  Force = 0.1;
  procedure GravBody(B: TCSBody; Amp: single = 1);
  var
    Dir: TCSVector;
    F: single;
    N: TCSVector;
  begin
    Dir := CSVector(X, Y) - B.Pos^;
    F := CSVecLength(Dir);
    if (F < GravDist) and (F > 0) then
    begin
      F := ((GravDist - F) * GravDistInv * Force);
      if F > Force then F := Force;
      if F > 0 then
      begin
        N := CSVecNormalize(Dir) * F * Amp;
        B.AccLinear^ := B.AccLinear^ + N;
      end;
    end;
  end;
var
  i: integer;
  B: TCSBody;
begin
  for i := 0 to PhysObjEn.Count - 1 do
  begin
    B := TPhysObj(PhysObjEn.Items[i]).B;
    GravBody(B);
  end;
  for i := 0 to LaserEn.Count - 1 do
  begin
    B := TLaser(LaserEn.Items[i]).B;
    GravBody(B, 10);
  end;
  for i := 0 to Ships.Count - 1 do
  begin
    B := TShip(Ships.Items[i]).B;
    if not (not PlayerDead and (TShip(Ships.Items[i]) = Player)) then
    GravBody(B);
  end; 
end;

procedure TGame.SetUpLevel;
begin
  Map.LevelStartTime := Game.CurTime;
  Map.AllysCreated := 0;
  Map.PlayerCreated := false;
  Map.MaxAllys := 3;
  Map.MaxEnemies := 2;
  Map.AllyCreateTime := Game.CurTime;
  Map.EnemyDieTime := Game.CurTime;
  ResLost := false;
  ResWin := false;
  case CurLevel of
    1:
    begin
      Map.MaxEnemies := 0;
      Map.MaxAllys := 1;
    end;
    2:
    begin
      Map.MaxEnemies := 1;
      Map.MaxAllys := 1;
    end;
    3:
    begin
      Map.MaxEnemies := 5;
      Map.MaxAllys := 10;
    end;
    4:
    begin
      Map.MaxEnemies := 1;
      Map.MaxAllys := 2;
    end;
  end;
end;
//TGame END

//TQuickPlayer BEGIN
constructor TQuickPlayer.Create;
begin
  inherited Create;
  CoInitialize(nil);
  m_Graphs := TList.Create;
end;

destructor TQuickPlayer.Destroy;
begin
  FreeGraphs;
  m_Graphs.Free;
  CoUninitialize;
  inherited Destroy;
end;

procedure TQuickPlayer.PlayFile(f: string; Looped: boolean = false);
var
  Graph: TQPGraph;
begin
  Graph := AddGraph;
  Graph.OpenFile(f);
  Graph.Looped := Looped;
  Graph.Play;
end;

procedure TQuickPlayer.FreeGraphs;
begin
  while m_Graphs.Count > 0 do
  TQPGraph(m_Graphs.Items[0]).Free;
  m_Graphs.Clear;
end;

function TQuickPlayer.AddGraph: TQPGraph;
begin
  Result := TQPGraph.Create;
  m_Graphs.Add(Result);
end;

procedure TQuickPlayer.Update;
var
  i, j: integer;
  Graph: TQPGraph;
begin
  j := m_Graphs.Count;
  i := 0;
  while i < j do
  begin
    Graph := TQPGraph(m_Graphs.Items[i]);
    if not Graph.m_Looped then
    begin
      if not Graph.IsPlaying and (GetTickCount - Graph.m_PlayTime > 1000) then
      begin
        m_Graphs.Remove(Graph);
        Graph.Free;
        j := j - 1;
        Continue;
      end;
    end
    else
    begin
      if not Graph.IsPlaying and (GetTickCount - Graph.m_PlayTime > 1000) then
      begin
        Graph.Play;
      end;
    end;
    i := i + 1;
  end;
end;
//TQuickPlayer END

//TQPGraph BEGIN
constructor TQPGraph.Create;
begin
  inherited Create;
  CoCreateInstance(
    CLSID_FilterGraph,
    nil, 
    CLSCTX_INPROC_SERVER,
    IID_IGraphBuilder,
    m_Graph
  );
  m_Graph.QueryInterface(IID_IMediaControl, m_Control);
  m_Graph.QueryInterface(IID_IMediaEventEx, m_Event);
  m_Graph.QueryInterface(IID_IMediaPosition, m_Position);
  m_Looped := false;
  m_PlayTime := GetTickCount;
end;

destructor TQPGraph.Destroy;
begin
  safe_release(m_Position);
  safe_release(m_Event);
  safe_release(m_Control);
  safe_release(m_Graph);
  inherited Destroy;
end;

procedure TQPGraph.OpenFile(f: WideString);
begin
  m_Graph.RenderFile(PWideChar(f), nil);
end;

procedure TQPGraph.Play;
begin
  m_Position.put_CurrentPosition(0);
  m_Control.Run;
  m_Playing := true;
  m_PlayTime := GetTickCount;
end;

procedure TQPGraph.Stop;
begin
  m_Position.put_CurrentPosition(0);
  m_Control.Stop;
  m_Playing := false;
end;

procedure TQPGraph.Pause;
begin
  m_Playing := false;
  m_Control.Pause;
end;

function TQPGraph.IsPlaying: boolean;
var
 EventCode, Param1, Param2: longint;
begin
  while m_Event.GetEvent(EventCode, Param1, Param2, 0) = S_OK do
  begin
    if (EventCode = EC_COMPLETE)then
    begin
      m_Playing := false;
    end;
    m_Event.FreeEventParams(EventCode, Param1, Param2);
  end;
  Result := m_Playing;
end;
//TQPGraph END

//TShip BEGIN
constructor TShip.Create(Engine: TDJXEngine);
var
  S: TCSShape;
begin
  inherited Create(Engine);  
  BodyData.IsShip := true;
  BodyData.IsPlayer := false;
  BodyData.IsLaser := false;
  BodyData.Ship := Self;
  FlareCreateTime := Game.CurTime;
  FlareCreateLength := 200;
  SmokeCreateTime := Game.CurTime;
  SmokeCreateLength := 50;
  MaxSpeed := 5;
  MoveUp := false;
  MoveDown := false;
  MoveLeft := false;
  MoveRight := false;
  Acc := 0.06;
  B := Game.PhysWorld.CreateBody;
  S := TCSShape.Create;
  S.AddCircle(CSVector(0, 0), 24);
  B.Shape := S;
  S.Free;
  B.Friction := 0.3;
  B.Hardness := 1;
  B.VelLinear^ := CSVector(0, 0);
  B.AccLinear^ := CSVector(0, 0);
  B.Data := @BodyData;
  ShotTime := Game.CurTime;
  ShotLength := 300;
  MaxHealth := 100;
  Health := 100;
  IsDead := false;
end;

destructor TShip.Destroy;
var
  i: integer;
begin
  for i := 0 to Game.Ships.Count - 1 do
  if (TShip(Game.Ships.Items[i]) <> Self)
  and (TShip(Game.Ships.Items[i]) is TBot)
  and (TBot(Game.Ships.Items[i]).TargetShip = Self) then
  TBot(Game.Ships.Items[i]).TargetShip := nil;
  Game.PhysWorld.DestroyBody(B);
  inherited Destroy;
end;

procedure TShip.Process(SpeedFactor: single);
var
  Dir: TCSVector;
  DirN: TCSVector;
  v: TCSVector;
begin
  inherited Process(SpeedFactor);
  if IsDead then Exit;
  if MoveUp
  or MoveDown
  or MoveLeft
  or MoveRight then
  begin
    Dir := CSVector(0, 0);
    if MoveUp then Dir.y := Dir.y - 1;
    if MoveDown then Dir.y := Dir.y + 1;
    if MoveLeft then Dir.x := Dir.x - 1;
    if MoveRight then Dir.x := Dir.x + 1;
    if CSVecLength(Dir) > 0 then
    begin
      DirN := CSVecNormalize(Dir);
      if Game.CurTime - SmokeCreateTime > SmokeCreateLength then
      begin
        SmokeCreateTime := Game.CurTime;
        with TSmoke.Create(Game.SmokeEn) do
        begin
          x := B.Pos^.x - DirN.x * 32;
          y := B.Pos^.y - DirN.y * 32;
        end;
      end;
      Dir := (DirN * Acc) * B.Rot^;
      B.AccLinear^ := B.AccLinear^ + Dir;
    end;
  end;
  if CSVecLength(B.AccLinear^) > MaxSpeed then
  B.AccLinear^ := CSVecNormalize(B.AccLinear^) * MaxSpeed;
  B.AccLinear^.y := B.AccLinear^.y + Gravity;
  v := CSVector(0, 0.02) * B.Rot^;
  B.VelAngular^ := B.VelAngular^ * 0.995 + v.x;
  if Health < 0 then
  begin
    Health := 0;
    IsDead := true;
    Dead;
    with TExplosion.Create(Game.ExplosionEn) do
    begin
      x := B.Pos^.x;
      y := B.Pos^.y;
    end;
    if Self is TEnemy then
    begin
      Game.Map.EnemyDieTime := Game.CurTime;
      Game.Score := Game.Score + 10;
    end;
    if Self is TPlayer then
    begin
      Game.ResLost := true;
      Game.ResTime := Game.CurTime;
    end;
  end;
end;

procedure TShip.Draw;
var
  Dir: TCSVector;
  Pos: TCSVector;
  r, sc: single;
  Ang: single;
begin
  inherited Draw;
  if MoveUp
  or MoveDown
  or MoveLeft
  or MoveRight then
  begin
    Dir := CSVector(0, 0);
    if MoveUp then Dir.y := Dir.y - 1;
    if MoveDown then Dir.y := Dir.y + 1;
    if MoveLeft then Dir.x := Dir.x - 1;
    if MoveRight then Dir.x := Dir.x + 1;
    Dir := CSVecNormalize(Dir) * B.Rot^;
    r := (GetAngRad(0, 0, Dir.x, Dir.y) + HalfPi);
    Pos := B.Pos^ - Game.Map.Pos;
    sc := (Random(11) + 40) * 0.02;
    Game.DanJetX1.BlendMode := bmSrcColAdd;
    Game.tex_Flame.Draw(
      Pos.x - Game.tex_Flame.Width * 0.5, Pos.y,
      $ffffffff,
      0.5, 0,
      r, sc, sc
    );
    Game.DanJetX1.BlendMode := bmNormal;
  end;
  Pos := B.Pos^ - Game.Map.Pos;
  Pos.x := Pos.x - Game.tex_Ship1.Width * 0.5;
  Pos.y := Pos.y - Game.tex_Ship1.Height * 0.5;
  Ang := B.Ang;
  ShipTex.Draw(
    Pos.x, Pos.y, $ffffffff,
    0.5, 0.5, Ang,
    1, 1
  );
  Game.DanJetX1.Primitives2D.Rectangle(
    B.Pos^.x - 32 - Game.Map.Pos.x - 2, B.Pos^.y - 40 - Game.Map.Pos.y - 2,
    64 + 4, 2 + 4,
    $88888888
  );
  Game.DanJetX1.Primitives2D.Rectangle(
    B.Pos^.x - 32 - Game.Map.Pos.x, B.Pos^.y - 40 - Game.Map.Pos.y,
    (Health / MaxHealth) * 64, 2,
    $ffff0000
  );
end;

procedure TShip.SetPos(x, y: single);
begin
  B.Pos^ := CSVector(x, y);
end;

procedure TShip.Shoot(TargetX, TargetY: single);
begin
  if (Game.CurTime - ShotTime > ShotLength) then
  begin
    ShotTime := Game.CurTime;
    with TLaser.Create(Game.LaserEn) do
    begin
      x := Self.B.Pos^.x;
      y := Self.B.Pos^.y;
      Ang := GetAngRad(
        x, y,
        TargetX,
        TargetY
      );
      BodyData.Ship := Self;
      InitLaser;
    end;
  end;
end;
//TShip END

//TPlayer BEGIN
constructor TPlayer.Create(Engine: TDJXEngine);
begin
  inherited Create(Engine);
  ShotLength := 200;
  BodyData.IsPlayer := true;
  ShipTex := Game.tex_Ship1;
  Game.PlayerDead := false;
  GravTime := Game.CurTime;
  GravLength := 300;
end;

destructor TPlayer.Destroy;
begin
  Game.PlayerDead := true;
  inherited Destroy;
end;

procedure TPlayer.Process(SpeedFactor: single);
begin
  inherited Process(SpeedFactor);
  if B.Pos^.x < 0 then
  Dead;
  if IsDead then Exit;
  MoveUp := Game.Keys[Ord('W')];
  MoveDown := Game.Keys[Ord('S')];
  MoveLeft := Game.Keys[Ord('A')];
  MoveRight := Game.Keys[Ord('D')];
  if Game.MDL then
  Shoot(
    Game.DanJetX1.MousePos.x + Game.Map.Pos.x,
    Game.DanJetX1.MousePos.y + Game.Map.Pos.y
  );
  if Game.MDR then
  begin
    Game.GravPoint(
      Game.DanJetX1.MousePos.x + Game.Map.Pos.x,
      Game.DanJetX1.MousePos.y + Game.Map.Pos.y
    );
    if (Game.CurTime - GravTime > GravLength) then
    begin
      GravTime := Game.CurTime;
      with TGravFlash.Create(Game.GravFlashEn) do
      begin
        x := Game.DanJetX1.MousePos.x + Game.Map.Pos.x;
        y := Game.DanJetX1.MousePos.y + Game.Map.Pos.y;
      end;
    end;
  end;
  if distance_dbl(
    B.Pos^.x, B.Pos^.y, 0,
    Game.Map.Finish.X * Game.Map.CellSize + Game.Map.CellSize * 0.5,
    Game.Map.Finish.Y * Game.Map.CellSize + Game.Map.CellSize * 0.5,
    0
  ) < Game.Map.CellSize * 0.5 then
  begin
    Dead;
    Game.Score := Game.Score + 30;
    Game.ResWin := true;
    Game.ResTime := Game.CurTime;
    with TFlash.Create(Game.FlashEn) do
    begin
      X := Self.B.Pos^.x;
      Y := Self.B.Pos^.y;
    end;
  end;
end;

procedure TPlayer.Draw;
begin
  inherited Draw;
end;
//TPlayer END

//TBot BEGIN
constructor TBot.Create(Engine: TDJXEngine);
begin
  inherited Create(Engine);
  AITime := Game.CurTime;
  AILength := 500;
  TargetShip := nil;
end;

destructor TBot.Destroy;
begin
  inherited Destroy;
end;

procedure TBot.Process(SpeedFactor: single);
begin
  if Game.CurTime - AITime > AILength then
  begin
    AITime := Game.CurTime;
    DoAI;
  end;
  inherited Process(SpeedFactor);
  if Assigned(TargetShip) then
  begin
    Shoot(TargetShip.B.Pos^.x, TargetShip.B.Pos^.y);
  end;
end;

procedure TBot.Draw;
begin
  inherited Draw;
end;

procedure TBot.DoAI;
var
  cwp, cp: TPoint;
  tg: TCSVector;
  HCell: single;
begin
  HCell := Game.Map.CellSize * 0.5;
  MoveUp := false;
  MoveDown := false;
  MoveLeft := false;
  MoveRight := false;
  cp := Point(
    Trunc(B.Pos^.x / Game.Map.CellSize),
    Trunc(B.Pos^.y / Game.Map.CellSize)
  );
  if Assigned(TargetShip) and not DoAIShipVisible(TargetShip) then
  TargetShip := nil;
  if (TargetShip = nil) and (CurWayPoint < Length(WayPoints)) then
  begin
    cwp := Point(WayPoints[CurWayPoint].x, WayPoints[CurWayPoint].y);
    if distance_dbl(cp.x, cp.Y, 0, cwp.X, cwp.Y, 0) > 2 then
    DoAIGoTo(TargetPos.x, TargetPos.y)
    else
    begin
      tg := CSVector(
        cwp.x * Game.Map.CellSize + Game.Map.CellSize * 0.5,
        cwp.y * Game.Map.CellSize + Game.Map.CellSize * 0.5
      );
      if B.Pos^.y > tg.y then MoveUp := true;
      if B.Pos^.x < tg.x - HCell then MoveRight := true;
      if B.Pos^.x > tg.x + HCell then MoveLeft := true;
      if (B.Pos^.x > tg.x - HCell) and (B.Pos^.x < tg.x + HCell) then
      begin
        if B.VelLinear^.x < 0 then MoveRight := true;
        if B.VelLinear^.x > 0 then MoveLeft := true;
      end;
      if distance_dbl(B.Pos^.x, B.Pos^.y, 0, tg.x, tg.y, 0) < Game.Map.CellSize * 0.5 then
      CurWayPoint := CurWayPoint + 1;
    end;
  end;
  if B.VelLinear^.y > 1 then
  begin
    MoveUp := true;
    MoveDown := false;
  end;
  if B.VelLinear^.y < -2 then
  begin
    MoveUp := false;
    MoveDown := true;
  end;
end;

procedure TBot.DoAIGoTo(DestX, DestY: single);
begin
  TargetPos := CSVector(DestX, DestY);
  Game.PathFinder.FindPath(
    Game.Map.PathMap,
    Trunc(B.Pos.x / Game.Map.CellSize),
    Trunc(B.Pos.y / Game.Map.CellSize),
    Trunc(DestX / Game.Map.CellSize),
    Trunc(DestY / Game.Map.CellSize),
    @WayPoints
  );
  CurWayPoint := 0;
end;

function TBot.DoAIShipVisible(Ship: TShip): boolean;
var
  i: integer;
  S: TCSShape;
  P: TCSPolygon;
  Prim: TCSPrimPolygon;
begin
  Result := true;
  SetLength(P, 2);
  P[0] := B.Pos^;
  P[1] := Ship.B.Pos^;
  for i := 0 to Game.Map.ColyCount - 1 do
  begin
    Prim := TCSPrimPolygon(Game.Map.Colys[i].Shape.Prims.First);
    if CSPolyIntersect(P, Prim.Polygon^) then
    begin
      Result := false;
      Break;
    end;
  end;
end;
//TBot END

//TAlly BEGIN
constructor TAlly.Create(Engine: TDJXEngine);
begin
  inherited Create(Engine);
  ShipTex := Game.tex_Bot1;
  B.Pos^ := CSVector(
    Game.Map.Start.X * Game.Map.CellSize + Game.Map.CellSize * 0.5,
    Game.Map.Start.Y * Game.Map.CellSize + Game.Map.CellSize * 0.5
  );
  Game.Map.AllysCreated := Game.Map.AllysCreated + 1;
  AILength := 100;
  MaxSpeed := 2;
  Acc := 0.04;
end;

destructor TAlly.Destroy;
begin
  inherited Destroy;
end;

procedure TAlly.Process(SpeedFactor: single);
begin
  inherited Process(SpeedFactor);
  if IsDead then Exit;
  if distance_dbl(
    B.Pos^.x, B.Pos^.y, 0,
    Game.Map.Finish.X * Game.Map.CellSize + Game.Map.CellSize * 0.5,
    Game.Map.Finish.Y * Game.Map.CellSize + Game.Map.CellSize * 0.5,
    0
  ) < Game.Map.CellSize * 0.5 then
  begin
    Dead;
    Game.Score := Game.Score + 25;
    with TFlash.Create(Game.FlashEn) do
    begin
      X := Self.B.Pos^.x;
      Y := Self.B.Pos^.y;
    end;
  end;
end;

procedure TAlly.Draw;
begin
  inherited Draw;
end;

procedure TAlly.DoAI;
begin
  if Length(WayPoints) = 0 then
  DoAIGoTo(
    Game.Map.Finish.X * Game.Map.CellSize + Game.Map.CellSize * 0.5,
    Game.Map.Finish.Y * Game.Map.CellSize + Game.Map.CellSize * 0.5
  );
  inherited DoAI;
end;
//TAlly END

//TEnemy BEGIN
constructor TEnemy.Create(Engine: TDJXEngine);
var
  i: integer;
begin
  inherited Create(Engine);
  ShipTex := Game.tex_Enemy1;
  Game.Map.EnemiesCreated := Game.Map.EnemiesCreated + 1;
  if Length(Game.Map.EnemyPortals) = 0 then
  B.Pos^ := CSVector(
    Game.Map.Finish.X * Game.Map.CellSize + Game.Map.CellSize * 0.5,
    Game.Map.Finish.Y * Game.Map.CellSize + Game.Map.CellSize * 0.5
  )
  else
  begin
    i := Random(Length(Game.Map.EnemyPortals));
    B.Pos^ := CSVector(
      Game.Map.EnemyPortals[i].X * Game.Map.CellSize + Game.Map.CellSize * 0.5,
      Game.Map.EnemyPortals[i].Y * Game.Map.CellSize + Game.Map.CellSize * 0.5
    )
  end;
  AILength := 100;
  MaxSpeed := 3;
  Acc := 0.04;
end;

destructor TEnemy.Destroy;
begin
  Game.Map.EnemiesCreated := Game.Map.EnemiesCreated - 1;
  inherited Destroy;
end;

procedure TEnemy.Process(SpeedFactor: single);
begin
  inherited Process(SpeedFactor);
end;

procedure TEnemy.Draw;
var
  i: integer;
  col: TDJXColor;
begin
  inherited Draw;
  {for i := 0 to High(WayPoints) do
  begin
    if i = CurWaypoint then Col := $00ff00 else Col := $ff0000;
    Game.DanJetX1.Primitives2D.Circle2col(
      WayPoints[i].X * Game.Map.CellSize + Game.Map.CellSize * 0.5 - Game.Map.Pos.x,
      WayPoints[i].Y * Game.Map.CellSize + Game.Map.CellSize * 0.5 - Game.Map.Pos.y,
      20, 5, $ff000000 or Col, Col
    );
  end;  }
end;

procedure TEnemy.DoAI;
begin
  if not Game.PlayerDead then
  TargetShip := Game.Player
  else
  TargetShip := nil;
  if Assigned(TargetShip)
  and (
    (Length(WayPoints) = 0)
    or (
      (Length(WayPoints) > 0)
      and (
        distance_dbl(
          WayPoints[High(Waypoints)].X,
          WayPoints[High(Waypoints)].Y,
          0,
          Trunc(TargetShip.B.Pos^.x / Game.Map.CellSize),
          Trunc(TargetShip.B.Pos^.y / Game.Map.CellSize),
          0
        ) > 3
      )
    )
  ) then
  DoAIGoTo(TargetShip.B.Pos^.x, TargetShip.B.Pos^.y);
  inherited DoAI;
end;
//TEnemy END

//TSmoke BEGIN
constructor TSmoke.Create;
begin
  inherited Create(Engine);
  TimeCreate := Game.CurTime;
  TimeLive := 1000;
  Rotation := GetRandomPi;
  RotationSpeed := (Random(101) - 50) * 0.0001;
end;

destructor TSmoke.Destroy;
begin
  inherited Destroy;
end;

procedure TSmoke.Process(SpeedFactor: single);
var
  g: single;
begin
  inherited Process(SpeedFactor);
  if Game.CurTime - TimeCreate > TimeLive then Dead
  else
  begin
    g := (Game.CurTime - TimeCreate) / TimeLive;
    Alpha := Trunc((1 - g) * 255);
    Scale := 0.5 + g;
    Rotation := Rotation + RotationSpeed;
  end;
end;

procedure TSmoke.Draw;             
begin
  inherited Draw;
  with Game do
  begin
    tex_Smoke.Draw(
      x - tex_Smoke.Width * 0.5 - Game.Map.Pos.x,
      y - tex_Smoke.Height * 0.5 - Game.Map.Pos.y,
      $ffffff or (Alpha shl 24), 0.5, 0.5, Rotation, Scale, Scale
    );
  end;
end;
//TSmoke END

//TExplosion BEGIN
constructor TExplosion.Create;
begin
  inherited Create(Engine);
  TimeCreate := Game.CurTime;
  TimeLive := 300;
  Rotation := GetRandomPi;
  RotationSpeed := (Random(101) - 50) * 0.002;
end;

destructor TExplosion.Destroy;
begin
  inherited Destroy;
end;

procedure TExplosion.Process(SpeedFactor: single);
var
  g: single;
begin
  inherited Process(SpeedFactor);
  if Game.CurTime - TimeCreate > TimeLive then Dead
  else
  begin
    g := (Game.CurTime - TimeCreate) / TimeLive;
    if g < 0.5 then
    Alpha := 255
    else
    Alpha := Trunc((1 - (g - 0.5) * 2) * 255);
    Scale := 0.3 + g * 2;
    Rotation := Rotation + RotationSpeed;
  end;
end;

procedure TExplosion.Draw;
var
  col: TDJXColor;            
begin
  inherited Draw;
  with Game do
  begin
    col := DJXColor(Alpha, Alpha, Alpha);
    DanJetX1.Primitives2D.Circle2Col(
      x - Game.Map.Pos.x, y - Game.Map.Pos.y,
      90 * Scale, 20, col, 0
    );
    tex_Explosion.Draw(
      x - tex_Explosion.Width * 0.5 - Game.Map.Pos.x,
      y - tex_Explosion.Height * 0.5 - Game.Map.Pos.y,
      col, 0.5, 0.5, Rotation, Scale, Scale
    );
    tex_Explosion.Draw(
      x - tex_Explosion.Width * 0.5 - Game.Map.Pos.x,
      y - tex_Explosion.Height * 0.5 - Game.Map.Pos.y,
      col, 0.5, 0.5, -Rotation, Scale, Scale
    );
  end;
end;
//TExplosion END

//TFlare BEGIN
constructor TFlare.Create;
var
  Ang: single;
  Speed: single;
begin
  inherited Create(Engine);
  TimeCreate := Game.CurTime;
  TimeLive := 1000;
  Rotation := GetRandomPi;
  RotationSpeed := (Random(101) - 50) * 0.0001;
  Ang := GetRandomPi;
  Speed := (Random(51) + 50) * 0.01;
  VelX := Cos(Ang) * Speed;
  VelY := Sin(Ang) * Speed;
end;

destructor TFlare.Destroy;
begin
  inherited Destroy;
end;

procedure TFlare.Process(SpeedFactor: single);
var
  g: single;
begin
  inherited Process(SpeedFactor);
  if Game.CurTime - TimeCreate > TimeLive then Dead
  else
  begin
    VelY := VelY + Gravity;
    x := x + VelX;
    y := y + VelY;
    g := (Game.CurTime - TimeCreate) / TimeLive;
    Alpha := Trunc((1 - g) * 255);
    Scale := 1 - g;
    Rotation := Rotation + RotationSpeed;
  end;
end;

procedure TFlare.Draw;             
begin
  inherited Draw;
  with Game do
  begin
    tex_Flare.Draw(
      x - tex_Flare.Width * 0.5 - Game.Map.Pos.x,
      y - tex_Flare.Height * 0.5 - Game.Map.Pos.y,
      $ffffff or (Alpha shl 24), 0.5, 0.5, Rotation, Scale, Scale
    );
  end;
end;
//TFlare END

//TLaser BEGIN
constructor TLaser.Create(Engine: TDJXEngine);
begin
  inherited Create(Engine);
  Ang := 0;
  Speed := 7;
  Damage := 5;
  BodyData.IsShip := false;
  BodyData.IsPlayer := false;
  BodyData.IsLaser := true;
  BodyData.Laser := Self;
  B := Game.PhysWorld.CreateBody;
end;

destructor TLaser.Destroy;
begin
  Game.PhysWorld.DestroyBody(B);
  inherited Destroy;
end;

procedure TLaser.Process(SpeedFactor: single);   
begin
  inherited Process(SpeedFactor);
  x := B.Pos^.x;
  y := B.Pos^.y;
  Ang := GetAngRad(0, 0, B.VelLinear^.x, B.VelLinear^.y);
  B.Rot^ := CSMatrixRotation(Ang);
end;

procedure TLaser.Draw;
begin
  inherited Draw;
  Game.DanJetX1.BlendMode := bmSrcColAdd;
  Game.tex_Laser2.Draw(
    x - Game.tex_Laser2.Width * 0.5 - Game.Map.Pos.x,
    y - Game.tex_Laser2.Height * 0.5 - Game.Map.Pos.y,
    $ff808080, 0.5, 0.5, Ang + HalfPi, 2, 2
  );
  Game.DanJetX1.BlendMode := bmNormal;
  Game.tex_Laser.Draw(
    x - Game.tex_Laser.Width * 0.5 - Game.Map.Pos.x,
    y - Game.tex_Laser.Height * 0.5 - Game.Map.Pos.y,
    $ffffffff, 0.5, 0.5, Ang + HalfPi, 1, 1
  );     
end;

procedure TLaser.InitLaser;
var
  S: TCSShape;
  P: TCSPolygon;
begin
  SetLength(P, 2);
  P[0] := CSVector(-16, 0);
  P[1] := CSVector(16, 0);
  S := TCSShape.Create;
  S.AddPolygon(P);
  S.AddCircle(CSVector(0, 0), 8);
  B.Shape := S;
  B.Rot^ := CSMatrixRotation(Ang);
  B.VelLinear^ := CSVector(Cos(Ang) * Speed, Sin(Ang) * Speed);
  B.Pos^ := CSVector(x, y) + B.VelLinear^;
  S.Free;
  B.Data := @BodyData;
  B.Mass := 0.1;
end;
//TLaser END

//TLaserFlash BEGIN
constructor TLaserFlash.Create;
begin
  inherited Create(Engine);
  TimeCreate := Game.CurTime;
  TimeLive := 400;
end;

destructor TLaserFlash.Destroy;
begin
  inherited Destroy;
end;

procedure TLaserFlash.Process(SpeedFactor: single);
var
  g: single;
begin
  inherited Process(SpeedFactor);
  if Game.CurTime - TimeCreate > TimeLive then Dead
  else
  begin
    g := (Game.CurTime - TimeCreate) / TimeLive;
    Alpha := Trunc((1 - g) * 255);
    Scale := 0.5 + g;
  end;
end;

procedure TLaserFlash.Draw;
var
  col1, col2: TDJXColor;
begin
  inherited Draw;
  with Game do
  begin
    col1 := DJXColor(0, 0, 0);
    col2 := DJXColor(Alpha, 0, 0);
    DanJetX1.Primitives2D.Circle2col(
      x - Game.Map.Pos.x, y - Game.Map.Pos.y, Scale * 30, 10,
      col1, col2
    );
  end;
end;
//TLaserFlash END

//TGravFlash BEGIN
constructor TGravFlash.Create;
begin
  inherited Create(Engine);
  TimeCreate := Game.CurTime;
  TimeLive := 400;
end;

destructor TGravFlash.Destroy;
begin
  inherited Destroy;
end;

procedure TGravFlash.Process(SpeedFactor: single);
var
  g: single;
begin
  inherited Process(SpeedFactor);
  if Game.CurTime - TimeCreate > TimeLive then Dead
  else
  begin
    g := (Game.CurTime - TimeCreate) / TimeLive;
    Alpha := Trunc((1 - g) * 255);
    Scale := 1.5 - g;
  end;
end;

procedure TGravFlash.Draw;
var
  col1, col2: TDJXColor;
begin
  inherited Draw;
  with Game do
  begin
    col1 := DJXColor(0, Alpha, 0);
    col2 := DJXColor(255 - Alpha, 255 - Alpha, 0);
    DanJetX1.Primitives2D.Circle2col(
      x - Game.Map.Pos.x, y - Game.Map.Pos.y, Scale * 30, 20,
      col1, col2
    );
  end;
end;
//TGravFlash END

//TFlash BEGIN
constructor TFlash.Create;
begin
  inherited Create(Engine);
  TimeCreate := Game.CurTime;
  TimeLive := 400;
end;

destructor TFlash.Destroy;
begin
  inherited Destroy;
end;

procedure TFlash.Process(SpeedFactor: single);
var
  g: single;
begin
  inherited Process(SpeedFactor);
  if Game.CurTime - TimeCreate > TimeLive then Dead
  else
  begin
    g := (Game.CurTime - TimeCreate) / TimeLive;
    Alpha := Trunc((1 - g) * 255);
    Scale := 1.5 - g;
  end;
end;

procedure TFlash.Draw;
var
  col1, col2: TDJXColor;
begin
  inherited Draw;
  with Game do
  begin
    col1 := DJXColor(Alpha, Alpha, Alpha);
    col2 := DJXColor(0, 0, 0);
    DanJetX1.Primitives2D.Circle2col(
      x - Game.Map.Pos.x, y - Game.Map.Pos.y, Scale * 100, 20,
      col1, col2
    );
  end;
end;
//TFlash END

//TPhysObj BEGIN
constructor TPhysObj.Create(Engine: TDJXEngine);
begin
  inherited Create(Engine);
end;

destructor TPhysObj.Destroy;                    
begin
  inherited Destroy;
end;

procedure TPhysObj.Process(SpeedFactor: single);
begin
  inherited Process(SpeedFactor);
  B.AccLinear^.y := B.AccLinear^.y + Gravity;
  {if CSVecLength(B.VelLinear^) > 5 then
  B.VelLinear^ := CSVecNormalize(B.VelLinear^) * 5 ;
  if Abs(B.VelAngular^) > 0.3 then B.VelAngular^ := Sign(B.VelAngular^) * 0.3; }
end;

procedure TPhysObj.Draw;
var
  Ang: single;
begin
  Ang := B.Ang;
  ObjTex.Draw4col(
    B.Pos^.x - w - Game.Map.Pos.x, B.Pos^.y - h - Game.Map.Pos.y,
    B.Pos^.x + w - Game.Map.Pos.x, B.Pos^.y - h - Game.Map.Pos.y,
    B.Pos^.x + w - Game.Map.Pos.x, B.Pos^.y + h - Game.Map.Pos.y,
    B.Pos^.x - w - Game.Map.Pos.x, B.Pos^.y + h - Game.Map.Pos.y,
    $ffffffff, $ffffffff, $ffffffff, $ffffffff,
    0.5, 0.5, Ang, 1, 1
  );
end;

procedure TPhysObj.InitObj;
var
  S: TCSShape;
  P: TCSPolygon;
begin
  B := Game.PhysWorld.CreateBody;
  S := TCSShape.Create;
  case Obj.ObjType of
    otBall:
    begin
      w := Obj.Size.x;
      h := Obj.Size.x;
      ObjTex := Game.tex_Ball;
      S.AddCircle(CSVector(0, 0), Obj.Size.x);
    end;
    otBox:
    begin
      w := Obj.Size.x;
      h := Obj.Size.y;
      ObjTex := Game.tex_Box;
      SetLength(P, 4);
      P[0] := CSVector(-w, -h);
      P[1] := CSVector(w, -h);
      P[2] := CSVector(w, h);
      P[3] := CSVector(-w, h);
      S.AddPolygon(P);
    end;
  end;
  B.Shape := S;
  B.Pos^ := Obj.Pos;
  //B.Inertia := 1;
  B.Mass := 0.8;
  B.Friction := 0.2;
  B.Hardness := 0.6;
  S.Free;
end;
//TPhysObj END

function GetParam(Str: string; SubStr: string; Param: integer): string;
var
  i: integer;
  j: integer;
  CurParam: integer;
  PrevParamIndex: integer;
  b: boolean;
begin
  Result := '';
  b := false;
  CurParam := 0;
  PrevParamIndex := 1;
  for i := 1 to Length(Str) do
  begin
    for j := 1 to Length(SubStr) do
    begin
      if Str[i + j] <> SubStr[j] then
      break
      else
      begin
        if j = length(SubStr) then
        begin
          if CurParam = Param then
          begin
            b := true;
            SetLength(Result, i - PrevParamIndex + 1);
            move(Str[PrevParamIndex], Result[1], Length(Result));
          end
          else
          begin
            CurParam := CurParam + 1;
            PrevParamIndex := i + Length(SubStr) + 1;
          end;
        end;
      end;
    end;
    if b then break;
    if (i = Length(Str)) and (CurParam = Param) then
    begin
      SetLength(Result, i - PrevParamIndex + 1);
      move(Str[PrevParamIndex], Result[1], Length(Result));
    end;
  end;
end;

end.