unit BTGame;

interface

uses
  Windows,
  SysUtils,
  Controls,
  Math,
  Types,
  DXTypes,
  Direct3D9,
  D3DX9,
  DirectInput,
  Gen2,
  G2Math,
  G2MeshLoaderG2M,
  G2Group,
  CSD;

type
  TFloorType = (ftGrass, ftPavement, ftWater);
  TWallType = (wtNone, wtBrick, wtConcrete);

  TExplosion = class (TG2GroupItem)
  public
    var Pos: TG2Vec3;
    var TimeCreate: DWord;
    var TimeLive: DWord;
    var t: Single;
    constructor Create(const Group: TG2Group); override;
    destructor Destroy; override;
    procedure Render(const Pass: Integer = 0); override;
    procedure Update; override;
  end;

  TDust = class (TG2GroupItem)
  public
    var Pos: TG2Vec3;
    var TimeCreate: DWord;
    var TimeLive: DWord;
    var t: Single;
    var rd: Integer;
    constructor Create(const Group: TG2Group); override;
    destructor Destroy; override;
    procedure Render(const Pass: Integer = 0); override;
    procedure Update; override;
  end;

  TMapCell = record
    Floor: TFloorType;
    Wall: TWallType;
    Broken: Boolean;
    Poly: TCSPoly;
  end;

  TMap = record
    Cells: array[0..59, 0..59] of TMapCell;
    PSpawn: array of TPoint;
    ESpawn: array of TPoint;
  end;

  TTank = record
    Enabled: Boolean;
    Pos: TG2Vec2;
    Ang: Single;
    GunAng: Single;
    MoveL: Boolean;
    MoveU: Boolean;
    MoveR: Boolean;
    MoveD: Boolean;
    AimAt: TG2Vec2;
    Health: Integer;
    UID: DWord;
    ShotTime: DWord;
    Align: Byte;
    MeshInst: TG2MeshInst;
  end;
  PTank = ^TTank;

  TShot = record
    Enabled: Boolean;
    Pos: TG2Vec2;
    Dir: TG2Vec2;
    OwnerUID: DWord;
    Align: Byte;
  end;

  TPlayer = record
    Enabled: Boolean;
    TankID: Integer;
    RespawnTime: DWord;
    Lives: Integer;
  end;

  TBot = record
    Enabled: Boolean;
    TankID: Integer;
    ChangeDirTime: DWord;
    ReactionTime: DWord;
    Dir: TPoint;
  end;

  TMenuItem = record
    R: TRect;
    Tex0, Tex1: TG2Texture2D;
    Proc: TG2ProcObj;
  end;

  TMenu = record
    Items: array of TMenuItem;
  end;

  TTimedExpl = record
    Pos: TG2Vec3;
    Time: DWord;
    Delay: DWord;
  end;
  PTimedExpl = ^TTimedExpl;

  TGameState = (gsLoading, gsMenu, gsGame, gsEnd);

  TBTGame = class (TG2App)
  public
    GameState: TGameState;
    Font1: TG2Font;
    MapPos: TG2Vec2;
    Map: TMap;
    MgrTextures: TG2TextureMgr;
    MgrMeshes: TG2MeshMgr;
    MgrSurfaces: TG2SurfaceMgr;
    MgrEffects: TG2EffectMgr;
    MgrSounds: TG2SoundMgr;
    MgrMusic: TG2MusicMgr;
    FxShaders: TG2Effect;
    TexTitle: TG2Texture2D;
    TexStart0: TG2Texture2D;
    TexStart1: TG2Texture2D;
    TexQuit0: TG2Texture2D;
    TexQuit1: TG2Texture2D;
    TexDefeat: TG2Texture2D;
    TexVictory: TG2Texture2D;
    TexTankIcon: TG2Texture2D;
    TexGrass: TG2Texture2D;
    TexPavement: TG2Texture2D;
    TexWater: TG2Texture2D;
    TexWaterN: TG2Texture2D;
    TexBrick: TG2Texture2D;
    TexConcrete: TG2Texture2D;
    TexGrassMask: TG2Texture2D;
    TexPavementMask: TG2Texture2D;
    TexBlast: TG2Texture2D;
    TexExpl: TG2Texture2D;
    TexDust: TG2Texture2D;
    TexTankA0: TG2Texture2D;
    TexTankA1: TG2Texture2D;
    TexTankB0: TG2Texture2D;
    TexTankB1: TG2Texture2D;
    TexHQ: TG2Texture2D;
    TexDepthRT: TG2Texture2DRT;
    SurDepthDS: TG2SurfaceDS;
    SndExplosion: TG2Sound;
    Music: TG2Music;
    MeshTankA: TG2Mesh;
    MeshHQ: TG2Mesh;
    Menu: TMenu;
    Player: TPlayer;
    CurUID: DWord;
    Shots: array of TShot;
    ShotCount: Integer;
    Tanks: array of TTank;
    TankCount: Integer;
    Bots: array of TBot;
    BotCount: Integer;
    GroupExpl: TG2Group;
    GroupDust: TG2Group;
    TimedExpl: TG2QuickList;
    MatB: TG2Mat;
    BotSpawnTime: DWord;
    MapUpdate: Boolean;
    MapVBBrick: TG2VB;
    MapIBBrick: TG2IB;
    MapVBConcrete: TG2VB;
    MapIBConcrete: TG2IB;
    MapVBCountBrick: Integer;
    MapIBCountBrick: Integer;
    MapVBCountConcrete: Integer;
    MapIBCountConcrete: Integer;
    HQPos: TG2Vec2;
    HQHealth: Integer;
    HQAlive: Boolean;
    Victory: Boolean;
    EndGame: Boolean;
    EndGameTime: DWord;
    BotsToSpawn: Integer;
    CurLevel: Integer;
    procedure OnRender; override;
    procedure OnUpdate; override;
    procedure OnKeyDown(const Key: Byte); override;
    procedure OnMouseDown(const Button: Byte); override;
    procedure Initialize; override;
    procedure Finalize; override;
    procedure LoadMap(const f: String);
    procedure InitializeMenu;
    procedure RenderLoading;
    procedure RenderMenu;
    procedure RenderGame;
    procedure RenderEnd;
    procedure RenderMap3D;
    procedure RenderTank3D(const Tank: TTank);
    procedure RenderShot3D(const Shot: TShot);
    procedure UpdateMenu;
    procedure UpdateGame;
    procedure UpdateEnd;
    procedure UpdateTank(var Tank: TTank);
    procedure UpdateShot(var Shot: TShot);
    procedure UpdateBot(var Bot: TBot);
    procedure UpdateMapCells(const MinX, MinY, MaxX, MaxY: Integer);
    procedure TankShoot(var Tank: TTank);
    procedure TankKill(var Tank: TTank);
    procedure ClearTanks;
    procedure ClearAll;
    function Trace(const PtStart, PtEnd: TG2Vec2): Boolean;
    function NewTank(const PosX, PosY: Integer; const Align: Byte = 0): Integer;
    procedure NewPlayer;
    procedure NewBot;
    procedure CreateDust(const x, y: Single; const Count: Integer; const Spread: Single = 0.5);
    procedure CreateTimedExpl(const x, y: Single; const Delay: DWord);
    function ActiveBotCount: Integer;
    function GetTank3DMat(const Tank: TTank; const Part: Integer = 0): TG2Mat; inline;
    function CheckCell(const x, y: Integer): Byte;
    function GetUID: DWord;
    function LevelExists(const LevelID: Integer): Boolean;
    procedure BeginLevel(const LevelID: Integer);
    procedure EndLevel;
    procedure GameStart;
    procedure GameQuit;
  end;

  const CellSize = 12;

  var Game: TBTGame;

implementation

//TBBGame BEGIN
procedure TBTGame.OnRender;
begin
  Render.Clear(False, True, True, $ffffffff);
  Render.RenderStart;

  MatB := Gfx.Transforms.Vb;
  case GameState of
    gsMenu: RenderMenu;
    gsGame: RenderGame;
    gsEnd: RenderEnd;
  end;

  //Font1.Print(10, 10, $ff000000, 'FPS: ' + IntToStr(Tmr.FPS));

  Render.RenderStop;
  Render.Present;
end;

procedure TBTGame.OnUpdate;
begin
  case GameState of
    gsMenu: UpdateMenu;
    gsGame: UpdateGame;
    gsEnd: UpdateEnd;
  end;
end;

procedure TBTGame.OnKeyDown(const Key: Byte);
begin
  case GameState of
    gsMenu:
    begin
      if Key = DIK_ESCAPE then Running := False;
    end;
    gsGame:
    begin
      if Key = DIK_ESCAPE then
      begin
        ClearAll;
        GameState := gsMenu;
      end;
    end;
    gsEnd:
    begin
      GameState := gsMenu;
    end;
  end;
end;

procedure TBTGame.OnMouseDown(const Button: Byte);
  var i: Integer;
begin
  case GameState of
    gsMenu:
    begin
      for i := 0 to High(Menu.Items) do
      if PtInRect(Menu.Items[i].R, PlugInput.MousePos) then
      Menu.Items[i].Proc;
    end;
    gsGame:
    begin
      if (Button = 0) and Tanks[Player.TankID].Enabled then
      begin
        TankShoot(Tanks[Player.TankID]);
      end;
    end;
    gsEnd:
    begin
      GameState := gsMenu;
    end;
  end;
end;

procedure TBTGame.Initialize;
  var i, j: Integer;
begin
  if Gfx.Specs.CompatiableResolutionCount > 0 then
  begin
    j := Gfx.Specs.CompatiableResolutionCount - 1;
    for i := 0 to Gfx.Specs.CompatiableResolutionCount - 1 do
    if Gfx.Specs.CompatiableResolutions[i].Width >= 1024 then
    begin
      j := i;
      Break;
    end;
    Gfx.InitParams.Width := Gfx.Specs.CompatiableResolutions[j].Width;
    Gfx.InitParams.Height := Gfx.Specs.CompatiableResolutions[j].Height;
  end
  else
  begin
    Gfx.InitParams.Width := 800;
    Gfx.InitParams.Height := 600;
  end;
  Game := Self;
  Randomize;
  Gfx.InitParams.FullScreen := True;
  Gfx.InitParams.VSync := False;
  Gfx.InitParams.FormatBackBuffer := D3DFMT_A8R8G8B8;
  Gfx.InitParams.FormatSurfaceRT := D3DFMT_A8R8G8B8;
  Gfx.InitParams.FormatSurfaceDS := D3DFMT_D24S8;
  g2.Handle := WindowCreate;//(Gfx.InitParams.Width, Gfx.InitParams.Height, 'Gen2');
  Tmr.Mode := tmManual;
  Tmr.MaxFPS := 0;
  inherited Initialize;
  Font1 := Gfx.Shared.RequestFont('Times New Roman', 16);
  GameState := gsLoading;
  RenderLoading;
  g2.RequestMod(TG2TextureMgr, @MgrTextures);
  g2.RequestMod(TG2MeshMgr, @MgrMeshes);
  g2.RequestMod(TG2SurfaceMgr, @MgrSurfaces);
  g2.RequestMod(TG2EffectMgr, @MgrEffects);
  g2.RequestMod(TG2SoundMgr, @MgrSounds);
  g2.RequestMod(TG2MusicMgr, @MgrMusic);
  g2.PackLinker.LinkPack('Data.g2pk');
  FxShaders := MgrEffects.CreateEffectFromPack('Shaders', 'Data', 'Shaders.cfx');
  TexTitle := MgrTextures.CreateTexture2DFromPack('Title', 'Data', 'Title.png', 1);
  TexStart0 := MgrTextures.CreateTexture2DFromPack('Start0', 'Data', 'MBNewGame0.png', 1);
  TexStart1 := MgrTextures.CreateTexture2DFromPack('Start1', 'Data', 'MBNewGame1.png', 1);
  TexQuit0 := MgrTextures.CreateTexture2DFromPack('Quit0', 'Data', 'MBQuit0.png', 1);
  TexQuit1 := MgrTextures.CreateTexture2DFromPack('Quit1', 'Data', 'MBQuit1.png', 1);
  TexDefeat := MgrTextures.CreateTexture2DFromPack('Defeat', 'Data', 'Defeat.png', 1);
  TexVictory := MgrTextures.CreateTexture2DFromPack('Victory', 'Data', 'Victory.png', 1);
  TexTankIcon := MgrTextures.CreateTexture2DFromPack('TankIcon', 'Data', 'TankIcon.png', 1);
  TexGrass := MgrTextures.CreateTexture2DFromPack('grass', 'Data', 'grass.jpg', 0);
  TexPavement := MgrTextures.CreateTexture2DFromPack('pavement', 'Data', 'pavement.jpg', 0);
  TexBrick := MgrTextures.CreateTexture2DFromPack('brick', 'Data', 'brick.jpg', 0);
  TexConcrete := MgrTextures.CreateTexture2DFromPack('concrete', 'Data', 'concrete.jpg', 0);
  TexWater := MgrTextures.CreateTexture2DFromPack('water', 'Data', 'clouds.jpg', 0);
  TexWaterN := MgrTextures.CreateTexture2DFromPack('nm1', 'Data', 'nm1.jpg', 0);
  TexBlast := MgrTextures.CreateTexture2DFromPack('blast', 'Data', 'Blast.jpg');
  TexExpl := MgrTextures.CreateTexture2DFromPack('expl', 'Data', 'expl.png');
  TexDust := MgrTextures.CreateTexture2DFromPack('dust', 'Data', 'Dust.png');
  TexHQ := MgrTextures.CreateTexture2DFromPack('hq', 'Data', 'hq.jpg');
  SndExplosion := MgrSounds.CreateSoundFromFile('Explosion', 'Explosion.wav');
  TexGrassMask := MgrTextures.CreateTexture2D('grass_mask', 60, 60, 1, 0, D3DFMT_A8R8G8B8);
  TexPavementMask := MgrTextures.CreateTexture2D('concrete_mask', 60, 60, 1, 0, D3DFMT_A8R8G8B8);
  TexTankA0 := MgrTextures.CreateTexture2DFromPack('TankA0', 'Data', 'Body Heavy Texture.jpg', 0);
  TexTankA1 := MgrTextures.CreateTexture2DFromPack('TankA1', 'Data', 'Barrel Heavy Texture.jpg', 0);
  TexTankB0 := MgrTextures.CreateTexture2DFromPack('TankB0', 'Data', 'Body Heavy Texture2.jpg', 0);
  TexTankB1 := MgrTextures.CreateTexture2DFromPack('TankB1', 'Data', 'Barrel Heavy Texture2.jpg', 0);
  MeshTankA := MgrMeshes.CreateMeshFromPack('TankA', 'Data', 'Heavy Tank.g2m');
  MeshHQ := MgrMeshes.CreateMeshFromPack('HQ', 'Data', 'hq.g2m');
  TexDepthRT := MgrTextures.CreateTexture2DRT('DepthRT', Gfx.InitParams.Width, Gfx.InitParams.Height, {D3DFMT_A8R8G8B8}D3DFMT_R32F);
  SurDepthDS := MgrSurfaces.CreateDepthStencilSurface('DepthDS', TexDepthRT.RealWidth, TexDepthRT.RealHeight);
  Music := MgrMusic.CreateMusic('Music');
  Music.OpenFile('Music.mp3');
  MapVBBrick := TG2VB.Create;
  MapVBBrick.Initialize(g2);
  MapIBBrick := TG2IB.Create;
  MapIBBrick.Initialize(g2);
  MapVBConcrete := TG2VB.Create;
  MapVBConcrete.Initialize(g2);
  MapIBConcrete := TG2IB.Create;
  MapIBConcrete.Initialize(g2);
  GroupExpl := TG2Group.Create;
  GroupDust := TG2Group.Create;
  TimedExpl.Clear;
  CurUID := 0;
  SetLength(Shots, 16);
  ShotCount := 0;
  SetLength(Tanks, 16);
  TankCount := 0;
  SetLength(Bots, 16);
  BotCount := 0;
  InitializeMenu;
  Music.Play;
  GameState := gsMenu;
  Tmr.Enabled := True;
end;

procedure TBTGame.Finalize;
begin
  ClearAll;
  MapVBBrick.Free;
  MapIBBrick.Free;
  MapVBConcrete.Free;
  MapIBConcrete.Free;
  GroupDust.Free;
  GroupExpl.Free;
  inherited Finalize;
end;

procedure TBTGame.LoadMap(const f: String);
  type TObjType = (otNone, otPSpawn, otESpawn, otHQ);
  var Def: array[0..3] of AnsiChar;
  var fs: TG2FileRW;
  var i, j: Integer;
  var Obj: TObjType;
begin
  if FileExists(f) then
  begin
    ZeroMemory(@Map, SizeOf(Map));
    fs := TG2FileRW.Create;
    try
      fs.OpenRead(f);
      fs.ReadBuffer(Def, 4);
      if Def = 'BBLV' then
      begin
        Map.PSpawn := nil;
        Map.ESpawn := nil;
        fs.Compression := True;
        TexGrassMask.Surfaces[0].Lock(D3DLOCK_DISCARD);
        TexPavementMask.Surfaces[0].Lock(D3DLOCK_DISCARD);
        for i := 0 to 59 do
        for j := 0 to 59 do
        begin
          fs.ReadBuffer(Map.Cells[i, j].Floor, SizeOf(Map.Cells[i, j].Floor));
          fs.ReadBuffer(Map.Cells[i, j].Wall, SizeOf(Map.Cells[i, j].Wall));
          fs.ReadBuffer(Obj, SizeOf(Obj));
          if Map.Cells[i, j].Floor = ftWater then
          Map.Cells[i, j].Broken := True;
          if Map.Cells[i, j].Wall <> wtNone then
          Map.Cells[i, j].Floor := ftPavement;
          if Map.Cells[i, j].Floor = ftPavement then
          TexPavementMask.Surfaces[0].Pixels[i, j] := $ffffffff
          else
          TexPavementMask.Surfaces[0].Pixels[i, j] := $00ffffff;
          if Map.Cells[i, j].Floor = ftWater then
          TexGrassMask.Surfaces[0].Pixels[i, j] := $00ffffff
          else
          TexGrassMask.Surfaces[0].Pixels[i, j] := $ffffffff;
          if Obj = otPSpawn then
          begin
            SetLength(Map.PSpawn, Length(Map.PSpawn) + 1);
            Map.PSpawn[High(Map.PSpawn)] := Point(i, j);
          end;
          if Obj = otESpawn then
          begin
            SetLength(Map.ESpawn, Length(Map.ESpawn) + 1);
            Map.ESpawn[High(Map.ESpawn)] := Point(i, j);
          end;
          if Obj = otHQ then
          begin
            HQPos.SetValue(i + 2, j + 2);
            HQHealth := 1;
            HQAlive := True;
          end;
        end;
        TexPavementMask.Surfaces[0].UnLock;
        TexGrassMask.Surfaces[0].UnLock;
        UpdateMapCells(0, 0, 59, 59);
      end;
    finally
      fs.Free;
    end;
  end;
end;

procedure TBTGame.InitializeMenu;
  var s, x, y, w, h: Single;
begin
  s := Gfx.Params.Height / 500;
  SetLength(Menu.Items, 2);
  w := TexStart0.Width * s;
  h := TexStart1.Height * s;
  x := (Gfx.Params.Width - w) * 0.5;
  y := 200 * s;
  Menu.Items[0].R.Left := Round(x);
  Menu.Items[0].R.Top := Round(y);
  Menu.Items[0].R.Right := Round(x + w);
  Menu.Items[0].R.Bottom := Round(y + h);
  Menu.Items[0].Proc := GameStart;
  Menu.Items[0].Tex0 := TexStart0;
  Menu.Items[0].Tex1 := TexStart1;
  Menu.Items[1].R.Left := Round(x);
  Menu.Items[1].R.Top := Round(y + h);
  Menu.Items[1].R.Right := Round(x + w);
  Menu.Items[1].R.Bottom := Round(y + h * 2);
  Menu.Items[1].Proc := GameQuit;
  Menu.Items[1].Tex0 := TexQuit0;
  Menu.Items[1].Tex1 := TexQuit1;
end;

procedure TBTGame.RenderLoading;
  var Str: AnsiString;
begin
  Render.RenderStart;
  Render.Clear(False, True, True, $ffffffff);
  Str := 'Loading...';
  Font1.Print(Gfx.Params.Width - 10 - Font1.GetTextWidth(Str), Gfx.Params.Height - 10 - Font1.GetTextHeight(Str), $ff000000, Str);
  Render.RenderStop;
  Render.Present;
end;

procedure TBTGame.RenderMenu;
  var s, w, h: Single;
  var i: Integer;
  var Tex: TG2Texture2D;
begin
  RenderModes.FilteringLinear;
  s := Gfx.Params.Height / 500;
  Render2D.DrawRect(
    0, 0, Gfx.Params.Width, Gfx.Params.Height,
    Rect(
      0, 0,
      Round(Gfx.Params.Width * s),
      Round(Gfx.Params.Height * s)
    ), TexConcrete
  );
  w := TexTitle.Width * s;
  h := TexTitle.Height * s;
  Render2D.DrawRect(
    (Gfx.Params.Width - w) * 0.5, 20 * s,
    w, h, $ffffffff, TexTitle
  );
  for i := 0 to High(Menu.Items) do
  begin
    if PtInRect(Menu.Items[i].R, PlugInput.MousePos) then
    Tex := Menu.Items[i].Tex1
    else
    Tex := Menu.Items[i].Tex0;
    Render2D.DrawRect(
      Menu.Items[i].R.Left, Menu.Items[i].R.Top,
      Menu.Items[i].R.Right - Menu.Items[i].R.Left,
      Menu.Items[i].R.Bottom - Menu.Items[i].R.Top,
      $ffffffff, Tex
    );
  end;
  RenderModes.FilteringPoint;
end;

procedure TBTGame.RenderGame;
  var t, s, sa, ca: Single;
  var i: Integer;
  var Str: AnsiString;
begin
  RenderMap3D;
  if EndGame then
  begin
    t := (Game.AppTime - EndGameTime) / 2000;
    if t > 1 then t := 1;
    Render.TextureClear();
    Prim2D.DrawRect(0, 0, Gfx.Params.Width, Gfx.Params.Height, Round(t * $ff) shl 24);
  end;
  RenderModes.FilteringLinear;
  s := Gfx.Params.Height / 500;
  if Player.Lives > 0 then
  begin
    for i := 0 to Player.Lives - 1 do
    Render2D.DrawRect(10 * s + i * 20 * s, Gfx.Params.Height - 10 * s - 50 * s, 50 * s, 50 * s, $ffffffff, TexTankIcon);
  end;
  i := ActiveBotCount + BotsToSpawn;
  G2SinCos(G2PiTime, sa, ca);
  sa := sa * 2; ca := ca * 2;
  Str := 'Enemies Remaining: ' + IntToStr(i);
  Font1.Print(10 * s - ca, 10 * s - sa, s, s, $ff000000, Str);
  Font1.Print(10 * s + ca, 10 * s + sa, s, s, $ff000000, Str);
  Font1.Print(10 * s, 10 * s, s, s, $ffffffff, Str);
  RenderModes.FilteringPoint;
end;

procedure TBTGame.RenderEnd;
  var s, w, h: Single;
  var Tex: TG2Texture2D;
begin
  RenderModes.FilteringLinear;
  s := Gfx.Params.Height / 500;
  Render2D.DrawRect(
    0, 0, Gfx.Params.Width, Gfx.Params.Height,
    Rect(
      0, 0,
      Round(Gfx.Params.Width * s),
      Round(Gfx.Params.Height * s)
    ), TexConcrete
  );
  if Victory then
  Tex := TexVictory
  else
  Tex := TexDefeat;
  w := Tex.Width * s;
  h := Tex.Height * s;
  Render2D.DrawRect(
    (Gfx.Params.Width - w) * 0.5,
    (Gfx.Params.Height - h) * 0.5,
    w, h, $ffffffff, Tex
  );
end;

procedure TBTGame.RenderMap3D;
  var TexScale: Single;
  procedure PolyAddD0(const i, j: Integer);
  begin
    Render3D.AddPos((i + 1), 1, 60 - (j + 0));
    Render3D.AddPos((i + 0), 1, 60 - (j + 1));
    Render3D.AddPos((i + 1), 0, 60 - (j + 0));
    Render3D.AddPos((i + 0), 0, 60 - (j + 1));
    Render3D.AddTex((i + 1) / TexScale, (j + 0) / TexScale);
    Render3D.AddTex((i + 0) / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 1 - 0.4) / TexScale, (j + 0 - 0.4) / TexScale);
    Render3D.AddTex((i + 0 - 0.4) / TexScale, (j + 1 - 0.4) / TexScale);
    Render3D.AddCol($ffaaaaaa, 2); Render3D.AddCol($ff666666, 2);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.AddInd(2); Render3D.AddInd(1); Render3D.AddInd(3);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 4;
  end;
  procedure PolyAddD1(const i, j: Integer);
  begin
    Render3D.AddPos((i + 1), 1, 60 - (j + 0));
    Render3D.AddPos((i + 0), 1, 60 - (j + 0));
    Render3D.AddPos((i + 1), 0, 60 - (j + 0));
    Render3D.AddPos((i + 0), 0, 60 - (j + 0));
    Render3D.AddTex((i + 1) / TexScale, (j + 0) / TexScale);
    Render3D.AddTex((i + 0) / TexScale, (j + 0) / TexScale);
    Render3D.AddTex((i + 1) / TexScale, (j - 1) / TexScale);
    Render3D.AddTex((i + 0) / TexScale, (j - 1) / TexScale);
    Render3D.AddCol($ffaaaaaa, 2); Render3D.AddCol($ff666666, 2);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.AddInd(2); Render3D.AddInd(1); Render3D.AddInd(3);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 4;
  end;
  procedure PolyAddD2(const i, j: Integer);
  begin
    Render3D.AddPos((i + 1), 1, 60 - (j + 1));
    Render3D.AddPos((i + 0), 1, 60 - (j + 0));
    Render3D.AddPos((i + 1), 0, 60 - (j + 1));
    Render3D.AddPos((i + 0), 0, 60 - (j + 0));
    Render3D.AddTex((i + 1) / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 0) / TexScale, (j + 0) / TexScale);
    Render3D.AddTex((i + 1 + 0.4) / TexScale, (j + 1 - 0.4) / TexScale);
    Render3D.AddTex((i + 0 + 0.4) / TexScale, (j + 0 - 0.4) / TexScale);
    Render3D.AddCol($ffaaaaaa, 2); Render3D.AddCol($ff666666, 2);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.AddInd(2); Render3D.AddInd(1); Render3D.AddInd(3);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 4;
  end;
  procedure PolyAddD3(const i, j: Integer);
  begin
    Render3D.AddPos((i + 1), 1, 60 - (j + 1));
    Render3D.AddPos((i + 1), 1, 60 - (j + 0));
    Render3D.AddPos((i + 1), 0, 60 - (j + 1));
    Render3D.AddPos((i + 1), 0, 60 - (j + 0));
    Render3D.AddTex((i + 1) / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 1) / TexScale, (j + 0) / TexScale);
    Render3D.AddTex((i + 2) / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 2) / TexScale, (j + 0) / TexScale);
    Render3D.AddCol($ffaaaaaa, 2); Render3D.AddCol($ff666666, 2);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.AddInd(2); Render3D.AddInd(1); Render3D.AddInd(3);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 4;
  end;
  procedure PolyAddD4(const i, j: Integer);
  begin
    Render3D.AddPos((i + 0), 1, 60 - (j + 1));
    Render3D.AddPos((i + 1), 1, 60 - (j + 0));
    Render3D.AddPos((i + 0), 0, 60 - (j + 1));
    Render3D.AddPos((i + 1), 0, 60 - (j + 0));
    Render3D.AddTex((i + 0) / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 1) / TexScale, (j + 0) / TexScale);
    Render3D.AddTex((i + 0 + 0.4) / TexScale, (j + 1 + 0.4) / TexScale);
    Render3D.AddTex((i + 1 + 0.4) / TexScale, (j + 0 + 0.4) / TexScale);
    Render3D.AddCol($ffaaaaaa, 2); Render3D.AddCol($ff666666, 2);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.AddInd(2); Render3D.AddInd(1); Render3D.AddInd(3);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 4;
  end;
  procedure PolyAddD5(const i, j: Integer);
  begin
    Render3D.AddPos((i + 0), 1, 60 - (j + 1));
    Render3D.AddPos((i + 1), 1, 60 - (j + 1));
    Render3D.AddPos((i + 0), 0, 60 - (j + 1));
    Render3D.AddPos((i + 1), 0, 60 - (j + 1));
    Render3D.AddTex((i + 0) / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 1) / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 0) / TexScale, (j + 2) / TexScale);
    Render3D.AddTex((i + 1) / TexScale, (j + 2) / TexScale);
    Render3D.AddCol($ffaaaaaa, 2); Render3D.AddCol($ff666666, 2);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.AddInd(2); Render3D.AddInd(1); Render3D.AddInd(3);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 4;
  end;
  procedure PolyAddD6(const i, j: Integer);
  begin
    Render3D.AddPos((i + 0), 1, 60 - (j + 0));
    Render3D.AddPos((i + 1), 1, 60 - (j + 1));
    Render3D.AddPos((i + 0), 0, 60 - (j + 0));
    Render3D.AddPos((i + 1), 0, 60 - (j + 1));
    Render3D.AddTex((i + 0) / TexScale, (j + 0) / TexScale);
    Render3D.AddTex((i + 1) / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 0 - 0.4) / TexScale, (j + 0 + 0.4) / TexScale);
    Render3D.AddTex((i + 1 - 0.4) / TexScale, (j + 1 + 0.4) / TexScale);
    Render3D.AddCol($ffaaaaaa, 2); Render3D.AddCol($ff666666, 2);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.AddInd(2); Render3D.AddInd(1); Render3D.AddInd(3);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 4;
  end;
  procedure PolyAddD7(const i, j: Integer);
  begin
    Render3D.AddPos((i + 0), 1, 60 - (j + 0));
    Render3D.AddPos((i + 0), 1, 60 - (j + 1));
    Render3D.AddPos((i + 0), 0, 60 - (j + 0));
    Render3D.AddPos((i + 0), 0, 60 - (j + 1));
    Render3D.AddTex((i + 0) / TexScale, (j + 0) / TexScale);
    Render3D.AddTex((i + 0) / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i - 1) / TexScale, (j + 0) / TexScale);
    Render3D.AddTex((i - 1) / TexScale, (j + 1) / TexScale);
    Render3D.AddCol($ffaaaaaa, 2); Render3D.AddCol($ff666666, 2);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.AddInd(2); Render3D.AddInd(1); Render3D.AddInd(3);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 4;
  end;
  procedure PolyAddTD0(const i, j: Integer);
  begin
    Render3D.AddPos(i, 1, 60 - j);
    Render3D.AddPos(i + 1, 1, 60 - j);
    Render3D.AddPos(i, 1, 60 - (j + 1));
    Render3D.AddPos(i + 1, 1, 60 - (j + 1));
    Render3D.AddTex(i / TexScale, j / TexScale);
    Render3D.AddTex((i + 1) / TexScale, j / TexScale);
    Render3D.AddTex(i / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 1) / TexScale, (j + 1) / TexScale);
    Render3D.AddCol($ffffffff, 4);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.AddInd(2); Render3D.AddInd(1); Render3D.AddInd(3);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 4;
  end;
  procedure PolyAddTD1(const i, j: Integer);
  begin
    Render3D.AddPos(i + 1, 1, 60 - j);
    Render3D.AddPos(i, 1, 60 - (j + 1));
    Render3D.AddPos(i + 1, 1, 60 - (j + 1));
    Render3D.AddTex((i + 1) / TexScale, j / TexScale);
    Render3D.AddTex(i / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 1) / TexScale, (j + 1) / TexScale);
    Render3D.AddCol($ffffffff, 3);
    Render3D.AddInd(0); Render3D.AddInd(2); Render3D.AddInd(1);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 3;
  end;
  procedure PolyAddTD2(const i, j: Integer);
  begin
    Render3D.AddPos(i, 1, 60 - j);
    Render3D.AddPos(i, 1, 60 - (j + 1));
    Render3D.AddPos(i + 1, 1, 60 - (j + 1));
    Render3D.AddTex(i / TexScale, j / TexScale);
    Render3D.AddTex(i / TexScale, (j + 1) / TexScale);
    Render3D.AddTex((i + 1) / TexScale, (j + 1) / TexScale);
    Render3D.AddCol($ffffffff, 3);
    Render3D.AddInd(0); Render3D.AddInd(2); Render3D.AddInd(1);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 3;
  end;
  procedure PolyAddTD3(const i, j: Integer);
  begin
    Render3D.AddPos(i, 1, 60 - j);
    Render3D.AddPos(i + 1, 1, 60 - j);
    Render3D.AddPos(i, 1, 60 - (j + 1));
    Render3D.AddTex(i / TexScale, j / TexScale);
    Render3D.AddTex((i + 1) / TexScale, j / TexScale);
    Render3D.AddTex(i / TexScale, (j + 1) / TexScale);
    Render3D.AddCol($ffffffff, 3);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 3;
  end;
  procedure PolyAddTD4(const i, j: Integer);
  begin
    Render3D.AddPos(i, 1, 60 - j);
    Render3D.AddPos(i + 1, 1, 60 - j);
    Render3D.AddPos(i + 1, 1, 60 - (j + 1));
    Render3D.AddTex(i / TexScale, j / TexScale);
    Render3D.AddTex((i + 1) / TexScale, j / TexScale);
    Render3D.AddTex((i + 1) / TexScale, (j + 1) / TexScale);
    Render3D.AddCol($ffffffff, 3);
    Render3D.AddInd(0); Render3D.AddInd(1); Render3D.AddInd(2);
    Render3D.BaseVertexIndex := Render3D.BaseVertexIndex + 3;
  end;
  var i, j: Integer;
  var r: TG2Rect;
  var m: TG2Mat;
  var v4: TG2Vec4;
  var s: Single;
begin
  Cam.SetPerspective();
  if Player.Enabled then
  begin
    Cam.SetView(
      Tanks[Player.TankID].Pos.x, 25, 60 - Tanks[Player.TankID].Pos.y - 8,
      Tanks[Player.TankID].Pos.x, 1, 60 - Tanks[Player.TankID].Pos.y,
      0, 1, 0
    );
  end
  else
  Cam.SetView(30, 70, -20, 30, 0, 30, 0, 1, 0);
  Gfx.Transforms.P := Cam.Proj;
  Gfx.Transforms.V := Cam.View;
  Gfx.Transforms.ApplyP;
  Gfx.Transforms.ApplyV;
  RenderModes.FilteringAnisotropic(0);
  FxShaders.Technique := 'Water';
  FxShaders.BeginEffect(nil);
  FxShaders.SetMatrix('g_WVP', Gfx.Transforms.WVP);
  v4.SetValue(-G2TimeInterval(15791), -G2TimeInterval(11731), 0, 0);
  FxShaders.SetVector('TexShift0', v4);
  v4.SetValue(G2TimeInterval(15000), G2TimeInterval(15000), 0, 0);
  FxShaders.SetVector('TexShift1', v4);
  v4.SetValue(G2TimeInterval(13153), G2TimeInterval(14741), 0, 0);
  FxShaders.SetVector('TexShift2', v4);
  FxShaders.SetTexture('TexDiffuse', TexWater.Texture);
  FxShaders.SetTexture('TexNormals', TexWaterN.Texture);
  FxShaders.BeginPass(0);
  Render3D.DrawQuad2(
    G2Vec3(0, 0, 60), G2Vec3(60, 0, 60),
    G2Vec3(0, 0, 0), G2Vec3(60, 0, 0),
    $ffffffff,
    nil
  );
  FxShaders.EndPass;
  FxShaders.EndEffect;
  Gfx.RenderStates.SeparateAlphaBlendEnable := True;
  Gfx.TextureStageStates.AlphaOp[1] := D3DTOP_SELECTARG1;
  Gfx.TextureStageStates.AlphaArg1[1] := D3DTA_TEXTURE;
  Gfx.TextureStageStates.ColorOp[1] := D3DTOP_SELECTARG1;
  Gfx.TextureStageStates.ColorArg1[1] := D3DTA_CURRENT;
  RenderModes.FilteringAnisotropic(1);
  Render3D.DrawQuad2(
    G2Vec3(0, 0, 60), G2Vec3(60, 0, 60),
    G2Vec3(0, 0, 0), G2Vec3(60, 0, 0),
    $ffffffff,
    TexGrass,
    nil,
    TexGrassMask,
    nil
  );
  Render3D.DrawQuad2(
    G2Vec3(0, 0, 60), G2Vec3(60, 0, 60),
    G2Vec3(0, 0, 0), G2Vec3(60, 0, 0),
    $ffffffff,
    TexPavement,
    nil,
    TexPavementMask,
    nil
  );
  RenderModes.FilteringPoint(1);
  Render.TextureClear(1);
  Gfx.TextureStageStates.ColorOp[1] := D3DTOP_DISABLE;
  Gfx.TextureStageStates.AlphaOp[1] := D3DTOP_DISABLE;
  Gfx.RenderStates.ZEnable := True;
  if MapUpdate then
  begin
    TexScale := 5;
    Render3D.DrawBegin(ptTriangleList);
    for j := 0 to 59 do
    for i := 0 to 59 do
    begin
      if Map.Cells[i, j].Wall = wtBrick then
      begin
        if (CheckCell(i - 1, j) = 1)
        and (CheckCell(i, j - 1) = 1) then
        begin
          PolyAddTD1(i, j);
          PolyAddD0(i, j);
          PolyAddD3(i, j);
          PolyAddD5(i, j);
        end
        else if (CheckCell(i, j - 1) = 1)
        and (CheckCell(i + 1, j) = 1) then
        begin
          PolyAddTD2(i, j);
          PolyAddD2(i, j);
          PolyAddD5(i, j);
          PolyAddD7(i, j);
        end
        else if (CheckCell(i + 1, j) = 1)
        and (CheckCell(i, j + 1) = 1) then
        begin
          PolyAddTD3(i, j);
          PolyAddD4(i, j);
          PolyAddD7(i, j);
          PolyAddD1(i, j);
        end
        else if (CheckCell(i, j + 1) = 1)
        and (CheckCell(i - 1, j) = 1) then
        begin
          PolyAddTD4(i, j);
          PolyAddD6(i, j);
          PolyAddD1(i, j);
          PolyAddD3(i, j);
        end
        else
        begin
          PolyAddTD0(i, j);
          if CheckCell(i, j - 1) <> 2 then
          begin
            PolyAddD1(i, j);
          end;
          if (CheckCell(i, j + 1) <> 2) then
          begin
            PolyAddD5(i, j);
          end;
          if CheckCell(i - 1, j) <> 2 then
          begin
            PolyAddD7(i, j);
          end;
          if CheckCell(i + 1, j) <> 2 then
          begin
            PolyAddD3(i, j);
          end;
        end;
      end;
    end;
    MapVBCountBrick := Render3D.ExportVB(MapVBBrick);
    MapIBCountBrick := Render3D.ExportIB(MapIBBrick);
    Render3D.DrawDiscard;
    TexScale := 10;
    Render3D.DrawBegin(ptTriangleList);
    for j := 0 to 59 do
    for i := 0 to 59 do
    begin
      if Map.Cells[i, j].Wall = wtConcrete then
      begin
        PolyAddTD0(i, j);
        if CheckCell(i, j - 1) <> 2 then
        begin
          PolyAddD1(i, j);
        end;
        if (CheckCell(i, j + 1) <> 2) then
        begin
          PolyAddD5(i, j);
        end;
        if CheckCell(i - 1, j) <> 2 then
        begin
          PolyAddD7(i, j);
        end;
        if CheckCell(i + 1, j) <> 2 then
        begin
          PolyAddD3(i, j);
        end;
      end;
    end;
    MapVBCountConcrete := Render3D.ExportVB(MapVBConcrete);
    MapIBCountConcrete := Render3D.ExportIB(MapIBConcrete);
    Render3D.DrawDiscard;
    MapUpdate := False;
  end;
  Render.TextureSet(TexBrick);
  MapVBBrick.SetToDevice;
  MapIBBrick.SetToDevice;
  Gfx.Device.DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, MapVBCountBrick, 0, MapIBCountBrick div 3);
  Render.TextureSet(TexConcrete);
  MapVBConcrete.SetToDevice;
  MapIBConcrete.SetToDevice;
  Gfx.Device.DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, MapVBCountConcrete, 0, MapIBCountConcrete div 3);
  for i := 0 to TankCount - 1 do
  if Tanks[i].Enabled then
  RenderTank3D(Tanks[i]);
  if HQAlive then
  begin
    Render.TextureSet(TexHQ);
    m.SetScaling(0.1, 0.3, 0.1);
    m.Translate(HQPos.x, 0, 60 - HQPos.y);
    m := MeshHQ.Nodes[0].Transform * m;
    Gfx.Transforms.W[0] := m;
    Gfx.Transforms.ApplyW(0);
    MeshHQ.Geoms[0].Mesh.DrawSubset(0);
  end;
  Gfx.RenderStates.ZEnable := False;
  Gfx.SetRenderTargetTexture2D(TexDepthRT);
  Gfx.SetDepthStencilSurface(SurDepthDS);
  Gfx.SetViewPort(0, 0, TexDepthRT.Width, TexDepthRT.Height);
  Render.Clear(False, True, True, $ffffffff);
  Gfx.RenderStates.ZEnable := True;
  FxShaders.Technique := 'Depth';
  FxShaders.BeginEffect(nil);
  FxShaders.BeginPass(0);
  m.SetIdentity;
  Gfx.Transforms.W[0] := m;
  FxShaders.SetMatrix('g_WVP', Gfx.Transforms.WVP);
  FxShaders.CommitChanges;
  MapVBBrick.SetToDevice;
  MapIBBrick.SetToDevice;
  Gfx.Device.DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, MapVBCountBrick, 0, MapIBCountBrick div 3);
  MapVBConcrete.SetToDevice;
  MapIBConcrete.SetToDevice;
  Gfx.Device.DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, MapVBCountConcrete, 0, MapIBCountConcrete div 3);
  Prim3D.DrawQuad(
    G2Vec3(0, 0, 60), G2Vec3(60, 0, 60),
    G2Vec3(0, 0, 0), G2Vec3(60, 0, 0),
    $ffffffff
  );
  for i := 0 to TankCount - 1 do
  if Tanks[i].Enabled then
  begin
    Gfx.Transforms.W[0] := GetTank3DMat(Tanks[i], 0);
    FxShaders.SetMatrix('g_WVP', Gfx.Transforms.WVP);
    FxShaders.CommitChanges;
    MeshTankA.Geoms[1].Mesh.DrawSubset(0);
    Gfx.Transforms.W[0] := GetTank3DMat(Tanks[i], 1);
    FxShaders.SetMatrix('g_WVP', Gfx.Transforms.WVP);
    FxShaders.CommitChanges;
    MeshTankA.Geoms[0].Mesh.DrawSubset(0);
  end;
  if HQAlive then
  begin
    m.SetScaling(0.1, 0.3, 0.1);
    m.Translate(HQPos.x, 0, 60 - HQPos.y);
    m := MeshHQ.Nodes[0].Transform * m;
    Gfx.Transforms.W[0] := m;
    FxShaders.SetMatrix('g_WVP', Gfx.Transforms.WVP);
    FxShaders.CommitChanges;
    MeshHQ.Geoms[0].Mesh.DrawSubset(0);
  end;
  Gfx.RenderStates.ZEnable := False;
  FxShaders.EndPass;
  FxShaders.EndEffect;
  Gfx.SetRenderTargetDefault;
  Gfx.SetDepthStencilDefault;
  Gfx.RenderStates.AlphaTestEnable := True;
  Gfx.RenderStates.AlphaRef := $f;
  FxShaders.Technique := 'Outline';
  FxShaders.BeginEffect(nil);
  FxShaders.SetTexture('TexDiffuse', TexDepthRT.Texture);
  v4.SetValue(TexDepthRT.TexelSize, 0, 0);
  FxShaders.SetVector('PixelSize', v4);
  FxShaders.BeginPass(0);
  Render2D.DrawQuadRaw(
    G2Vec2(0, 0), G2Vec2(TexDepthRT.Width - 1, 0),
    G2Vec2(0, TexDepthRT.Height - 1), G2Vec2(TexDepthRT.Width - 1, TexDepthRT.Height - 1),
    $ffffffff, $ffffffff, $ffffffff, $ffffffff,
    //G2Vec2(0, 0), G2Vec2(1, 0), G2Vec2(0, 1), G2Vec2(1, 1)
    G2Vec2(TexDepthRT.DrawRect^.Left, TexDepthRT.DrawRect^.Top),
    G2Vec2(TexDepthRT.DrawRect^.Right, TexDepthRT.DrawRect^.Top),
    G2Vec2(TexDepthRT.DrawRect^.Left, TexDepthRT.DrawRect^.Bottom),
    G2Vec2(TexDepthRT.DrawRect^.Right, TexDepthRT.DrawRect^.Bottom)
  );
  FxShaders.EndPass;
  FxShaders.EndEffect;
  Gfx.RenderStates.AlphaTestEnable := False;
  Gfx.RenderStates.ZWriteEnable := False;
  GroupDust.Render();
  m.SetIdentity;
  Gfx.Transforms.W[0] := m;
  Gfx.Transforms.ApplyW(0);
  RenderModes.BlendModeAdd;
  for i := 0 to ShotCount - 1 do
  if Shots[i].Enabled then
  RenderShot3D(Shots[i]);
  GroupExpl.Render();
  m.SetIdentity;
  Gfx.Transforms.W[0] := m;
  Gfx.Transforms.ApplyW(0);
  RenderModes.BlendModeNormal;
  Gfx.RenderStates.ZWriteEnable := True;
  RenderModes.FilteringPoint(0);
end;

procedure TBTGame.RenderTank3D(const Tank: TTank);
  var s: Single;
  var m, m1, m2: TG2Mat;
  var GeomBodyID, GeomBarrelID, BarrelID, BodyID: Integer;
begin
  if not Tank.Enabled then Exit;
  BarrelID := Tank.MeshInst.Mesh.NodeIndex('TankBarrel');
  BodyID := Tank.MeshInst.Mesh.NodeIndex('TankBody');
  GeomBarrelID := Tank.MeshInst.Mesh.GeomIndex('TankBarrel');
  GeomBodyID := Tank.MeshInst.Mesh.GeomIndex('TankBody');
  s := 0.004;
  m.SetScaling(1, 1, 0.7);
  m1.SetScaling(s, s, s);
  m1.RotateY(Tank.Ang + HalfPi);
  m1.Translate(Tank.Pos.x, 0, 60 - Tank.Pos.y);
  Render.TextureSet(Tank.MeshInst.Materials[0].MapDiffuse);
  m2 := m * Tank.MeshInst.NodeTransforms[BodyID].TransformDef * m1;
  Gfx.Transforms.W[0] := m2;
  Gfx.Transforms.ApplyW(0);
  Tank.MeshInst.Mesh.Geoms[GeomBodyID].Mesh.DrawSubset(0);
  m1.SetScaling(s, s, s);
  m1.RotateY(Tank.GunAng + HalfPi);
  m1.Translate(Tank.Pos.x, 0, 60 - Tank.Pos.y);
  Render.TextureSet(Tank.MeshInst.Materials[1].MapDiffuse);
  m2 := m * Tank.MeshInst.NodeTransforms[BarrelID].TransformDef * m1;
  Gfx.Transforms.W[0] := m2;
  Gfx.Transforms.ApplyW(0);
  Tank.MeshInst.Mesh.Geoms[GeomBarrelID].Mesh.DrawSubset(0);
  m.SetIdentity;
  Gfx.Transforms.W[0] := m;
  Gfx.Transforms.ApplyW(0);
end;

procedure TBTGame.RenderShot3D(const Shot: TShot);
  var v0, v1: TG2Vec3;
  var v: TG2Vec2;
begin
  if not Shot.Enabled then Exit;
  v := Shot.Pos - Shot.Dir;
  v0.SetValue(v.x, 1, 60 - v.y);
  v1.SetValue(Shot.Pos.x, 1, 60 - Shot.Pos.y);
  Render3D.DrawAxisRect(
    v1, v0,
    0.4, 0.4,
    $ffffffff,
    TexBlast
  );
end;

procedure TBTGame.UpdateMenu;
begin

end;

procedure TBTGame.UpdateGame;
  var i: Integer;
  var d: Single;
  var r: TG2Ray;
  var v: TG2Vec3;
begin
  if PlugInput.MousePos.x < 0 then
  MapPos.x := MapPos.x + 1;
  if PlugInput.MousePos.y < 0 then
  MapPos.y := MapPos.y + 1;
  if PlugInput.MousePos.x > Gfx.Params.Width - 1 then
  MapPos.x := MapPos.x - 1;
  if PlugInput.MousePos.y > Gfx.Params.Height - 1 then
  MapPos.y := MapPos.y - 1;
  if Player.Enabled then
  begin
    Tanks[Player.TankID].MoveL := PlugInput.KeyDown[DIK_A];
    Tanks[Player.TankID].MoveU := PlugInput.KeyDown[DIK_W];
    Tanks[Player.TankID].MoveR := PlugInput.KeyDown[DIK_D];
    Tanks[Player.TankID].MoveD := PlugInput.KeyDown[DIK_S];
    r := Gfx.PointToRay(PlugInput.MousePos);
    if r.IntersectPlane(G2Plane(G2Vec3(0, 1, 0), G2Vec3(0, 1, 0)), False, d) then
    begin
      v := r.Origin + r.Dir * d;
      Tanks[Player.TankID].AimAt.SetValue(v.x, 60 - v.z);
    end;
    //Tanks[Player.TankID].AimAt := (PlugInput.MousePos - G2Vec2(x, y)) / CellSize;
  end;
  for i := 0 to BotCount - 1 do
  if Bots[i].Enabled then
  UpdateBot(Bots[i]);
  for i := 0 to TankCount - 1 do
  if Tanks[i].Enabled then
  UpdateTank(Tanks[i]);
  for i := 0 to ShotCount - 1 do
  if Shots[i].Enabled then
  UpdateShot(Shots[i]);
  for i := TimedExpl.Count - 1 downto 0 do
  if AppTime - PTimedExpl(TimedExpl[i])^.Delay > PTimedExpl(TimedExpl[i])^.Time then
  begin
    with TExplosion.Create(GroupExpl) do
    begin
      Pos := PTimedExpl(TimedExpl[i])^.Pos;
    end;
    Dispose(PTimedExpl(TimedExpl[i]));
    TimedExpl.Delete(i);
  end;
  GroupExpl.Update;
  GroupDust.Update;
  if not Player.Enabled
  and (Player.Lives > 0)
  and (Game.AppTime - Player.RespawnTime > 5000) then
  begin
    NewPlayer;
    Player.Lives := Player.Lives - 1;
  end;
  if (BotsToSpawn > 0) and (ActiveBotCount < 5) and (Game.AppTime - BotSpawnTime > 5000) then
  begin
    NewBot;
    BotsToSpawn := BotsToSpawn - 1;
  end;
  if not EndGame
  and (
    not HQAlive
    or (
      not Player.Enabled
      and (Player.Lives <= 0)
    )
  ) then
  begin
    EndGame := True;
    Victory := False;
    EndGameTime := Game.AppTime;
  end;
  if not EndGame
  and (ActiveBotCount = 0)
  and (BotsToSpawn = 0) then
  begin
    EndGame := True;
    EndGameTime := Game.AppTime;
  end;
  if EndGame
  and (Game.AppTime - EndGameTime > 2000) then
  begin
    ClearAll;
    if Victory and LevelExists(CurLevel + 1) then
    begin
      CurLevel := CurLevel + 1;
      BeginLevel(CurLevel);
    end
    else
    GameState := gsEnd;
  end;
end;

procedure TBTGame.UpdateEnd;
begin

end;

procedure TBTGame.UpdateTank(var Tank: TTank);
  var v, vl, vr: TG2Vec2;
  var cv: TG2Vec2;
  var s, c: Single;
  var xl, xh, yl, yh, i, j, hqx, hqy: Integer;
  var p, p2: TCSPoly;
  var bp: array[0..3] of TCSPoly;
  var m: TCSMat2;
  var r: TCSResp;
  const TurnSpeed = 0.04;
  const MoveSpeed = 0.1;
  const TurnSpeedGun = 0.06;
  const TankSize = 0.7;
begin
  if Tank.Health <= 0 then
  begin
    TankKill(Tank);
    Exit;
  end;
  hqx := Round(HQPos.x) - 2;
  hqy := Round(HQPos.y) - 2;
  if Tank.MoveL
  or Tank.MoveU
  or Tank.MoveR
  or Tank.MoveD then
  begin
    v.SetValue(0, 0);
    if Tank.MoveL then
    v.x := v.x - 1;
    if Tank.MoveU then
    v.y := v.y - 1;
    if Tank.MoveR then
    v.x := v.x + 1;
    if Tank.MoveD then
    v.y := v.y + 1;
    v.Normalize;
    G2SinCos(Tank.Ang, cv.y, cv.x);
    if v.Dot(cv) < Cos(TurnSpeed) then
    begin
      vr := cv.Perp;
      vl := -vr;
      if v.Dot(vr) > v.Dot(vl) then
      Tank.Ang := Tank.Ang + TurnSpeed
      else
      Tank.Ang := Tank.Ang - TurnSpeed;
    end
    else
    begin
      Tank.Ang := G2Vec2AngleOX(Tank.Pos, Tank.Pos + v);
    end;
    G2SinCos(Tank.Ang, cv.y, cv.x);
    Tank.Pos := Tank.Pos + (cv * MoveSpeed);
  end;
  v := (Tank.AimAt - Tank.Pos).Normalized;
  G2SinCos(Tank.GunAng, cv.y, cv.x);
  if v.Dot(cv) < Cos(TurnSpeedGun) then
  begin
    vr := cv.Perp;
    vl := -vr;
    if v.Dot(vr) > v.Dot(vl) then
    Tank.GunAng := Tank.GunAng + TurnSpeedGun
    else
    Tank.GunAng := Tank.GunAng - TurnSpeedGun;
  end
  else
  begin
    Tank.GunAng := G2Vec2AngleOX(Tank.Pos, Tank.Pos + v);
  end;
  SetLength(p.v, 4);
  p.v[0].SetValue(-TankSize, -TankSize);
  p.v[1].SetValue(TankSize, -TankSize);
  p.v[2].SetValue(TankSize, TankSize);
  p.v[3].SetValue(-TankSize, TankSize);
  m.SetRotation(Tank.Ang);
  for i := 0 to 3 do
  p.v[i] := CSVec2Mat2Mul(p.v[i], m) + TCSVec2(Tank.Pos);
  SetLength(p2.v, 4);
  for i := 0 to TankCount - 1 do
  if Tanks[i].Enabled
  and (Tanks[i].UID <> Tank.UID) then
  begin
    p2.v[0].SetValue(-TankSize, -TankSize);
    p2.v[1].SetValue(TankSize, -TankSize);
    p2.v[2].SetValue(TankSize, TankSize);
    p2.v[3].SetValue(-TankSize, TankSize);
    m.SetRotation(Tanks[i].Ang);
    for j := 0 to 3 do
    p2.v[j] := CSVec2Mat2Mul(p2.v[j], m) + TCSVec2(Tanks[i].Pos);
    if CSIntersectPoly(p, p2, @r) then
    begin
      Tank.Pos := Tank.Pos + G2Vec2(r.n.x * (r.d * 0.5), r.n.y * (r.d * 0.5));
      p.Translate(r.n * (r.d * 0.5));
      Tanks[i].Pos := Tanks[i].Pos - G2Vec2(r.n.x * (r.d * 0.5), r.n.y * (r.d * 0.5));
    end;
  end;
  xl := Max(0, Trunc(Tank.Pos.x) - 2);
  xh := Min(59, Trunc(Tank.Pos.x) + 3);
  yl := Max(0, Trunc(Tank.Pos.y) - 2);
  yh := Min(59, Trunc(Tank.Pos.y) + 3);
  for j := yl to yh do
  for i := xl to xh do
  if (
    (Map.Cells[i, j].Wall <> wtNone)
    or (Map.Cells[i, j].Floor = ftWater)
    or ((i >= hqx) and (i < hqx + 4) and (j >= hqy) and (j < hqy + 4))
  )
  and (Length(Map.Cells[i, j].Poly.v) > 2) then
  begin
    if CSIntersectPoly(p, Map.Cells[i, j].Poly, @r) then
    begin
      Tank.Pos := Tank.Pos + G2Vec2(r.n.x * r.d, r.n.y * r.d);
      p.Translate(r.n * r.d);
    end;
  end;
  SetLength(bp[0].v, 2);
  SetLength(bp[1].v, 2);
  SetLength(bp[2].v, 2);
  SetLength(bp[3].v, 2);
  bp[0].v[0].SetValue(0, 0);
  bp[0].v[1].SetValue(60, 0);
  bp[1].v[0].SetValue(0, 0);
  bp[1].v[1].SetValue(0, 60);
  bp[2].v[0].SetValue(60, 0);
  bp[2].v[1].SetValue(60, 60);
  bp[3].v[0].SetValue(0, 60);
  bp[3].v[1].SetValue(60, 60);
  for i := 0 to 3 do
  if CSIntersectPoly(p, bp[i], @r) then
  begin
    Tank.Pos := Tank.Pos + G2Vec2(r.n.x * r.d, r.n.y * r.d);
    p.Translate(r.n * r.d);
  end;
  if (Tank.Pos.x < 0) or (Tank.Pos.y < 0) or (Tank.Pos.x > 60) or (Tank.Pos.y > 60) then
  Tank.Health := 0;
end;

procedure TBTGame.UpdateShot(var Shot: TShot);
  var x, y, lx, ly, hx, hy, i, j: Integer;
begin
  if not Shot.Enabled then Exit;
  Shot.Pos := Shot.Pos + Shot.Dir * 0.5;
  x := Trunc(Shot.Pos.x);
  y := Trunc(Shot.Pos.y);
  for i := 0 to ShotCount - 1 do
  if (Shot.OwnerUID <> Shots[i].OwnerUID)
  and (Shots[i].Enabled) then
  begin
    if (Shots[i].Pos - Shot.Pos).Len < 0.5 then
    begin
      Shots[i].Enabled := False;
      Shot.Enabled := False;
      Break;
    end;
  end;
  if Shot.Enabled and HQAlive then
  begin
    if (Shot.Pos.x >= HQPos.x - 2)
    and (Shot.Pos.x <= HQPos.x + 2)
    and (Shot.Pos.y >= HQPos.y - 2)
    and (Shot.Pos.y <= HQPos.y + 2) then
    begin
      HQHealth := HQHealth - 1;
      if HQHealth <= 0 then
      begin
        HQAlive := False;
        for i := 0 to 15 do
        CreateTimedExpl((Round(HQPos.x) * 10 + Random(40) - 20) * 0.1, (Round(HQPos.y) * 10 + Random(40) - 20) * 0.1, Random(1001));
        CreateDust(HQPos.x, HQPos.y, 10, 2);
      end;
      Shot.Enabled := False;
    end;
  end;
  if Shot.Enabled then
  begin
    if (Map.Cells[x, y].Wall <> wtNone)
    and (not Map.Cells[x, y].Broken) then
    Shot.Enabled := False;
  end;
  if Shot.Enabled then
  begin
    for i := 0 to TankCount - 1 do
    if Tanks[i].Enabled
    and (Tanks[i].Align <> Shot.Align)
    and ((Tanks[i].Pos - Shot.Pos).Len < 1) then
    begin
      Shot.Enabled := False;
      Tanks[i].Health := Tanks[i].Health - 1;
    end;
    if (Shot.Pos.x < 0) or (Shot.Pos.y < 0)
    or (Shot.Pos.x > 60) or (Shot.Pos.y > 60) then
    Shot.Enabled := False;
  end;
  if not Shot.Enabled then
  begin
    lx := Max(0, x - 3);
    hx := Min(59, x + 3);
    ly := Max(0, y - 3);
    hy := Min(59, y + 3);
    for j := ly to hy do
    for i := lx to hx do
    if (G2Vec2(x, y) - G2Vec2(i, j)).Len <= 1 then
    if (Map.Cells[i, j].Wall = wtBrick)
    and (not Map.Cells[i, j].Broken) then
    begin
      Map.Cells[i, j].Broken := True;
      Map.Cells[i, j].Wall := wtNone;
      CreateDust(i + 0.5, j + 0.5, 3);
    end;
    UpdateMapCells(lx - 1, ly - 1, hx + 1, hy + 1);
    with TExplosion.Create(GroupExpl) do
    begin
      Pos.SetValue(Shot.Pos.x, 1, 60 - Shot.Pos.y);
    end;
  end;
end;

procedure TBTGame.UpdateBot(var Bot: TBot);
  var v: TG2Vec2;
  var sa, ca: Single;
begin
  if not Bot.Enabled or not Tanks[Bot.TankID].Enabled then Exit;
  G2SinCos(Tanks[Bot.TankID].Ang, v.y, v.x);
  if (Game.AppTime - Bot.ChangeDirTime > 3000)
  or (
    (Game.AppTime - Bot.ChangeDirTime > 1000)
    and
    (CheckCell(Trunc(Tanks[Bot.TankID].Pos.x + v.x), Trunc(Tanks[Bot.TankID].Pos.y + v.y)) = 2)
  ) then
  begin
    Bot.Dir := Point(Random(3) - 1, Random(3) - 1);
    Tanks[Bot.TankID].MoveL := Bot.Dir.x = -1;
    Tanks[Bot.TankID].MoveU := Bot.Dir.y = -1;
    Tanks[Bot.TankID].MoveR := Bot.Dir.x = 1;
    Tanks[Bot.TankID].MoveD := Bot.Dir.y = 1;
    Bot.ChangeDirTime := Game.AppTime;
  end;
  if Game.AppTime - Tanks[Bot.TankID].ShotTime > 1000 then
  TankShoot(Tanks[Bot.TankID]);
  Tanks[Bot.TankID].AimAt := Tanks[Bot.TankID].Pos + v;
  if (
    Player.Enabled
    and Trace(Tanks[Bot.TankID].Pos, Tanks[Player.TankID].Pos)
    and Gfx.Transforms.Frustum.PointInFrustum(
      G2Vec3(Tanks[Bot.TankID].Pos.x, 0, 60 - Tanks[Bot.TankID].Pos.y)
    )
  ) then
  begin
    if Game.AppTime - Bot.ReactionTime > 1000 then
    begin
      G2SinCos(G2PiTime, sa, ca);
      Tanks[Bot.TankID].AimAt.SetValue(
        Tanks[Player.TankID].Pos.x + ca * 2,
        Tanks[Player.TankID].Pos.y + sa * 2
      );
    end;
  end
  else
  Bot.ReactionTime := Game.AppTime;
end;

procedure TBTGame.UpdateMapCells(const MinX, MinY, MaxX, MaxY: Integer);
  var xl, xh, yl, yh: Integer;
  var i, j, d, n, rn, hqx, hqy: Integer;
  var ReDo: Boolean;
  const Dirs: array[0..3] of TPoint = ((X: -1; Y: 0), (X: 0; Y: -1), (X: 1; Y: 0), (X: 0; Y: 1));
begin
  xl := Max(0, MinX);
  xh := Min(59, MaxX);
  yl := Max(0, MinY);
  yh := Max(59, MaxY);
  hqx := Round(HQPos.x) - 2;
  hqy := Round(HQPos.y) - 2;
  rn := 0;
  ReDo := False;
  repeat
    Inc(rn);
    for j := yl to yh do
    for i := xl to xh do
    begin
      if Map.Cells[i, j].Wall = wtBrick then
      begin
        n := 0;
        for d := 0 to High(Dirs) do
        if CheckCell(i + Dirs[d].X, j + Dirs[d].Y) = 1 then
        Inc(n);
        if n > 2 then
        begin
          Map.Cells[i, j].Broken := True;
          Map.Cells[i, j].Wall := wtNone;
          CreateDust(i + 0.5, j + 0.5, 3);
          ReDo := True;
        end
        else if (CheckCell(i + 1, j) = 1)
        and (CheckCell(i, j + 1) = 1) then
        begin
          if Length(Map.Cells[i, j].Poly.v) <> 3 then
          SetLength(Map.Cells[i, j].Poly.v, 3);
          Map.Cells[i, j].Poly.v[0].SetValue(i, j);
          Map.Cells[i, j].Poly.v[1].SetValue(i + 1, j);
          Map.Cells[i, j].Poly.v[2].SetValue(i, j + 1);
        end
        else if (CheckCell(i, j + 1) = 1)
        and (CheckCell(i - 1, j) = 1) then
        begin
          if Length(Map.Cells[i, j].Poly.v) <> 3 then
          SetLength(Map.Cells[i, j].Poly.v, 3);
          Map.Cells[i, j].Poly.v[0].SetValue(i, j);
          Map.Cells[i, j].Poly.v[1].SetValue(i + 1, j);
          Map.Cells[i, j].Poly.v[2].SetValue(i + 1, j + 1);
        end
        else if (CheckCell(i - 1, j) = 1)
        and (CheckCell(i, j - 1) = 1) then
        begin
          if Length(Map.Cells[i, j].Poly.v) <> 3 then
          SetLength(Map.Cells[i, j].Poly.v, 3);
          Map.Cells[i, j].Poly.v[0].SetValue(i + 1, j);
          Map.Cells[i, j].Poly.v[1].SetValue(i + 1, j + 1);
          Map.Cells[i, j].Poly.v[2].SetValue(i, j + 1);
        end
        else if (CheckCell(i, j - 1) = 1)
        and (CheckCell(i + 1, j) = 1) then
        begin
          if Length(Map.Cells[i, j].Poly.v) <> 3 then
          SetLength(Map.Cells[i, j].Poly.v, 3);
          Map.Cells[i, j].Poly.v[0].SetValue(i, j);
          Map.Cells[i, j].Poly.v[1].SetValue(i + 1, j + 1);
          Map.Cells[i, j].Poly.v[2].SetValue(i, j + 1);
        end
        else
        begin
          if Length(Map.Cells[i, j].Poly.v) <> 4 then
          SetLength(Map.Cells[i, j].Poly.v, 4);
          Map.Cells[i, j].Poly.v[0].SetValue(i, j);
          Map.Cells[i, j].Poly.v[1].SetValue(i + 1, j);
          Map.Cells[i, j].Poly.v[2].SetValue(i + 1, j + 1);
          Map.Cells[i, j].Poly.v[3].SetValue(i, j + 1);
        end;
      end;
      if (Map.Cells[i, j].Wall = wtConcrete)
      or (Map.Cells[i, j].Floor = ftWater)
      or ((i >= hqx) and (i < hqx + 4) and (j >= hqy) and (j < hqy + 4)) then
      begin
        if Length(Map.Cells[i, j].Poly.v) <> 4 then
        SetLength(Map.Cells[i, j].Poly.v, 4);
        Map.Cells[i, j].Poly.v[0].SetValue(i, j);
        Map.Cells[i, j].Poly.v[1].SetValue(i + 1, j);
        Map.Cells[i, j].Poly.v[2].SetValue(i + 1, j + 1);
        Map.Cells[i, j].Poly.v[3].SetValue(i, j + 1);
      end;
    end;
  until (not ReDo) or (rn > 5);
  MapUpdate := True;
end;

procedure TBTGame.TankShoot(var Tank: TTank);
  var n, i: Integer;
begin
  if Game.AppTime - Tank.ShotTime < 500 then Exit;
  n := -1;
  for i := 0 to ShotCount - 1 do
  if not Shots[i].Enabled then
  begin
    n := i;
    Break;
  end;
  if n = -1 then
  begin
    if ShotCount >= Length(Shots) then
    SetLength(Shots, Length(Shots) + 16);
    n := ShotCount;
    Inc(ShotCount);
  end;
  G2SinCos(Tank.GunAng, Shots[n].Dir.y, Shots[n].Dir.x);
  Shots[n].Pos := Tank.Pos;
  Shots[n].OwnerUID := Tank.UID;
  Shots[n].Enabled := True;
  Shots[n].Align := Tank.Align;
  for i := 0 to 1 do
  UpdateShot(Shots[n]);
  Tank.ShotTime := Game.AppTime;
end;

procedure TBTGame.TankKill(var Tank: TTank);
  var i: Integer;
begin
  if Player.Enabled and (Tanks[Player.TankID].UID = Tank.UID) then
  begin
    Player.Enabled := False;
    Player.RespawnTime := Game.AppTime;
  end
  else
  begin
    for i := 0 to BotCount - 1 do
    if Bots[i].Enabled and (Tanks[Bots[i].TankID].UID = Tank.UID) then
    begin
      Bots[i].Enabled := False;
      Break;
    end;
  end;
  Tank.MeshInst.Free;
  Tank.Enabled := False;
  with TExplosion.Create(GroupExpl) do
  begin
    Pos.SetValue(Tank.Pos.x, 1, 60 - Tank.Pos.y);
  end;
  SndExplosion.Play;
end;

procedure TBTGame.ClearTanks;
  var i: Integer;
begin
  for i := 0 to TankCount - 1 do
  begin
    if Tanks[i].Enabled then
    Tanks[i].MeshInst.Free;
    Tanks[i].Enabled := False;
  end;
end;

procedure TBTGame.ClearAll;
  var i: Integer;
begin
  ClearTanks;
  for i := 0 to TimedExpl.Count - 1 do
  Dispose(PTimedExpl(TimedExpl[i]));
  TimedExpl.Clear;
  GroupDust.FreeItems;
  GroupExpl.FreeItems;
end;

function TBTGame.Trace(const PtStart, PtEnd: TG2Vec2): Boolean;
  var cp: TG2Vec2;
  var cpi: TPoint;
  var i, d: Integer;
begin
  d := Round((PtStart - PtEnd).Len * 3);
  for i := 0 to d do
  begin
    cp := G2LerpVec2(PtStart, PtEnd, i / d);
    cpi := Point(Trunc(cp.x), Trunc(cp.y));
    if CheckCell(cpi.x, cpi.y) = 2 then
    begin
      Result := False;
      Exit;
    end;
  end;
  Result := True;
end;

function TBTGame.NewTank(const PosX, PosY: Integer; const Align: Byte = 0): Integer;
var
  n, i: Integer;
begin
  n := -1;
  for i := 0 to TankCount - 1 do
  if not Tanks[i].Enabled then
  begin
    n := i;
    Break;
  end;
  if n = -1 then
  begin
    if TankCount >= Length(Tanks) then
    SetLength(Tanks, Length(Tanks) + 16);
    n := TankCount;
    Inc(TankCount);
  end;
  Result := n;
  Tanks[n].Enabled := True;
  Tanks[n].UID := GetUID;
  Tanks[n].Pos.SetValue(PosX + 0.5, PosY + 0.5);
  Tanks[n].Ang := -HalfPi;
  Tanks[n].GunAng := -HalfPi;
  Tanks[n].AimAt.SetValue(PosX + 0.5, PosY);
  Tanks[n].Health := 1;
  Tanks[n].Align := Align;
  Tanks[n].MeshInst := Game.MeshTankA.InstanceCreate;
  Tanks[n].MeshInst.Materials[0].MapDiffuse := TexTankA0;
  Tanks[n].MeshInst.Materials[1].MapDiffuse := TexTankA1;
  CreateDust(PosX + 0.5, PosY + 0.5, 32, 2);
end;

procedure TBTGame.NewPlayer;
  var p: TPoint;
begin
  if Player.Enabled then Exit;
  Player.Enabled := True;
  p := Map.PSpawn[Random(Length(Map.PSpawn))];
  Player.TankID := NewTank(p.X, p.Y, 0);
end;

procedure TBTGame.NewBot;
  var n, i: Integer;
  var p: TPoint;
begin
  n := -1;
  for i := 0 to BotCount - 1 do
  if not Bots[i].Enabled then
  begin
    n := i;
    Break;
  end;
  if n = -1 then
  begin
    if BotCount >= Length(Bots) then
    SetLength(Bots, Length(Bots) + 16);
    n := BotCount;
    Inc(BotCount);
  end;
  BotSpawnTime := Game.AppTime;
  Bots[n].Enabled := True;
  p := Map.ESpawn[Random(Length(Map.ESpawn))];
  Bots[n].TankID := NewTank(p.X, p.Y, 1);
  Bots[n].ChangeDirTime := Game.AppTime;
  Bots[n].ReactionTime := Game.AppTime;
  Bots[n].Dir := Point(0, 0);
  Tanks[Bots[n].TankID].MeshInst.Materials[0].MapDiffuse := TexTankB0;
  Tanks[Bots[n].TankID].MeshInst.Materials[1].MapDiffuse := TexTankB1;
end;

procedure TBTGame.CreateDust(const x, y: Single; const Count: Integer; const Spread: Single = 0.5);
  var i: Integer;
begin
  for i := 0 to Count - 1 do
  with TDust.Create(GroupDust) do
  begin
    Pos.x := x + ((Random(201) - 100) * 0.01) * Spread;
    Pos.y := 1;
    Pos.z := 60 - y + ((Random(201) - 100) * 0.01) * Spread;
  end;
end;

procedure TBTGame.CreateTimedExpl(const x, y: Single; const Delay: DWord);
  var e: PTimedExpl;
begin
  New(e);
  e^.Pos.SetValue(x, 1, 60 - y);
  e^.Time := Game.AppTime;
  e^.Delay := Delay;
  TimedExpl.Add(e);
end;

function TBTGame.ActiveBotCount: Integer;
  var i: Integer;
begin
  Result := 0;
  for i := 0 to BotCount - 1 do
  if Bots[i].Enabled and Tanks[Bots[i].TankID].Enabled then
  Inc(Result);
end;

function TBTGame.GetTank3DMat(const Tank: TTank; const Part: Integer = 0): TG2Mat;
  var s: Single;
  var m, m1, m2: TG2Mat;
  var BarrelID, BodyID: Integer;
begin
  BarrelID := Tank.MeshInst.Mesh.NodeIndex('TankBarrel');
  BodyID := Tank.MeshInst.Mesh.NodeIndex('TankBody');
  s := 0.004;
  m.SetScaling(1, 1, 0.7);
  case Part of
    0:
    begin
      m1.SetScaling(s, s, s);
      m1.RotateY(Tank.GunAng + HalfPi);
      m1.Translate(Tank.Pos.x, 0, 60 - Tank.Pos.y);
      m2 := m * Tank.MeshInst.NodeTransforms[BarrelID].TransformDef * m1;
    end;
    1:
    begin
      m1.SetScaling(s, s, s);
      m1.RotateY(Tank.Ang + HalfPi);
      m1.Translate(Tank.Pos.x, 0, 60 - Tank.Pos.y);
      m2 := m * Tank.MeshInst.NodeTransforms[BodyID].TransformDef * m1;
    end;
    else
    m2.SetIdentity;
  end;
  Result := m2;
end;

function TBTGame.CheckCell(const x, y: Integer): Byte;
begin
  if (x < 0) or (y < 0) or (x > 59) or (y > 59) then
  begin
    Result := 0;
    Exit;
  end;
  if (Map.Cells[x, y].Wall = wtNone) then
  begin
    if Map.Cells[x, y].Broken then
    Result := 1
    else
    Result := 0;
    Exit;
  end
  else
  Result := 2;
end;

function TBTGame.GetUID: DWord;
begin
  if CurUID >= High(DWord) then CurUID := 0;
  Result := CurUID;
  Inc(CurUID);
end;

function TBTGame.LevelExists(const LevelID: Integer): Boolean;
  var LevelStr: AnsiString;
begin
  if LevelID < 10 then
  LevelStr := 'map0' + IntToStr(LevelID) + '.map'
  else
  LevelStr := 'map' + IntToStr(LevelID) + '.map';
  Result := FileExists(LevelStr);
end;

procedure TBTGame.BeginLevel(const LevelID: Integer);
  var LevelStr: AnsiString;
begin
  ZeroMemory(@Map, SizeOf(Map));
  MapPos.SetValue(0, 0);
  CurUID := 0;
  SetLength(Shots, 16);
  ShotCount := 0;
  SetLength(Tanks, 16);
  TankCount := 0;
  SetLength(Bots, 16);
  BotCount := 0;
  MapUpdate := False;
  if LevelID < 10 then
  LevelStr := 'map0' + IntToStr(LevelID) + '.map'
  else
  LevelStr := 'map' + IntToStr(LevelID) + '.map';
  LoadMap(LevelStr);
  Player.Enabled := False;
  NewPlayer;
  EndGame := False;
  EndGameTime := Game.AppTime;
  Victory := True;
  BotsToSpawn := 30;
  Player.Lives := 3;
  GameState := gsGame;
end;

procedure TBTGame.EndLevel;
begin
  ClearAll;
end;

procedure TBTGame.GameStart;
begin
  CurLevel := 1;
  BeginLevel(CurLevel);
end;

procedure TBTGame.GameQuit;
begin
  Running := False;
end;
//TBBGame END

//TExplosion BEGIN
constructor TExplosion.Create(const Group: TG2Group);
begin
  inherited Create(Group);
  TimeCreate := Game.AppTime;
  TimeLive := 200;
  t := 0;
end;

destructor TExplosion.Destroy;
begin
  inherited Destroy;
end;

procedure TExplosion.Render(const Pass: Integer = 0);
  var m: TG2Mat;
  var v: Single;
  var a: Byte;
begin
  with Game do
  begin
    v := t * 3;
    a := Trunc(Sin(t * Pi) * 255);
    m.SetRotationZ(G2PiTime(300));
    m := m * MatB;
    m.Translate(Pos);
    Gfx.Transforms.W[0] := m;
    Gfx.Transforms.ApplyW(0);
    Render3D.DrawQuad(
      G2Vec3(-v, v, 0), G2Vec3(v, v, 0),
      G2Vec3(-v, -v, 0), G2Vec3(v, -v, 0),
      G2Color(a, a, a, 255),
      TexExpl
    );
    m.SetRotationZ(-G2PiTime(400));
    m := m * MatB;
    m.Translate(Pos);
    Gfx.Transforms.W[0] := m;
    Gfx.Transforms.ApplyW(0);
    Render3D.DrawQuad(
      G2Vec3(-v, v, 0), G2Vec3(v, v, 0),
      G2Vec3(-v, -v, 0), G2Vec3(v, -v, 0),
      G2Color(a, a, a, 255),
      TexExpl
    );
  end;
end;

procedure TExplosion.Update;
begin
  t := (Game.AppTime - TimeCreate) / TimeLive;
  if t >= 1 then Die;
end;
//TExplosion END

//TDust BEGIN
constructor TDust.Create(const Group: TG2Group);
begin
  inherited Create(Group);
  TimeCreate := Game.AppTime;
  TimeLive := 1000 + Random(500);
  t := 0;
  rd := Random(2) * 2 - 1;
end;

destructor TDust.Destroy;
begin
  inherited Destroy;
end;

procedure TDust.Render(const Pass: Integer = 0);
  var m: TG2Mat;
  var v: Single;
  var a: Byte;
begin
  with Game do
  begin
    v := 1 + t;
    a := Trunc((1 - t) * 255);
    m.SetRotationZ(rd * G2PiTime(2000));
    m := m * MatB;
    m.Translate(Pos);
    Gfx.Transforms.W[0] := m;
    Gfx.Transforms.ApplyW(0);
    Render3D.DrawQuad(
      G2Vec3(-v, v, 0), G2Vec3(v, v, 0),
      G2Vec3(-v, -v, 0), G2Vec3(v, -v, 0),
      G2Color(200, 200, 200, a),
      TexDust
    );
  end;
end;

procedure TDust.Update;
begin
  t := (Game.AppTime - TimeCreate) / TimeLive;
  if t >= 1 then Die;
end;
//TDust END

end.
