program Project69;

uses
  Windows,
  Messages,
  Classes,
  Types,
  SysUtils,
  SyncObjs,
  Math,
  G2Math,
  Gen2,
  DXTypes,
  Direct3D9,
  D3DX9,
  DirectInput,
  G2Particles3D,
  G2SceneGraph,
  UtilFuncs,
  G2Newton,
  Newton;

{$R *.res}

type
  TMob = class;

  TPhys = record
  public
    var World: TNewtonWorld;
    var SceneBody: TNewtonBody;
    procedure Init;
    procedure UnInit;
  end;

  TParticle = class (TG2Particle3DBillboard)
  strict protected
    var m_CreateTime: DWord;
    var m_LifeLength: DWord;
  public
    var t: Single;
    property LifeLength: DWord read m_LifeLength write m_LifeLength;
    constructor Create; override;
    procedure Update; override;
    procedure OnUpdate; virtual;
  end;

  TParticleAxis = class (TG2Particle3DBillboardAxis)
  strict protected
    var m_CreateTime: DWord;
    var m_LifeLength: DWord;
  public
    var t: Single;
    property LifeLength: DWord read m_LifeLength write m_LifeLength;
    constructor Create; override;
    procedure Update; override;
    procedure OnUpdate; virtual;
  end;

  TParticleFixed = class (TG2Particle3DFixed)
  strict protected
    var m_CreateTime: DWord;
    var m_LifeLength: DWord;
    var m_Scale: Single;
  public
    var t: Single;
    property LifeLength: DWord read m_LifeLength write m_LifeLength;
    property Scale: Single read m_Scale write m_Scale;
    constructor Create; override;
    procedure SetDir(const DirZ: TG2Vec3);
    procedure Update; override;
    procedure OnUpdate; virtual;
  end;

  TParticleBounce = class (TParticleAxis)
  public
    var Dir: TG2Vec3;
    var Len: Single;
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TParticleBloodSplash = class (TParticleAxis)
  public
    var Dir: TG2Vec3;
    var Len: Single;
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TParticleBloodSplat = class (TParticle)
  public
    var Vel: TG2Vec3;
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TParticleDust = class (TParticle)
  public
    var Vel: TG2Vec3;
    var VelAng: Single;
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TParticleBulletHole = class (TParticleFixed)
  public
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  PWaypoint = ^TWaypoint;
  TWaypoint = record
    var Pos: TG2Vec3;
    var Links: TG2QuickList;
    var FetchID: DWord;
    var Path: PWaypoint;
  end;

  TWaypointBlock = record
    var Waypoints: TG2QuickList;
  end;

  TWaypointMgr = record
    var Scene: TG2SceneGraph;
    var Pos: TG2Vec3;
    var Size: TG2Vec3;
    var TableSizeX: Integer;
    var TableSizeY: Integer;
    var TableSizeZ: Integer;
    var CellSizeX: Single;
    var CellSizeY: Single;
    var CellSizeZ: Single;
    var Threshold: Single;
    var StepSize: Single;
    var LinkSize: Single;
    var Table: array of array of array of TWaypointBlock;
    var TotalWaypoints: Integer;
    var WaypointList: TG2QuickSortList;
    var CurFetchID: DWord;
    procedure Initialize(
      const SceneGraph: TG2SceneGraph;
      const MinCellSize: Single = 15;
      const WaypointThreshold: Single = 2;
      const WaypointStepSize: Single = 3;
      const WaypointLinkSize: Single = 5
    );
    procedure Finalize;
    procedure GenerateWaypoints(const StartPos: TG2Vec3);
    procedure SaveWaypoints(const f: WideString);
    procedure LoadWaypoints(const f: WideString); overload;
    procedure LoadWaypoints(const Buffer: Pointer; const Size: DWord); overload;
    function AddWaypoint(const WaypointPos: TG2Vec3; var OutWaypoint: PWaypoint): Boolean;
    function GetWaypointBlock(const WaypointPos: TG2Vec3; var x, y, z: Integer): Boolean;
    function FindPath(const Start, Finish: TG2Vec3; const OutWaypoints: TG2QuickList): Boolean;
  end;

  TPhysUpdateThread = class (TThread)
  public
    var UpdatesNeeded: Integer;
    constructor Create;
    destructor Destroy; override;
    procedure Execute; override;
    procedure IncreaseUpdates;
  end;

  TLightDataPoint = packed record
  public
    var Pos: TG2Vec3;
    var Att: TG2Vec2;
    var Color: TG2Vec4;
  end;

  TLightDataDir = packed record
  public
    var Dir: TG2Vec3;
    var Color: TG2Vec4;
  end;

  TLightsData = record
  public
    var LightsCountPoint: Integer;
    var LightsCountDir: Integer;
    var LightsPoint: array[0..3] of TLightDataPoint;
    var LightsDir: array[0..3] of TLightDataDir;
  end;

  TLightTracingListItem = record
  public
    var Char: TG2SGChar;
    var Completed: Boolean;
    var TimeCompleted: DWord;
    var LightsData: TLightsData;
    var CharPos: TG2Vec3;
  end;
  PLightTracingListItem = ^TLightTracingListItem;

  TLightTracing = class (TThread)
  strict private
    var List: TG2QuickList;
    var LightsList: TG2QuickSortList;
  public
    class var TotalLightTraces: Integer;
    constructor Create;
    destructor Destroy; override;
    procedure Execute; override;
    function QueueAdd(const Char: TG2SGChar): Boolean;
    function TraceImmediate(const Pos: TG2Vec3): TLightsData;
  end;

  TFPSChar = class (TG2SGChar)
  public
    var TimeLightsGather: DWord;
    var LightsData: TLightsData;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    procedure Update; override;
    procedure GetLights;
  end;

  TPlayer = class (TFPSChar)
  public
    var TimeLastShot: DWord;
    var TimeShotLength: DWord;
    var ShotAng: Single;
    var TimeHit: DWord;
    var Health: Integer;
    var Armor: Integer;
    var Dead: Boolean;
    var TimeDead: DWord;
    var Accuracy: Single;
    var AccuracyMin: Single;
    var AccuracyMax: Single;
    var Ammo: Integer;
    var AmmoMax: Integer;
    var Clip: Integer;
    var Reloading: Boolean;
    var TimeReload: DWord;
    var LengthReload: DWord;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    procedure InitSetup;
    procedure Update; override;
    procedure Shoot;
    procedure Reload;
    procedure RenderGun;
    procedure Hit(const Damage: Integer);
  end;

  TExtraItem = class
  public
    var Pos: TG2Vec3;
    var MeshInst: TG2MeshInst;
    var LightsData: TLightsData;
    var PickUpTime: DWord;
    var TimeToActivate: DWord;
    var Active: Boolean;
    constructor Create(const NewPos: TG2Vec3); virtual;
    destructor Destroy; override;
    procedure Update;
    procedure PickUp;
    procedure OnPickUp; virtual; abstract;
  end;

  TArmor = class (TExtraItem)
  public
    constructor Create(const NewPos: TG2Vec3); override;
    destructor Destroy; override;
    procedure OnPickUp; override;
  end;

  TAmmo1 = class (TExtraItem)
  public
    constructor Create(const NewPos: TG2Vec3); override;
    destructor Destroy; override;
    procedure OnPickUp; override;
  end;

  TMenuButton = record
  public
    var Texture: TG2Texture2D;
    var Proc: TG2ProcObj;
  end;

  TMenu = record
  public
    var Buttons: array of TMenuButton;
    function McInButton(const Index: Integer): Boolean;
    procedure AddButton(const Texture: TG2Texture2D; const Proc: TG2ProcObj = nil);
    procedure Render;
    procedure Click;
  end;

  TGameState = (gsIntro, gsMenu, gsGame, gsGameOver);

  TMyApp = class (TG2App)
  public
    var GameState: TGameState;
    var MgrTextures: TG2TextureMgr;
    var MgrEffects: TG2EffectMgr;
    var MgrMeshes: TG2MeshMgr;
    var MgrSounds: TG2SoundMgr;
    var MgrMusic: TG2MusicMgr;
    var TexMuzzleFlash: TG2Texture2D;
    var TexBounce: TG2Texture2D;
    var TexBlood01: TG2Texture2D;
    var TexBlood02: TG2Texture2D;
    var TexDust01: TG2Texture2D;
    var TexBulletHole01: TG2Texture2D;
    var TexHUD: TG2Texture2D;
    var TexTitle: TG2Texture2D;
    var TexMenuBG: TG2Texture2D;
    var TexGameOver: TG2Texture2D;
    var TexLogoIGDC: TG2Texture2D;
    var SndClipOut: TG2SoundInst;
    var SndClipIn: TG2SoundInst;
    var SndPistolDryfire: TG2SoundInst;
    var SndShot1: TG2SoundInst;
    var SndHeadshot: TG2SoundInst;
    var SndDeath: TG2SoundInst;
    var SndAmmo: TG2SoundInst;
    var SndArmor: TG2SoundInst;
    var SndClawStrike: array[0..2] of TG2SoundInst;
    var Music: TG2Music;
    var Particles: TG2Particles3D;
    var Font1: TG2Font;
    var Font2: TG2Font;
    var Scene: TG2SceneGraph;
    var Query: PG2SGQuery;
    var Shaders: TG2Effect;
    var SkyBox: TG2SkyBox;
    var Player: TPlayer;
    var MeshPlayer: TG2Mesh;
    var MeshPlayerInst: TG2MeshInst;
    var MeshArmor: TG2Mesh;
    var MeshAmmo1: TG2Mesh;
    var Wireframe: Boolean;
    var Gravity: TG2Vec3;
    var Waypoints: TWaypointMgr;
    var ZombieSpawn: array of TG2Vec3;
    var ControlPoints: array of TG2Vec3;
    var ZombieTimeSpawn: DWord;
    var LightTracer: TLightTracing;
    var PhysUpdater: TPhysUpdateThread;
    var ExtraItems: TG2QuickList;
    var TimeToHardcore: DWord;
    var TimeStarted: DWord;
    var TimeToEndGame: Boolean;
    var Menu: TMenu;
    var GameLoaded: Boolean;
    var Score: Integer;
    var TimeIntroStart: DWord;
    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 RenderSceneDebug;
    procedure RenderScene;
    procedure RenderWaypoints;
    procedure RenderIntro;
    procedure RenderMenu;
    procedure RenderGame;
    procedure RenderGameOver;
    procedure UpdateIntro;
    procedure UpdateMenu;
    procedure UpdateGame;
    procedure UpdateGameOver;
    procedure UpdatePlayer;
    procedure GameBegin;
    procedure GameEnd;
    procedure LoadTextures(const MeshInst: TG2MeshInst);
    procedure RenderCrossair(const Radius: Single);
    procedure RenderLoading(const LoadMessage: AnsiString = 'Loading...');
    procedure CollideChars(const Char0, Char1: TG2SGChar);
    procedure SpawnZombie;
    procedure MenuBtnQuit;
  end;

  TMobState = (msIdle, msAttack);
  TMobTargetGeom = record
  public
    var GeomID: Integer;
    var DamageMod: Single;
  end;
  TMob = class (TFPSChar)
  public
    class var ZombieCount: Integer;
    class var TargetGeoms: array of TMobTargetGeom;
    class var RagdollCount: Integer;
    class var HardcoreMode: Boolean;
    var State: TMobState;
    var Health: Integer;
    var Anim: AnsiString;
    var AnimSpeed: DWord;
    var AnimSeed: DWord;
    var Scale: Single;
    var Speed: Single;
    var Dir: TG2Vec2;
    var NextWayPoint: TG2Vec3;
    var ResetWayPoint: Boolean;
    var WaypointList0: TG2QuickSortList;
    var WaypointList1: TG2QuickSortList;
    var WaypointReachTime: DWord;
    var Ragdoll: TG2MeshRagdoll;
    var Killed: Boolean;
    var TimeKilled: DWord;
    var TimeAI: DWord;
    var TimeBeenShot: DWord;
    var TimeLastHit: DWord;
    var Waypoints: TG2QuickList;
    var CurWaypoint: Integer;
    class procedure InitTargetGeoms;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; override;
    procedure Update; override;
    procedure Kill;
  end;

  TSceneObjectType = (otIrrelevant, otWater);
  TNodeExtraData = record
  public
    var ObjectType: TSceneObjectType;
    var TexMove: Boolean;
  end;
  PNodeExtraData = ^TNodeExtraData;

var
  App: TMyApp;
  Phys: TPhys;
  CS: TCriticalSection;

procedure ForceAndTorqueCallback(const Body: TNewtonBody; TimeStep: Single; ThreadIndex: Integer); cdecl;
  var Mass: Single;
  var Inertia: TG2Vec3;
  var Force: TG2Vec3;
begin
  NewtonBodyGetMassMatrix(Body, @Mass, @Inertia.x, @Inertia.y, @Inertia.z);
  Force := G2Vec3(0, -10, 0) * Mass;
  NewtonBodyAddForce(Body, @Force);
end;

//TPhys BEGIN
procedure TPhys.Init;
  var MinV, MaxV: TG2Vec3;
  var AABox: TG2AABox;
  var SceneCollision: TNewtonCollision;
  var i, j: Integer;
  var TmpFace: array[0..2] of TG2Vec3;
  var Geom: TG2SGGeom;
  var m: TG2Mat;
begin
  World := NewtonCreate();
  AABox := App.Scene.AABox;
  NewtonSetWorldSize(World, @AABox.MinV, @AABox.MaxV);
  SceneCollision := NewtonCreateTreeCollision(World, 0);
  NewtonTreeCollisionBeginBuild(SceneCollision);
  for i := 0 to App.Scene.Geoms.Count - 1 do
  if TG2SGGeom(App.Scene.Geoms[i]).Collide then
  begin
    Geom := TG2SGGeom(App.Scene.Geoms[i]);
    for j := 0 to Geom.FCount - 1 do
    begin
      TmpFace[0] := Geom.Collider.Vertices[Geom.Collider.Faces[j].Indices[0]];
      TmpFace[1] := Geom.Collider.Vertices[Geom.Collider.Faces[j].Indices[1]];
      TmpFace[2] := Geom.Collider.Vertices[Geom.Collider.Faces[j].Indices[2]];
      NewtonTreeCollisionAddFace(SceneCollision, 3, @TmpFace, SizeOf(TG2Vec3), 1);
    end;
  end;
  NewtonTreeCollisionEndBuild(SceneCollision, 1);
  SceneBody := NewtonCreateBody(World, SceneCollision);
  NewtonReleaseCollision(World, SceneCollision);
  m.SetIdentity;
  NewtonBodySetMatrix(SceneBody, @m);
end;

procedure TPhys.UnInit;
begin
  NewtonDestroyBody(World, SceneBody);
  NewtonDestroy(World);
end;
//TPhys END

//TParticle BEGIN
constructor TParticle.Create;
begin
  inherited Create;
  m_CreateTime := App.AppTime;
  m_LifeLength := 1000;
end;

procedure TParticle.Update;
begin
  inherited Update;
  t := (App.AppTime - m_CreateTime) / m_LifeLength;
  if t >= 1 then Die
  else
  OnUpdate;
end;

procedure TParticle.OnUpdate;
begin

end;
//TParticle END

//TParticleAxis BEGIN
constructor TParticleAxis.Create;
begin
  inherited Create;
  m_CreateTime := App.AppTime;
  m_LifeLength := 1000;
end;

procedure TParticleAxis.Update;
begin
  inherited Update;
  t := (App.AppTime - m_CreateTime) / m_LifeLength;
  if t >= 1 then Die
  else
  OnUpdate;
end;

procedure TParticleAxis.OnUpdate;
begin

end;
//TParticleAxis END

//TParticleFixed BEGIN
constructor TParticleFixed.Create;
begin
  inherited Create;
  m_CreateTime := App.AppTime;
  m_LifeLength := 1000;
  m_Scale := 1;
  DirX.SetValue(1, 0, 0);
  DirY.SetValue(0, 0, 1);
end;

procedure TParticleFixed.SetDir(const DirZ: TG2Vec3);
  var dx, dy: TG2Vec3;
begin
  dy.SetValue(0, 1, 0);
  if Abs(DirZ.Dot(dy)) > 0.95 then
  dy.SetValue(1, 0, 0);
  dx := DirZ.Cross(dy); dx.Normalize;
  dy := -DirZ.Cross(dx); dy.Normalize;
  DirX := dx * m_Scale;
  DirY := dy * m_Scale;
end;

procedure TParticleFixed.Update;
begin
  inherited Update;
  t := (App.AppTime - m_CreateTime) / m_LifeLength;
  if t >= 1 then Die
  else
  OnUpdate;
end;

procedure TParticleFixed.OnUpdate;
begin

end;
//TParticleFixed END

//TParticleBounce BEGIN
constructor TParticleBounce.Create;
begin
  inherited Create;
  Texture := App.TexBounce;
  Len := 3;
  LifeLength := 150;
  DistanceSorted := True;
end;

procedure TParticleBounce.OnUpdate;
  var ti: Single;
begin
  ti := 1 - t;
  Size := t;
  PosDst := PosSrc + Dir * (Len * t);
  Color := G2Color(255, 255, 255, Round(ti * 255));
end;
//TParticleBounce END

//TParticleBloodSplash BEGIN
constructor TParticleBloodSplash.Create;
begin
  inherited Create;
  Texture := App.TexBlood01;
  Len := 2;
  LifeLength := 300;
  BlendMode.SetNormal;
  DistanceSorted := True;
end;

procedure TParticleBloodSplash.OnUpdate;
  var ti: Single;
begin
  ti := 1 - t;
  Size := t;
  PosDst := PosSrc + Dir * (Len * t);
  Color := G2Color(255, 255, 255, Round(ti * 255));
end;
//TParticleBloodSplash END

//TParticleBloodSplat BEGIN
constructor TParticleBloodSplat.Create;
begin
  inherited Create;
  LifeLength := 200;
  Texture := App.TexBlood02;
  BlendMode.SetNormal;
  DistanceSorted := True;
  Vel.SetValue(0, 0, 0);// := G2RandomSpherePoint * 0.001;
  Rotation := G2Random2Pi;
end;

procedure TParticleBloodSplat.OnUpdate;
  var ti: Single;
begin
  ti := 1 - t;
  Vel.y := Vel.y - 0.0001;
  X := X + Vel.x;
  Y := Y + Vel.y;
  Z := Z + Vel.z;
  Scale := 1 + t * 2;
  Color := G2Color(255, 255, 255, Round(ti * 255));
end;
//TParticleBloodSplat END

//TParticleDust BEGIN
constructor TParticleDust.Create;
begin
  inherited Create;
  LifeLength := 500;
  Texture := App.TexDust01;
  BlendMode.SetNormal;
  DistanceSorted := True;
  Vel := G2RandomSpherePoint * 0.01;
  VelAng := (Random(101) - 50) * 0.0001;
  Rotation := G2Random2Pi;
end;

procedure TParticleDust.OnUpdate;
  var ti: Single;
begin
  ti := 1 - t;
  Rotation := Rotation + VelAng;
  Vel.y := Vel.y - 0.0002;
  Vel := Vel * 0.999;
  X := X + Vel.x;
  Y := Y + Vel.y;
  Z := Z + Vel.z;
  Scale := 0.5 + t;
  Color := G2Color(255, 255, 255, Round(ti * 255));
end;
//TParticleDust END

//TParticleBulletHole BEGIN
constructor TParticleBulletHole.Create;
begin
  inherited Create;
  LifeLength := 20000;
  Texture := App.TexBulletHole01;
  BlendMode.SetNormal;
  DistanceSorted := True;
  Scale := 0.4;
end;

procedure TParticleBulletHole.OnUpdate;
  var s: Single;
  var b: Byte;
begin
  if t > 0.9 then
  begin
    s := (t - 0.9) / 0.1;
    b := Round((1 - s) * 255);
    Color := G2Color(255, 255, 255, b);
  end;
end;
//TParticleBulletHole END

//TWaypointMgr BEGIN
procedure TWaypointMgr.Initialize(
  const SceneGraph: TG2SceneGraph;
  const MinCellSize: Single = 15;
  const WaypointThreshold: Single = 2;
  const WaypointStepSize: Single = 3;
  const WaypointLinkSize: Single = 5
);
  var AABox: TG2AABox;
  var x, y, z: Integer;
begin
  Scene := SceneGraph;
  AABox := Scene.AABox;
  Pos := AABox.MinV;
  Size := AABox.MaxV - AABox.MinV;
  TableSizeX := Trunc(Size.x / MinCellSize);
  TableSizeY := Trunc(Size.y / MinCellSize);
  TableSizeZ := Trunc(Size.z / MinCellSize);
  CellSizeX := Size.x / TableSizeX;
  CellSizeY := Size.y / TableSizeY;
  CellSizeZ := Size.z / TableSizeZ;
  SetLength(Table, TableSizeX, TableSizeY, TableSizeZ);
  for x := 0 to TableSizeX - 1 do
  for y := 0 to TableSizeY - 1 do
  for z := 0 to TableSizeZ - 1 do
  Table[x, y, z].Waypoints.Clear;
  Threshold := WaypointThreshold;
  StepSize := WaypointStepSize;
  LinkSize := WaypointLinkSize;
  CurFetchID := 0;
end;

procedure TWaypointMgr.Finalize;
  var x, y, z, i: Integer;
  var Waypoint: PWaypoint;
begin
  for x := 0 to TableSizeX - 1 do
  for y := 0 to TableSizeY - 1 do
  for z := 0 to TableSizeZ - 1 do
  begin
    for i := 0 to Table[x, y, z].Waypoints.Count - 1 do
    begin
      Waypoint := PWaypoint(Table[x, y, z].Waypoints[i]);
      Dispose(Waypoint);
    end;
    Table[x, y, z].Waypoints.Clear;
  end;
end;

procedure TWaypointMgr.GenerateWaypoints(const StartPos: TG2Vec3);
  const Dirs: array[0..7] of TG2Vec3 = (
    (x: -1; y: 0; z: 0), (x: 0; y: 0; z: 1), (x: 1; y: 0; z: 0), (x: 0; y: 0; z: -1),
    (x: -1; y: 0; z: -1), (x: -1; y: 0; z: 1), (x: 1; y: 0; z: 1), (x: 1; y: 0; z: -1)
  );
  function GenerateWaypoint(const WPos: TG2Vec3): PWaypoint;
    var i: Integer;
    var NewPos: TG2Vec3;
    var r: TG2Ray;
    var GeomID: Integer;
    var FaceID: Word;
    var U, V, D: Single;
  begin
    if AddWaypoint(WPos, Result) then
    begin
      Inc(TotalWaypoints);
      //if TotalWaypoints < 500 then
      for i := 0 to High(Dirs) do
      begin
        NewPos := Result^.Pos + Dirs[i] * StepSize;
        r.SetValue(Result^.Pos, Dirs[i]);
        if Scene.CollideRay(r, StepSize * 1.1, GeomID, FaceID, U, V, D) then
        begin
          if D > (NewPos - Result^.Pos).Len then
          GenerateWaypoint(NewPos);
        end
        else
        GenerateWaypoint(NewPos);
      end;
    end;
  end;
begin
  TotalWaypoints := 0;
  G2WriteLogTimed('Generating waypoints', 'App');
  GenerateWaypoint(StartPos);
  SaveWaypoints('Level01.wpt');
  G2WriteLogTimed('Total waypoints: ' + IntToStr(TotalWaypoints));
end;

procedure TWaypointMgr.SaveWaypoints(const f: WideString);
  var Waypoints: array of record
  public
    var Waypoint: PWaypoint;
    var Index: Integer;
    var Pos: TG2Vec3;
    var Links: array of Integer;
  end;
  var x, y, z, i, j, n: Integer;
  var fs: TG2FileRW;
begin
  SetLength(Waypoints, TotalWaypoints);
  j := 0;
  for x := 0 to TableSizeX - 1 do
  for y := 0 to TableSizeY - 1 do
  for z := 0 to TableSizeZ - 1 do
  for i := 0 to Table[x, y, z].Waypoints.Count - 1 do
  begin
    Waypoints[j].Waypoint := PWaypoint(Table[x, y, z].Waypoints[i]);
    Waypoints[j].Index := j;
    Waypoints[j].Pos := PWaypoint(Table[x, y, z].Waypoints[i]).Pos;
    SetLength(Waypoints[j].Links, PWaypoint(Table[x, y, z].Waypoints[i]).Links.Count);
    Inc(j);
  end;
  for i := 0 to High(Waypoints) do
  begin
    for j := 0 to Waypoints[i].Waypoint^.Links.Count - 1 do
    for n := 0 to High(Waypoints) do
    if Waypoints[n].Waypoint = PWaypoint(Waypoints[i].Waypoint^.Links[j]) then
    Waypoints[i].Links[j] := n;
  end;
  fs := TG2FileRW.Create;
  fs.OpenWrite(f);
  try
    fs.WriteSInt4(TotalWaypoints);
    for i := 0 to High(Waypoints) do
    begin
      fs.WriteVec3(Waypoints[i].Pos);
      fs.WriteSInt4(Length(Waypoints[i].Links));
      for j := 0 to High(Waypoints[i].Links) do
      fs.WriteSInt4(Waypoints[i].Links[j]);
    end;
  finally
    fs.Free;
  end;
end;

procedure TWaypointMgr.LoadWaypoints(const f: WideString);
  var fs: TG2FileRW;
  var Waypoints: array of record
    var Pos: TG2Vec3;
    var Links: array of Integer;
    var Waypoint: PWaypoint;
  end;
  var i, j: Integer;
  var x, y, z: Integer;
begin
  fs := TG2FileRW.Create;
  fs.OpenRead(f);
  try
    SetLength(Waypoints, fs.ReadSInt4);
    for i := 0 to High(Waypoints) do
    begin
      Waypoints[i].Pos := fs.ReadVec3;
      SetLength(Waypoints[i].Links, fs.ReadSInt4);
      for j := 0 to High(Waypoints[i].Links) do
      Waypoints[i].Links[j] := fs.ReadSInt4;
      if GetWaypointBlock(Waypoints[i].Pos, x, y, z) then
      begin
        New(Waypoints[i].Waypoint);
        Waypoints[i].Waypoint^.Links.Clear;
        Waypoints[i].Waypoint^.Pos := Waypoints[i].Pos;
        Waypoints[i].Waypoint^.FetchID := 0;
        Waypoints[i].Waypoint^.Path := nil;
        Table[x, y, z].Waypoints.Add(Waypoints[i].Waypoint);
      end;
    end;
    for i := 0 to High(Waypoints) do
    begin
      for j := 0 to High(Waypoints[i].Links) do
      Waypoints[i].Waypoint^.Links.Add(Waypoints[Waypoints[i].Links[j]].Waypoint);
    end;
  finally
    fs.Free;
  end;
end;

procedure TWaypointMgr.LoadWaypoints(const Buffer: Pointer; const Size: DWord);
  var fs: TG2FileRW;
  var Waypoints: array of record
    var Pos: TG2Vec3;
    var Links: array of Integer;
    var Waypoint: PWaypoint;
  end;
  var i, j, n: Integer;
  var x, y, z: Integer;
  var BufferPos: Pointer;
  var v: TG2Vec3;
  procedure CopyData(var Dst; const Count: DWord);
  begin
    Move(BufferPos^, Dst, Count);
    BufferPos := Pointer(DWord(BufferPos) + Count);
  end;
begin
  BufferPos := Buffer;
  CopyData(n, 4);
  SetLength(Waypoints, n);
  for i := 0 to High(Waypoints) do
  begin
    CopyData(v, SizeOf(TG2Vec3));
    Waypoints[i].Pos := v;
    CopyData(n, 4);
    SetLength(Waypoints[i].Links, n);
    for j := 0 to High(Waypoints[i].Links) do
    begin
      CopyData(n, 4);
      Waypoints[i].Links[j] := n;
    end;
    if GetWaypointBlock(Waypoints[i].Pos, x, y, z) then
    begin
      New(Waypoints[i].Waypoint);
      Waypoints[i].Waypoint^.Links.Clear;
      Waypoints[i].Waypoint^.Pos := Waypoints[i].Pos;
      Waypoints[i].Waypoint^.FetchID := 0;
      Waypoints[i].Waypoint^.Path := nil;
      Table[x, y, z].Waypoints.Add(Waypoints[i].Waypoint);
    end;
  end;
  for i := 0 to High(Waypoints) do
  begin
    for j := 0 to High(Waypoints[i].Links) do
    Waypoints[i].Waypoint^.Links.Add(Waypoints[Waypoints[i].Links[j]].Waypoint);
  end;
end;

function TWaypointMgr.AddWaypoint(const WaypointPos: TG2Vec3; var OutWaypoint: PWaypoint): Boolean;
  var x, y, z, i, GeomID, xmin, xmax, ymin, ymax, zmin, zmax: Integer;
  var FaceID: Word;
  var U, V, D, L: Single;
  var WPos: TG2Vec3;
  var r: TG2Ray;
  var s: TG2Sphere;
begin
  Result := False;
  OutWaypoint := nil;
  s.SetValue(WaypointPos, 1.5);
  Scene.CollideSphere(s);
  WPos := s.C;
  if Scene.CollideRay(
    G2Ray(WPos, G2Vec3(0, -1, 0)),
    GeomID, FaceID, U, V, D
  ) then
  begin
    if TG2SGGeom(Scene.Geoms[GeomID]).Collider.Faces[FaceID].Plane.N.Dot(G2Vec3(0, 1, 0)) < 0.7 then
    Exit;
    WPos := WPos + G2Vec3(0, 1.5 - D, 0)
  end
  else
  Exit;
  if not GetWaypointBlock(WPos, x, y, z) then
  Exit;
  for i := 0 to Table[x, y, z].Waypoints.Count - 1 do
  if (PWaypoint(Table[x, y, z].Waypoints[i])^.Pos - WPos).Len <= Threshold then
  begin
    OutWaypoint := PWaypoint(Table[x, y, z].Waypoints[i]);
    Exit;
  end;
  New(OutWaypoint);
  OutWaypoint^.Pos := WPos;
  OutWaypoint^.Links.Clear;
  OutWaypoint^.FetchID := 0;
  Table[x, y, z].Waypoints.Add(OutWaypoint);
  Result := True;
  xmin := x - 1; if xmin < 0 then xmin := 0;
  xmax := x + 1; if xmax > TableSizeX - 1 then xmax := TableSizeX - 1;
  ymin := y - 1; if ymin < 0 then ymin := 0;
  ymax := y + 1; if ymax > TableSizeY - 1 then ymax := TableSizeY - 1;
  zmin := z - 1; if zmin < 0 then zmin := 0;
  zmax := z + 1; if zmax > TableSizeZ - 1 then zmax := TableSizeZ - 1;
  for x := xmin to xmax do
  for y := ymin to ymax do
  for z := zmin to zmax do
  for i := 0 to Table[x, y, z].Waypoints.Count - 1 do
  if (Table[x, y, z].Waypoints[i] <> OutWaypoint) then
  begin
    r.SetValue(OutWaypoint^.Pos, PWaypoint(Table[x, y, z].Waypoints[i])^.Pos - OutWaypoint^.Pos);
    L := r.Dir.Len; r.Dir.Normalize;
    if (L < LinkSize)
    and (PWaypoint(Table[x, y, z].Waypoints[i])^.Pos.y - OutWaypoint^.Pos.y < 2) then
    begin
      if Scene.CollideRay(r, GeomID, FaceID, U, V, D) then
      begin
        if D > L then
        begin
          OutWaypoint^.Links.Add(PWaypoint(Table[x, y, z].Waypoints[i]));
          PWaypoint(Table[x, y, z].Waypoints[i])^.Links.Add(OutWaypoint);
        end;
      end
      else
      begin
        OutWaypoint^.Links.Add(PWaypoint(Table[x, y, z].Waypoints[i]));
        PWaypoint(Table[x, y, z].Waypoints[i])^.Links.Add(OutWaypoint);
      end;
    end;
  end;
end;

function TWaypointMgr.GetWaypointBlock(const WaypointPos: TG2Vec3; var x, y, z: Integer): Boolean;
begin
  if (WaypointPos.x >= Pos.x + Size.x)
  or (WaypointPos.y >= Pos.y + Size.y)
  or (WaypointPos.z >= Pos.z + Size.z)
  or (WaypointPos.x <= Pos.x)
  or (WaypointPos.y <= Pos.y)
  or (WaypointPos.z <= Pos.z) then
  begin
    Result := False;
    Exit;
  end;
  x := Trunc((WaypointPos.x - Pos.x) / Size.x * TableSizeX);
  y := Trunc((WaypointPos.y - Pos.y) / Size.y * TableSizeY);
  z := Trunc((WaypointPos.z - Pos.z) / Size.z * TableSizeZ);
  Result := True;
end;

function TWaypointMgr.FindPath(const Start, Finish: TG2Vec3; const OutWaypoints: TG2QuickList): Boolean;
  var x, y, z, i: Integer;
  var d: Single;
  var WptStart, WptFinish, WptCur: PWaypoint;
  var PathFound: Boolean;
begin
  Result := False;
  if not GetWaypointBlock(Start, x, y, z) then Exit;
  if Table[x, y, z].Waypoints.Count > 0 then
  WptStart := PWaypoint(Table[x, y, z].Waypoints[0])
  else
  Exit;
  if not GetWaypointBlock(Finish, x, y, z) then Exit;
  if Table[x, y, z].Waypoints.Count > 0 then
  WptFinish := PWaypoint(Table[x, y, z].Waypoints[0])
  else
  Exit;
  if CurFetchID >= High(DWord) - 1 then
  begin
    CurFetchID := 0;
    for x := 0 to TableSizeX - 1 do
    for y := 0 to TableSizeY - 1 do
    for z := 0 to TableSizeZ - 1 do
    for i := 0 to Table[x, y, z].Waypoints.Count - 1 do
    PWaypoint(Table[x, y, z].Waypoints[i])^.FetchID := 0;
  end;
  CurFetchID := CurFetchID + 1;
  WptFinish^.FetchID := CurFetchID;
  WptFinish^.Path := nil;
  WaypointList.Clear;
  WaypointList.Add(WptFinish);
  PathFound := False;
  while not PathFound and (WaypointList.Count > 0) do
  begin
    WptCur := WaypointList[0];
    WaypointList.Delete(0);
    if WptCur = WptStart then
    PathFound := True
    else
    for i := 0 to WptCur^.Links.Count - 1 do
    if PWaypoint(WptCur^.Links[i])^.FetchID < CurFetchID then
    begin
      PWaypoint(WptCur^.Links[i])^.FetchID := CurFetchID;
      PWaypoint(WptCur^.Links[i])^.Path := WptCur;
      d := (PWaypoint(WptCur^.Links[i])^.Pos - WptStart^.Pos).LenSq;
      WaypointList.Add(PWaypoint(WptCur^.Links[i]), d);
    end;
  end;
  if PathFound then
  begin
    Result := True;
    WptCur := WptStart;
    OutWaypoints.Clear;
    while WptCur <> WptFinish do
    begin
      WptCur := WptCur.Path;
      OutWaypoints.Add(WptCur);
    end;
  end;
end;
//TWaypointMgr END

//TPhysUpdateThread BEGIN
constructor TPhysUpdateThread.Create;
begin
  inherited Create(True);
  Priority := tpLowest;
  UpdatesNeeded := 0;
end;

destructor TPhysUpdateThread.Destroy;
begin
  inherited Destroy;
end;

procedure TPhysUpdateThread.Execute;
  var UpdatesToDo, i: Integer;
begin
  while not Terminated do
  begin
    if UpdatesNeeded > 0 then
    begin
      CS.Enter;
      try
        UpdatesToDo := UpdatesNeeded;
        UpdatesNeeded := 0;
      finally
        CS.Leave;
      end;
      for i := 0 to UpdatesToDo - 1 do
      NewtonUpdate(Phys.World, 1);
    end;
  end;
end;

procedure TPhysUpdateThread.IncreaseUpdates;
begin
  Inc(UpdatesNeeded);
end;
//TPhysUpdateThread NED

//TLightTracing BEGIN
constructor TLightTracing.Create;
begin
  inherited Create(True);
  Priority := tpLowest;
  List.Clear;
  TotalLightTraces := 0;
end;

destructor TLightTracing.Destroy;
  var i: Integer;
begin
  for i := 0 to List.Count - 1 do
  Dispose(PLightTracingListItem(List[i]));
  List.Clear;
  inherited Destroy;
end;

procedure TLightTracing.Execute;
  var i: Integer;
  var ListItem: PLightTracingListItem;
  var LightDir: TG2Vec3;
  var Dist, U, V, D, s: Single;
  var AddLight: Boolean;
  var GeomID: Integer;
  var FaceID: Word;
  var AABox: TG2AABox;
begin
  while not Terminated do
  begin
    if List.Count > 0 then
    begin
      for i := List.Count - 1 downto 0 do
      if PLightTracingListItem(List[i])^.Completed
      and (App.AppTime - PLightTracingListItem(List[i])^.TimeCompleted > 10000) then
      begin
        Dispose(PLightTracingListItem(List[i]));
        List.Delete(i);
      end;
      ListItem := nil;
      CS.Enter;
      try
        for i := 0 to List.Count - 1 do
        if not PLightTracingListItem(List[i])^.Completed then
        begin
          ListItem := PLightTracingListItem(List[i]);
          Break;
        end;
      finally
        CS.Leave;
      end;
      if ListItem <> nil then
      begin
        LightsList.Clear;
        for i := 0 to App.Scene.Lights.Count - 1 do
        begin
          case TG2SGLight(App.Scene.Lights[i]).LightType of
            ltPoint:
            begin
              LightDir := TG2SGLightPoint(App.Scene.Lights[i]).Transform.GetTranslation - ListItem^.CharPos;
              Dist := LightDir.Len;
              if Dist < TG2SGLightPoint(App.Scene.Lights[i]).AttEnd * 1.2 then
              begin
                AddLight := True;
                if App.Scene.CollideRay(G2Ray(ListItem^.CharPos, LightDir.Normalized), GeomID, FaceID, U, V, D) then
                if D < Dist then
                AddLight := False;
                if AddLight then
                begin
                  AABox := TG2SGLightPoint(App.Scene.Lights[i]).AABox;
                  s := (AABox.MaxV - AABox.MinV).Len;
                  LightsList.Add(TG2SGLightPoint(App.Scene.Lights[i]), Dist - s);
                end;
              end;
            end;
            ltDir:
            begin
              LightDir := G2Vec3(0, -1, 0).Transform3x3(TG2SGLightDir(App.Scene.Lights[i]).Transform);
              AddLight := True;
              if App.Scene.CollideRay(G2Ray(ListItem^.CharPos, -LightDir.Normalized), GeomID, FaceID, U, V, D) then
              AddLight := False;
              if AddLight then
              begin
                AABox := TG2SGLightDir(App.Scene.Lights[i]).AABox;
                s := (AABox.MaxV - AABox.MinV).Len;
                LightsList.Add(TG2SGLightDir(App.Scene.Lights[i]), -s);
              end;
            end;
          end;
        end;
        ListItem^.LightsData.LightsCountPoint := 0;
        ListItem^.LightsData.LightsCountDir := 0;
        for i := 0 to LightsList.Count - 1 do
        begin
          case TG2SGLight(LightsList[i]).LightType of
            ltPoint:
            begin
              if ListItem^.LightsData.LightsCountPoint < 4 then
              begin
                ListItem^.LightsData.LightsPoint[ListItem^.LightsData.LightsCountPoint].Pos := TG2SGLightPoint(LightsList[i]).Transform.GetTranslation;
                ListItem^.LightsData.LightsPoint[ListItem^.LightsData.LightsCountPoint].Att.x := TG2SGLightPoint(LightsList[i]).AttStart;
                ListItem^.LightsData.LightsPoint[ListItem^.LightsData.LightsCountPoint].Att.y := TG2SGLightPoint(LightsList[i]).AttEnd;
                ListItem^.LightsData.LightsPoint[ListItem^.LightsData.LightsCountPoint].Color := TG2SGLightPoint(LightsList[i]).Color;
                Inc(ListItem^.LightsData.LightsCountPoint);
              end;
            end;
            ltDir:
            begin
              if ListItem^.LightsData.LightsCountDir < 2 then
              begin
                ListItem^.LightsData.LightsDir[ListItem^.LightsData.LightsCountDir].Dir := G2Vec3(0, -1, 0).Transform3x3(TG2SGLightDir(LightsList[i]).Transform);
                ListItem^.LightsData.LightsDir[ListItem^.LightsData.LightsCountDir].Color := TG2SGLightDir(LightsList[i]).Color;
                Inc(ListItem^.LightsData.LightsCountDir);
              end;
            end;
          end;
        end;
        ListItem^.Completed := True;
        ListItem^.TimeCompleted := App.AppTime;
        Inc(TotalLightTraces);
      end;
    end;
  end;
end;

function TLightTracing.QueueAdd(const Char: TG2SGChar): Boolean;
  var ListItem: PLightTracingListItem;
  var AddItem: Boolean;
  var i: Integer;
begin
  CS.Enter;
  try
    Result := False;
    AddItem := True;
    for i := 0 to List.Count - 1 do
    if (PLightTracingListItem(List[i])^.Char = Char) then
    begin
      AddItem := False;
      ListItem := PLightTracingListItem(List[i]);
      if ListItem^.Completed then
      begin
        TFPSChar(Char).LightsData := ListItem^.LightsData;
        List.Delete(i);
        ListItem^.Completed := False;
        if (Char is TMob)
        and (Char.MeshInst <> nil)
        and (TMob(Char).Killed) then
        ListItem^.CharPos := Char.MeshInst.BBox.C
        else
        ListItem^.CharPos := Char.Transform.GetTranslation - Char.Step;
        List.Add(ListItem);
        Result := True;
      end;
      Break;
    end;
    if AddItem then
    begin
      New(ListItem);
      ListItem^.Char := Char;
      ListItem^.Completed := False;
      if (Char is TMob)
      and (Char.MeshInst <> nil)
      and (TMob(Char).Killed) then
      ListItem^.CharPos := Char.MeshInst.BBox.C
      else
      ListItem^.CharPos := Char.Transform.GetTranslation - Char.Step;
      List.Add(ListItem);
    end;
  finally
    CS.Leave;
  end;
end;

function TLightTracing.TraceImmediate(const Pos: TG2Vec3): TLightsData;
  var i: Integer;
  var LightDir: TG2Vec3;
  var Dist, U, V, D, s: Single;
  var AddLight: Boolean;
  var GeomID: Integer;
  var FaceID: Word;
  var AABox: TG2AABox;
begin
  LightsList.Clear;
  for i := 0 to App.Scene.Lights.Count - 1 do
  begin
    case TG2SGLight(App.Scene.Lights[i]).LightType of
      ltPoint:
      begin
        LightDir := TG2SGLightPoint(App.Scene.Lights[i]).Transform.GetTranslation - Pos;
        Dist := LightDir.Len;
        if Dist < TG2SGLightPoint(App.Scene.Lights[i]).AttEnd * 1.2 then
        begin
          AddLight := True;
          if App.Scene.CollideRay(G2Ray(Pos, LightDir.Normalized), GeomID, FaceID, U, V, D) then
          if D < Dist then
          AddLight := False;
          if AddLight then
          begin
            AABox := TG2SGLightPoint(App.Scene.Lights[i]).AABox;
            s := (AABox.MaxV - AABox.MinV).Len;
            LightsList.Add(TG2SGLightPoint(App.Scene.Lights[i]), Dist - s);
          end;
        end;
      end;
      ltDir:
      begin
        LightDir := G2Vec3(0, -1, 0).Transform3x3(TG2SGLightDir(App.Scene.Lights[i]).Transform);
        AddLight := True;
        if App.Scene.CollideRay(G2Ray(Pos, -LightDir.Normalized), GeomID, FaceID, U, V, D) then
        AddLight := False;
        if AddLight then
        begin
          AABox := TG2SGLightDir(App.Scene.Lights[i]).AABox;
          s := (AABox.MaxV - AABox.MinV).Len;
          LightsList.Add(TG2SGLightDir(App.Scene.Lights[i]), -s);
        end;
      end;
    end;
  end;
  Result.LightsCountPoint := 0;
  Result.LightsCountDir := 0;
  for i := 0 to LightsList.Count - 1 do
  begin
    case TG2SGLight(LightsList[i]).LightType of
      ltPoint:
      begin
        if Result.LightsCountPoint < 4 then
        begin
          Result.LightsPoint[Result.LightsCountPoint].Pos := TG2SGLightPoint(LightsList[i]).Transform.GetTranslation;
          Result.LightsPoint[Result.LightsCountPoint].Att.x := TG2SGLightPoint(LightsList[i]).AttStart;
          Result.LightsPoint[Result.LightsCountPoint].Att.y := TG2SGLightPoint(LightsList[i]).AttEnd;
          Result.LightsPoint[Result.LightsCountPoint].Color := TG2SGLightPoint(LightsList[i]).Color;
          Inc(Result.LightsCountPoint);
        end;
      end;
      ltDir:
      begin
        if Result.LightsCountDir < 2 then
        begin
          Result.LightsDir[Result.LightsCountDir].Dir := G2Vec3(0, -1, 0).Transform3x3(TG2SGLightDir(LightsList[i]).Transform);
          Result.LightsDir[Result.LightsCountDir].Color := TG2SGLightDir(LightsList[i]).Color;
          Inc(Result.LightsCountDir);
        end;
      end;
    end;
  end;
end;
//TLightTracing END

//TFPSChar BEGIN
constructor TFPSChar.Create(const SceneGraph: TG2SceneGraph);
begin
  inherited Create(SceneGraph);
  TimeLightsGather := App.AppTime - Random(100);
end;

procedure TFPSChar.Update;
begin
  inherited Update;
  if App.AppTime - TimeLightsGather > 20 then
  GetLights;
end;

procedure TFPSChar.GetLights;
begin
  App.LightTracer.QueueAdd(Self);
  TimeLightsGather := App.AppTime;
end;
//TFPSChar END

//TPlayer BEGIN
constructor TPlayer.Create(const SceneGraph: TG2SceneGraph);
begin
  inherited Create(SceneGraph);
  Collide := True;
  TimeShotLength := 100;
  TimeLastShot := App.AppTime - 2000;
  TimeHit := App.AppTime - 2000;
  AccuracyMin := 5;
  AccuracyMax := 40;
  Accuracy := AccuracyMin;
  AmmoMax := 12;
  Clip := 60;
  Ammo := AmmoMax;
  Reloading := False;
  LengthReload := 2000;
  InitSetup;
end;

procedure TPlayer.InitSetup;
begin
  Vel.SetValue(0, 0, 0);
  Radius := 1.2;
  Step.SetValue(0, -2, 0);
  Health := 100;
  Armor := 0;
  Clip := 0;
  Ammo := AmmoMax;
  Dead := False;
  Transform.SetTranslation(App.Scene.FindNode('Player_01').Transform.GetTranslation);
  GetLights;
end;

procedure TPlayer.Update;
  var i, AmmoReq: Integer;
  var Pos: TG2Vec3;
begin
  inherited Update;
  if not Dead
  and (Health <= 0) then
  begin
    Dead := True;
    TimeDead := App.AppTime;
    Radius := 1;
    Step.SetValue(0, -1.1, 0);
    App.SndDeath.Play;
  end;
  if Dead and (App.AppTime - TimeDead > 10000) then
  begin
    App.TimeToEndGame := True;
    Exit;
  end;
  if not Dead then
  begin
    if Reloading
    and (App.AppTime - TimeReload > LengthReload) then
    begin
      AmmoReq := AmmoMax - Ammo;
      if Clip >= AmmoReq then
      begin
        Clip := Clip - AmmoReq;
        Ammo := Ammo + AmmoReq;
      end
      else
      begin
        Ammo := Ammo + Clip;
        Clip := 0;
      end;
      App.SndClipIn.Play;
      Reloading := False;
    end;
    Accuracy := Accuracy - 0.3;
    if Accuracy < AccuracyMin then Accuracy := AccuracyMin;
    if Accuracy > AccuracyMax then Accuracy := AccuracyMax;
    Pos := Transform.GetTranslation - Step;
    for i := 0 to App.ExtraItems.Count - 1 do
    if TExtraItem(App.ExtraItems[i]).Active
    and ((TExtraItem(App.ExtraItems[i]).Pos - Pos).Len < 3) then
    TExtraItem(App.ExtraItems[i]).PickUp;
  end;
end;

procedure TPlayer.Shoot;
  var r: TG2Ray;
  var HitPos: TG2Vec3;
  var HitN: TG2Vec3;
  var GeomID: Integer;
  var FaceID: Word;
  var CurU, CurV, CurD: Single;
  var CharD: Single;
  var CurChar: TG2SGChar;
  var CharPos: TG2Vec3;
  var PlayerPos: TG2Vec3;
  var Bounce: TParticleBounce;
  var BloodSplat: TParticleBloodSplat;
  var BloodSplash: TParticleBloodSplash;
  var Dust: TParticleDust;
  var BulletHole: TParticleBulletHole;
  var m: TG2Mat;
  var i, j: Integer;
  var Crossair: TG2Vec2;
  var TargetDamageMod: Single;
begin
  if (Ammo <= 0) then
  begin
    App.SndPistolDryfire.Play;
    Exit;
  end;
  if Reloading
  or (App.AppTime - TimeLastShot < 100) then Exit;
  Ammo := Ammo - 1;
  TimeLastShot := App.AppTime;
  App.SndShot1.Play;
  ShotAng := G2Random2Pi;
  m.SetIdentity;
  App.Gfx.Transforms.W[0] := m;
  App.Gfx.Transforms.V := App.Cam.View;
  App.Gfx.Transforms.P := App.Cam.Proj;
  App.Gfx.Transforms.PushW;
  Crossair := G2Vec2(App.Gfx.Params.Width div 2, App.Gfx.Params.Height div 2) + G2RandomCirclePoint * (Accuracy * Random(1001) * 0.001);
  Accuracy := Accuracy + 6;
  r := App.Gfx.PointToRay(Point(Round(Crossair.x), Round(Crossair.y)));
  App.Scene.QueryFetch(App.Gfx.Transforms.Frustum, App.Query);
  CurChar := nil;
  TargetDamageMod := 1;
  for i := 0 to App.Query.CharCount - 1 do
  if TG2SGChar(App.Query.Chars[i]) is TMob then
  begin
    CharPos := TG2SGChar(App.Query.Chars[i]).Transform.GetTranslation - TG2SGChar(App.Query.Chars[i]).Step;
    PlayerPos := Transform.GetTranslation - Step;
    if (CharPos - PlayerPos).Len < 70 then
    TMob(App.Query.Chars[i]).TimeBeenShot := App.AppTime;
    App.Gfx.Transforms.W[0] := TG2SGChar(App.Query.Chars[i]).Transform;
    if r.IntersectAABox(TMob(App.Query.Chars[i]).GetAABox) then
    for j := 0 to High(TMob.TargetGeoms) do
    if TG2SGChar(App.Query.Chars[i]).MeshInst.PickGeom(TMob.TargetGeoms[j].GeomID, r, @CurD) then
    begin
      if (CurChar = nil) or (CurD < CharD) then
      begin
        CurChar := TG2SGChar(App.Query.Chars[i]);
        CharD := CurD;
        TargetDamageMod := TMob.TargetGeoms[j].DamageMod;
      end;
    end;
  end;
  App.Gfx.Transforms.PopW;
  if App.Scene.CollideRay(r, GeomID, FaceID, CurU, CurV, CurD) then
  begin
    if (CurChar = nil) or (CurD < CharD) then
    begin
      CurChar := nil;
      HitPos := r.Origin + r.Dir * CurD;
      HitN := TG2SGGeom(App.Scene.Geoms[GeomID]).Collider.Faces[FaceID].Plane.N;
      for i := 0 to 2 do
      begin
        Bounce := TParticleBounce.Create;
        Bounce.PosSrc := r.Origin + r.Dir * CurD;
        Bounce.Dir := (HitN + G2RandomSpherePoint * 0.4).Normalized;
        Bounce.Update;
        App.Particles.AddParticle(Bounce);
      end;
      for i := 0 to 4 do
      begin
        Dust := TParticleDust.Create;
        Dust.X := HitPos.x; Dust.Y := HitPos.y; Dust.Z := HitPos.z;
        App.Particles.AddParticle(Dust);
      end;
      BulletHole := TParticleBulletHole.Create;
      BulletHole.X := HitPos.x + HitN.x * 0.02;
      BulletHole.Y := HitPos.y + HitN.y * 0.02;
      BulletHole.Z := HitPos.z + HitN.z * 0.02;
      BulletHole.SetDir(HitN);
      App.Particles.AddParticle(BulletHole);
    end;
  end;
  if CurChar <> nil then
  begin
    HitPos := r.Origin + r.Dir * CharD;
    for i := 0 to 4 do
    begin
      BloodSplash := TParticleBloodSplash.Create;
      BloodSplash.PosSrc := r.Origin + r.Dir * CharD;
      BloodSplash.Dir := (G2RandomSpherePoint * 0.4 - r.Dir).Normalized;
      BloodSplash.Update;
      App.Particles.AddParticle(BloodSplash);
    end;
    BloodSplat := TParticleBloodSplat.Create;
    BloodSplat.X := HitPos.x - r.Dir.x * 0.1;
    BloodSplat.Y := HitPos.y - r.Dir.y * 0.1;
    BloodSplat.Z := HitPos.z - r.Dir.z * 0.1;
    App.Particles.AddParticle(BloodSplat);
    if TargetDamageMod > 1.5 then
    begin
      App.SndHeadshot.SoundPosition := HitPos - Transform.GetTranslation;
      App.SndHeadshot.SoundPosition := App.SndHeadshot.SoundPosition.Transform3x3(App.Cam.View);
      App.SndHeadshot.SoundPosition := App.SndHeadshot.SoundPosition * 0.1;
      App.SndHeadshot.Play;
    end;
    TMob(CurChar).Health := TMob(CurChar).Health - Round(15 * TargetDamageMod);
    TMob(CurChar).TimeBeenShot := App.AppTime;
    if not TMob(CurChar).Killed
    and (TMob(CurChar).Health <= 0) then
    begin
      TMob(CurChar).Kill;
      TMob(CurChar).Ragdoll.Impulse(HitPos, r.Dir * 10);
    end;
  end;
end;

procedure TPlayer.Reload;
begin
  if Reloading
  or (Ammo >= AmmoMax)
  or (Clip <= 0) then
  Exit;
  Reloading := True;
  App.SndClipOut.Play;
  TimeReload := App.AppTime;
end;

procedure TPlayer.RenderGun;
  var s: Single;
  var m, W, VP, WVP: TG2Mat;
  var MFID, VSID, PSID: Integer;
  var b: Byte;
  var vx, vy: TG2Vec2;
  var i, g: Integer;
  var c0, c1: TG2Color;
  var CamPos, CamTarget: TG2Vec3;
  var PlayerTransformInv: TG2Mat;
  var LightsPointPos: array[0..3] of TG2Vec3;
  var LightsPointAtt: array[0..3] of TG2Vec2;
  var LightsPointColor: array[0..3] of TG2Vec4;
  var LightsDirDir: array[0..3] of TG2Vec3;
  var LightsDirColor: array[0..3] of TG2Vec4;
  var LData: TLightsData;
  const DamageRectSize = 128;
  var HUDPosHealth, HUDPosArmor, HUDPosAmmo, HUDPosClip: Integer;
begin
  with App do
  begin
    if not Dead then
    begin
      CamPos := MeshPlayerInst.NodeTransforms[MeshPlayer.NodeIndex('Camera01')].TransformRen.GetTranslation;
      CamTarget := MeshPlayerInst.NodeTransforms[MeshPlayer.NodeIndex('Camera01Target')].TransformRen.GetTranslation;
      s := 0.005;
      CamPos := CamPos * s;
      CamTarget := CamTarget * s;
      m.SetScaling(s, s, s);
      if Reloading then
      begin
        s := Sin(Min((App.AppTime - TimeReload) / LengthReload, 1) * Pi) * 0.5;
        m.Translate(0, -s, 0);
      end;
      Gfx.Transforms.W[0] := m;
      m.SetView(CamPos, CamTarget, G2Vec3(0, 1, 0));
      Gfx.Transforms.V := m;
      Gfx.Transforms.ApplyV;
      VP := Gfx.Transforms.VP;
      Gfx.RenderStates.ZEnable := True;
      MeshPlayerInst.FrameSetFast('HandGun01', 0);

      PlayerTransformInv := Cam.View;
      PlayerTransformInv := PlayerTransformInv;
      LData := LightsData;
      for i := 0 to LData.LightsCountPoint - 1 do
      begin
        LData.LightsPoint[i].Pos := LData.LightsPoint[i].Pos - Cam.Pos^;
        LData.LightsPoint[i].Pos := LData.LightsPoint[i].Pos.Transform3x3(PlayerTransformInv);
      end;
      for i := 0 to LData.LightsCountDir - 1 do
      begin
        LData.LightsDir[i].Dir := LData.LightsDir[i].Dir.Transform3x3(PlayerTransformInv);
      end;
      Shaders.Technique := 'General';
      Shaders.BeginEffect(nil);
      Shaders.BeginPass(0);
      for i := 0 to LData.LightsCountPoint - 1 do
      begin
        LightsPointPos[i] := LData.LightsPoint[i].Pos;
        LightsPointAtt[i] := LData.LightsPoint[i].Att;
        LightsPointColor[i] := LData.LightsPoint[i].Color;
      end;
      for i := 0 to LData.LightsCountDir - 1 do
      begin
        LightsDirDir[i] := LData.LightsDir[i].Dir;
        LightsDirColor[i] := LData.LightsDir[i].Color;
      end;
      Shaders.SetVectorArray('LightsPointPos', @LightsPointPos[0], LData.LightsCountPoint);
      Shaders.SetVectorArray('LightsPointAtt', @LightsPointAtt[0], LData.LightsCountPoint);
      Shaders.SetVectorArray('LightsPointColor', @LightsPointColor[0], LData.LightsCountPoint);
      Shaders.SetVectorArray('LightsDirDir', @LightsDirDir[0], LData.LightsCountDir);
      Shaders.SetVectorArray('LightsDirColor', @LightsDirColor[0], LData.LightsCountDir);
      PSID := 4 + LightsData.LightsCountPoint * 3 + LData.LightsCountDir;
      Shaders.SetInt('PS_Index', PSID);
      for i := 0 to MeshPlayerInst.Mesh.GeomCount - 1 do
      begin
        VSID := MeshPlayerInst.Mesh.Geoms[i].MaxWeights + 1;
        Shaders.SetInt('VS_Index', VSID);
        if MeshPlayerInst.Mesh.Geoms[i].Skinned then
        begin
          Shaders.SetMatrixArray('g_SkinPallete', PD3DXMatrix(MeshPlayerInst.SkinTransforms[i]), MeshPlayerInst.Mesh.Geoms[i].BoneCount);
          W := Gfx.Transforms.W[0];
        end
        else
        W := MeshPlayerInst.NodeTransforms[MeshPlayerInst.Mesh.Geoms[i].NodeID].TransformRen * Gfx.Transforms.W[0];
        WVP := W * VP;
        Shaders.SetMatrix('g_W', W);
        Shaders.SetMatrix('g_WVP', WVP);
        for g := 0 to MeshPlayerInst.Mesh.Geoms[i].MaterialCount - 1 do
        begin
          Shaders.SetTexture('TexDiffuse0', MeshPlayerInst.Materials[MeshPlayerInst.Mesh.Geoms[i].Materials[g]].MapDiffuse.Texture);
          Shaders.CommitChanges;
          MeshPlayerInst.Mesh.Geoms[i].Mesh.DrawSubset(g);
        end;
      end;
      Shaders.EndPass;
      Shaders.EndEffect;
      //MeshPlayerInst.Render;

      if AppTime - TimeLastShot < TimeShotLength then
      begin
        MFID := MeshPlayerInst.Mesh.NodeIndex('MFPistol01');
        if MFID > -1 then
        begin
          G2SinCos(ShotAng, vx.y, vx.x);
          vy := vx.Perp;
          m.SetValue(
            G2Vec3(vx.x, vx.y, 0), G2Vec3(vy.x, vy.y, 0), G2Vec3(0, 0, 1),
            MeshPlayerInst.NodeTransforms[MFID].TransformRen.GetTranslation
          );
          m.PreScale(20, 20, 20);
          Gfx.Transforms.W[0] := m * Gfx.Transforms.W[0];
          Gfx.Transforms.ApplyW(0);
          RenderModes.BlendModeAddColor;
          s := 1 - (AppTime - TimeLastShot) / TimeShotLength;
          b := Round(s * 255);
          for i := 0 to 1 do
          Render3D.DrawQuad(
            G2Vec3(-1, 1, 0), G2Vec3(1, 1, 0),
            G2Vec3(-1, -1, 0), G2Vec3(1, -1, 0),
            G2Color(b, b, b), TexMuzzleFlash
          );
          RenderModes.BlendModeNormal;
        end;
      end;
    end;
    Gfx.RenderStates.ZEnable := False;
    s := (App.AppTime - TimeHit) / 2000;
    if Dead or (s < 1) then
    begin
      s := 1 - s;
      if Dead then
      b := 255
      else
      b := Round(255 * s);
      c0 := G2Color(b, 0, 0);
      c1 := $ff000000;
      Render.TextureClear();
      RenderModes.BlendModeAddColor;
      Prim2D.DrawQuad4Col(
        G2Vec2(0, 0), G2Vec2(DamageRectSize, DamageRectSize),
        G2Vec2(0, Gfx.Params.Height), G2Vec2(DamageRectSize, Gfx.Params.Height - DamageRectSize),
        c0, c1,
        c0, c1
      );
      Prim2D.DrawQuad4Col(
        G2Vec2(0, 0), G2Vec2(Gfx.Params.Width, 0),
        G2Vec2(DamageRectSize, DamageRectSize), G2Vec2(Gfx.Params.Width - DamageRectSize, DamageRectSize),
        c0, c0,
        c1, c1
      );
      Prim2D.DrawQuad4Col(
        G2Vec2(Gfx.Params.Width - DamageRectSize, DamageRectSize), G2Vec2(Gfx.Params.Width, 0),
        G2Vec2(Gfx.Params.Width - DamageRectSize, Gfx.Params.Height - DamageRectSize), G2Vec2(Gfx.Params.Width, Gfx.Params.Height),
        c1, c0,
        c1, c0
      );
      Prim2D.DrawQuad4Col(
        G2Vec2(DamageRectSize, Gfx.Params.Height - DamageRectSize), G2Vec2(Gfx.Params.Width - DamageRectSize, Gfx.Params.Height - DamageRectSize),
        G2Vec2(0, Gfx.Params.Height), G2Vec2(Gfx.Params.Width, Gfx.Params.Height),
        c1, c1,
        c0, c0
      );
      RenderModes.BlendModeNormal;
    end;
    if not Dead then
    begin
      HUDPosHealth := 4;
      HUDPosArmor := HUDPosHealth + 64 + 80 + 4;
      HUDPosClip := Gfx.Params.Width - 80 - 64 - 4;
      HUDPosAmmo := HUDPosClip - 80 - 64 - 4;
      Render2D.DrawRect(HUDPosHealth, Gfx.Params.Height - 64 - 4, 64, 64, Rect(0, 0, 64, 64), TexHUD);
      Font2.Print(HUDPosHealth + 64, Gfx.Params.Height - 64, $ffff0000, IntToStr(Health));
      Render2D.DrawRect(HUDPosArmor, Gfx.Params.Height - 64 - 4, 64, 64, Rect(64, 0, 128, 64), TexHUD);
      Font2.Print(HUDPosArmor + 64, Gfx.Params.Height - 64, $ffff0000, IntToStr(Armor));
      Render2D.DrawRect(HUDPosAmmo, Gfx.Params.Height - 64 - 4, 64, 64, Rect(128, 0, 192, 64), TexHUD);
      Font2.Print(HUDPosAmmo + 64, Gfx.Params.Height - 64, $ffff0000, IntToStr(Ammo));
      Render2D.DrawRect(HUDPosClip, Gfx.Params.Height - 64 - 4, 64, 64, Rect(192, 0, 256, 64), TexHUD);
      Font2.Print(HUDPosClip + 64, Gfx.Params.Height - 64, $ffff0000, IntToStr(Clip));
    end;
  end;
end;

procedure TPlayer.Hit(const Damage: Integer);
  var DamageToArmorGrad: Single;
  var DamageToArmor, DamageLeft: Integer;
begin
  DamageToArmorGrad := Max(Armor * 0.01, 0.05);
  DamageToArmor := Round(Damage * DamageToArmorGrad);
  if Armor > DamageToArmor then
  begin
    DamageLeft := Damage - DamageToArmor;
    Armor := Armor - DamageToArmor;
  end
  else
  begin
    DamageLeft := Damage - (DamageToArmor - Armor);
    Armor := 0;
  end;
  TimeHit := App.AppTime;
  Health := Health - DamageLeft;
end;
//TPlayer END

//TExtraItem BEGIN
constructor TExtraItem.Create(const NewPos: TG2Vec3);
  var r: TG2Ray;
  var GeomID: Integer;
  var FaceID: Word;
  var U, V, D: Single;
begin
  inherited Create;
  Pos := NewPos;
  r.SetValue(Pos, G2Vec3(0, -1, 0));
  if App.Scene.CollideRay(r, GeomID, FaceID, U, V, D) then
  Pos := Pos + G2Vec3(0, -(D - 0.1), 0);
  LightsData := App.LightTracer.TraceImmediate(Pos);
  Active := True;
  TimeToActivate := 60000;
  App.ExtraItems.Add(Self);
end;

destructor TExtraItem.Destroy;
begin
  App.ExtraItems.Remove(Self);
  inherited Destroy;
end;

procedure TExtraItem.Update;
begin
  if not Active
  and (App.AppTime - PickUpTime > TimeToActivate) then
  begin
    Active := True;
  end;
end;

procedure TExtraItem.PickUp;
begin
  Active := False;
  PickUpTime := App.AppTime;
  OnPickUp;
end;
//TExtraItem END

//TArmor BEGIN
constructor TArmor.Create(const NewPos: TG2Vec3);
begin
  inherited Create(NewPos);
  MeshInst := App.MeshArmor.InstanceCreate;
  App.LoadTextures(MeshInst);
end;

destructor TArmor.Destroy;
begin
  MeshInst.Free;
  inherited Destroy;
end;

procedure TArmor.OnPickUp;
begin
  App.Player.Armor := 100;
  App.SndArmor.Play;
end;
//TArmor END

//TAmmo1 BEGIN
constructor TAmmo1.Create(const NewPos: TG2Vec3);
begin
  inherited Create(NewPos);
  MeshInst := App.MeshAmmo1.InstanceCreate;
  App.LoadTextures(MeshInst);
end;

destructor TAmmo1.Destroy;
begin
  MeshInst.Free;
  inherited Destroy;
end;

procedure TAmmo1.OnPickUp;
begin
  App.Player.Clip := App.Player.Clip + 24;
  App.SndAmmo.Play;
end;
//TAmmo1 END

//TMenu BEGIN
function TMenu.McInButton(const Index: Integer): Boolean;
  var x, y: Integer;
begin
  x := (App.Gfx.Params.Width - 256) div 2;
  y := 320;
  Result := PtInRect(
    Rect(x, y + 64 * Index, x + 256, y + 64 * Index + 64),
    App.PlugInput.MousePos
  );
end;

procedure TMenu.AddButton(const Texture: TG2Texture2D; const Proc: TG2ProcObj = nil);
begin
  SetLength(Buttons, Length(Buttons) + 1);
  Buttons[High(Buttons)].Texture := Texture;
  Buttons[High(Buttons)].Proc := Proc;
end;

procedure TMenu.Render;
  var i, x, y: Integer;
begin
  x := (App.Gfx.Params.Width - 256) div 2;
  y := 320;
  for i := 0 to High(Buttons) do
  begin
    App.Render2D.DrawRect(
      x, y + i * 64,
      Buttons[i].Texture
    );
    if McInButton(i) then
    begin
      App.RenderModes.BlendModeAdd;
      App.Render2D.DrawRect(
        x, y + i * 64,
        Buttons[i].Texture
      );
      App.RenderModes.BlendModeNormal;
    end;
  end;
end;

procedure TMenu.Click;
  var i: Integer;
begin
  for i := 0 to High(Buttons) do
  if McInButton(i) then
  begin
    if Assigned(Buttons[i].Proc) then
    Buttons[i].Proc();
    Break;
  end;
end;
//TMenu END

//TMyApp BEGIN
procedure TMyApp.OnRender;
begin
  Render.Clear(False, True, True, $ff808080);
  Render.RenderStart;

  case GameState of
    gsIntro: RenderIntro;
    gsMenu: RenderMenu;
    gsGame: RenderGame;
    gsGameOver: RenderGameOver;
  end;

  Render.RenderStop;
  Render.Present;
end;

procedure TMyApp.OnUpdate;
begin
  if not Music.IsPlaying then
  Music.Play;
  case GameState of
    gsIntro: UpdateIntro;
    gsMenu: UpdateMenu;
    gsGame: UpdateGame;
    gsGameOver: UpdateGameOver;
  end;
end;

procedure TMyApp.OnKeyDown(const Key: Byte);
begin
  if Key = DIK_ESCAPE then Running := False;
  case GameState of
    gsMenu:
    begin

    end;
    gsGame:
    begin
      if Key = DIK_F then Wireframe := not Wireframe;
      if (Key = DIK_SPACE)
      and (not Player.Dead)
      and Player.Grounded then
      begin
        Player.Vel := Player.Vel + G2Vec3(0, 0.4, 0);
        Player.Grounded := False;
      end;
      if (Key = DIK_R)
      and (not Player.Dead) then
      Player.Reload;
    end;
    gsGameOver:
    begin
      GameState := gsMenu;
    end;
  end;
end;

procedure TMyApp.OnMouseDown(const Button: Byte);
begin
  case GameState of
    gsMenu:
    begin
      if Button = 0 then
      Menu.Click;
    end;
    gsGame:
    begin
      if (Button = 0)
      and not Player.Dead then
      Player.Shoot;
    end;
  end;
end;

procedure TMyApp.Initialize;
  var i, j: Integer;
begin
  CS := TCriticalSection.Create;
  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;
  Gfx.InitParams.FullScreen := True;
  Gfx.InitParams.FormatBackBuffer := D3DFMT_A8R8G8B8;
  Gfx.InitParams.FormatSurfaceRT := D3DFMT_A8R8G8B8;
  Gfx.InitParams.FormatSurfaceDS := D3DFMT_D24S8;
  g2.Handle := App.WindowCreate(0, 0, 'Gen2'); //(Gfx.InitParams.Width, Gfx.InitParams.Height, 'Gen2');
  Tmr.Mode := tmManual;
  //Tmr.MaxFPS := 120;
  inherited Initialize;
  ShowCursor(False);
  g2.PackLinker.LinkPack('Data.g2pk', 'Project69 Encryption Key');
  Font1 := Gfx.Shared.RequestFont('Times New Roman', 16);
  Font2 := Gfx.Shared.RequestFont('Times New Roman', 32);
  RenderLoading;
  g2.RequestMod(TG2MeshMgr, @MgrMeshes);
  g2.RequestMod(TG2TextureMgr, @MgrTextures);
  g2.RequestMod(TG2SoundMgr, @MgrSounds);
  g2.RequestMod(TG2MusicMgr, @MgrMusic);
  g2.RequestMod(TG2EffectMgr, @MgrEffects);
  g2.RequestMod(TG2Particles3D, @Particles);
  TexMuzzleFlash := MgrTextures.CreateTexture2DFromPack('MuzzleFlash', 'Textures', 'Fireball');
  TexBounce := MgrTextures.CreateTexture2DFromPack('Bounce', 'Textures', 'Bounce');
  TexBlood01 := MgrTextures.CreateTexture2DFromPack('Blood01', 'Textures', 'Blood01');
  TexBlood02 := MgrTextures.CreateTexture2DFromPack('Blood02', 'Textures', 'Blood02');
  TexDust01 := MgrTextures.CreateTexture2DFromPack('Dust01', 'Textures', 'Dust01');
  TexBulletHole01 := MgrTextures.CreateTexture2DFromPack('BulletHole01', 'Textures', 'BulletHole01');
  TexHUD := MgrTextures.CreateTexture2DFromPack('HUD', 'Textures', 'HUD');
  TexTitle := MgrTextures.CreateTexture2DFromPack('Title', 'Textures', 'Title');
  TexMenuBG := MgrTextures.CreateTexture2DFromPack('MenuBG', 'Textures', 'MenuBG');
  TexGameOver := MgrTextures.CreateTexture2DFromPack('GameOver', 'Textures', 'GameOver');
  TexLogoIGDC := MgrTextures.CreateTexture2DFromPack('IGDC', 'Textures', 'LogoIGDC');

  Shaders := MgrEffects.CreateEffectFromFile('Shaders', 'Shaders.cfx');

  SkyBox := TG2SkyBox.Create(g2);
  SkyBox.Texture := MgrTextures.CreateTextureCubeFromPack('SkyBox', 'Textures', 'Sky');

  MeshPlayer := MgrMeshes.CreateMeshFromPack('Player', 'Meshes', 'Player');
  MeshArmor := MgrMeshes.CreateMeshFromPack('Armor', 'Meshes', 'Armor');
  MeshAmmo1 := MgrMeshes.CreateMeshFromPack('Ammo1', 'Meshes', 'Ammo1');

  SndClipOut := MgrSounds.CreateSoundFromFile('ClipOut', 'ClipOut.wav').CreateInstance;
  SndClipIn := MgrSounds.CreateSoundFromFile('ClipIn', 'ClipIn.wav').CreateInstance;
  SndPistolDryfire := MgrSounds.CreateSoundFromFile('PistolDryfire', 'PistolDryfire.wav').CreateInstance;
  SndShot1 := MgrSounds.CreateSoundFromFile('Shot1', 'Shot1.wav').CreateInstance;
  SndHeadshot := MgrSounds.CreateSoundFromFile('Headshot', 'Headshot.wav').CreateInstance;
  SndDeath := MgrSounds.CreateSoundFromFile('Death', 'Death.wav').CreateInstance;
  SndAmmo := MgrSounds.CreateSoundFromFile('Ammo', 'Ammo.wav').CreateInstance;
  SndArmor := MgrSounds.CreateSoundFromFile('Armor', 'Armor.wav').CreateInstance;
  for i := 0 to 2 do
  SndClawStrike[i] := MgrSounds.CreateSoundFromFile('ClawStrike' + IntToStr(i), 'ClawStrike' + IntToStr(i) + '.wav').CreateInstance;
  Music := MgrMusic.CreateMusic('Music');
  Music.OpenFile('Music.mp3');

  Wireframe := False;
  Gfx.RenderStates.Ambient := $ffffff;
  Gravity.SetValue(0, -0.02, 0);

  Menu.AddButton(MgrTextures.CreateTexture2DFromPack('Btn01', 'Textures', 'MenuBtn01', 1), GameBegin);
  Menu.AddButton(MgrTextures.CreateTexture2DFromPack('Btn02', 'Textures', 'MenuBtn02', 1), MenuBtnQuit);

  GameLoaded := False;
  GameState := gsIntro;
  TimeIntroStart := App.AppTime;
  Music.Play;

  Tmr.Enabled := True;
end;

procedure TMyApp.Finalize;
begin
  if GameLoaded then
  GameEnd;
  SkyBox.Free;
  CS.Free;
  inherited Finalize;
end;

procedure TMyApp.RenderSceneDebug;
  var m: TG2Mat;
  var i, g: Integer;
  var Geom: TG2SGGeom;
begin
  Scene.QueryFetch(Gfx.Transforms.Frustum, Query);
  Gfx.RenderStates.ZEnable := True;
  RenderModes.FilteringLinear();
  Gfx.Device.SetVertexDeclaration(Scene.DataFormats.DeclGeom);
  if Wireframe then
  Gfx.RenderStates.FillMode := D3DFILL_WIREFRAME;
  for i := 0 to Query.GeomCount - 1 do
  begin
    Geom := Query.Geoms[i];
    m := Geom.Transform;
    Gfx.Transforms.W[0] := m;
    Gfx.Transforms.ApplyW(0);
    Gfx.Device.SetStreamSource(0, Geom.VB, 0, Scene.DataFormats.VertexStrideGeom);
    Gfx.Device.SetIndices(Geom.IB);
    for g := 0 to Geom.GCount - 1 do
    begin
      Render.TextureSet(Geom.Groups[g].Material^.Channels[0].TexDiffuse);
      Gfx.Device.DrawIndexedPrimitive(
        D3DPT_TRIANGLELIST, 0,
        Geom.Groups[g].VStart, Geom.Groups[g].VCount,
        Geom.Groups[g].FStart * 3, Geom.Groups[g].FCount
      );
    end;
  end;
  Gfx.Transforms.PushW;
  for i := 0 to Query.CharCount - 1 do
  begin
    Gfx.Transforms.W[0] := Query.Chars[i].Transform;
    Query.Chars[i].MeshInst.Render;
  end;
  Gfx.Transforms.PopW;
  Gfx.RenderStates.FillMode := D3DFILL_SOLID;
  Gfx.RenderStates.ZEnable := False;
  RenderModes.FilteringPoint();
end;

procedure TMyApp.RenderScene;
  var m, VP, W, WVP: TG2Mat;
  var i, j, g, l, VSID, PSID: Integer;
  var Geom: TG2SGGeom;
  var v: TG2Vec3;
  var TexMod: TG2Vec2;
  var AABox: TG2AABox;
  var LightsData: TLightsData;
  var LightsPointPos: array[0..3] of TG2Vec3;
  var LightsPointAtt: array[0..3] of TG2Vec2;
  var LightsPointColor: array[0..3] of TG2Vec4;
  var LightsDirDir: array[0..3] of TG2Vec3;
  var LightsDirColor: array[0..3] of TG2Vec4;
begin
  Scene.QueryFetch(Gfx.Transforms.Frustum, Query);
  RenderModes.FilteringLinear();
  SkyBox.Render;
  RenderModes.FilteringPoint();
  if Wireframe then
  Gfx.RenderStates.FillMode := D3DFILL_WIREFRAME;
  Gfx.Transforms.PushW;
  VP := Gfx.Transforms.VP;
  Gfx.RenderStates.ZEnable := True;
  RenderModes.FilteringLinear();
  Gfx.RenderStates.AlphaBlendEnable := False;
  Gfx.RenderStates.AlphaTestEnable := True;
  Gfx.RenderStates.AlphaRef := 180;
  Gfx.Device.SetVertexDeclaration(Scene.DataFormats.DeclGeom);
  Shaders.Technique := 'General';
  Shaders.BeginEffect(nil);
  Shaders.SetInt('VS_Index', 0);
  Shaders.BeginPass(0);
  for i := 0 to Query.GeomCount - 1 do
  begin
    Geom := Query.Geoms[i];
    WVP := Geom.Transform * VP;
    Shaders.SetMatrix('g_WVP', WVP);
    Gfx.Device.SetStreamSource(0, Geom.VB, 0, Scene.DataFormats.VertexStrideGeom);
    Gfx.Device.SetIndices(Geom.IB);
    if Assigned(Geom.UserData) then
    begin
      if PNodeExtraData(Geom.UserData)^.TexMove then
      TexMod.SetValue(0, (G2TimeInterval(15000)));
    end
    else
    begin
      TexMod.SetValue(0, 0);
    end;
    Shaders.SetVec2('g_TexMod', TexMod);
    for g := 0 to Geom.GCount - 1 do
    if Geom.Groups[g].Material^.ChannelCount > 0 then
    begin
      case Geom.Groups[g].Material^.ChannelCount of
        1:
        begin
          Shaders.SetTexture('TexDiffuse0', Geom.Groups[g].Material^.Channels[0].TexDiffuse.Texture);
          PSID := 0;
        end;
        2:
        begin
          Shaders.SetTexture('TexDiffuse0', Geom.Groups[g].Material^.Channels[0].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse1', Geom.Groups[g].Material^.Channels[1].TexDiffuse.Texture);
          PSID := 1;
        end;
        3:
        begin
          Shaders.SetTexture('TexDiffuse0', Geom.Groups[g].Material^.Channels[0].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse1', Geom.Groups[g].Material^.Channels[1].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse2', Geom.Groups[g].Material^.Channels[2].TexDiffuse.Texture);
          PSID := 2;
        end;
        4:
        begin
          Shaders.SetTexture('TexDiffuse0', Geom.Groups[g].Material^.Channels[0].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse1', Geom.Groups[g].Material^.Channels[1].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse2', Geom.Groups[g].Material^.Channels[2].TexDiffuse.Texture);
          Shaders.SetTexture('TexDiffuse3', Geom.Groups[g].Material^.Channels[3].TexDiffuse.Texture);
          PSID := 3;
        end;
      end;
      if Geom.Groups[g].Material^.Channels[0].TwoSided then
      Gfx.RenderStates.CullMode := D3DCULL_NONE
      else
      Gfx.RenderStates.CullMode := D3DCULL_CCW;
      Shaders.SetTexture('TexLightMap', Geom.Groups[g].Material^.Channels[0].TexLightMap.Texture);
      Shaders.SetInt('PS_Index', PSID);
      Shaders.CommitChanges;
      Gfx.Device.DrawIndexedPrimitive(
        D3DPT_TRIANGLELIST, 0,
        Geom.Groups[g].VStart, Geom.Groups[g].VCount,
        Geom.Groups[g].FStart * 3, Geom.Groups[g].FCount
      );
    end;
  end;
  Gfx.RenderStates.CullMode := D3DCULL_CCW;
  for i := 0 to Query.CharCount - 1 do
  if (TG2SGChar(Query.Chars[i]) is TFPSChar)
  and (Query.Chars[i].Mesh <> nil) then
  begin
    LightsData := TFPSChar(Query.Chars[i]).LightsData;
    for l := 0 to LightsData.LightsCountPoint - 1 do
    begin
      LightsPointPos[l] := LightsData.LightsPoint[l].Pos;
      LightsPointAtt[l] := LightsData.LightsPoint[l].Att;
      LightsPointColor[l] := LightsData.LightsPoint[l].Color;
    end;
    for l := 0 to LightsData.LightsCountDir - 1 do
    begin
      LightsDirDir[l] := LightsData.LightsDir[l].Dir;
      LightsDirColor[l] := LightsData.LightsDir[l].Color;
    end;
    Shaders.SetVectorArray('LightsPointPos', @LightsPointPos[0], LightsData.LightsCountPoint);
    Shaders.SetVectorArray('LightsPointAtt', @LightsPointAtt[0], LightsData.LightsCountPoint);
    Shaders.SetVectorArray('LightsPointColor', @LightsPointColor[0], LightsData.LightsCountPoint);
    Shaders.SetVectorArray('LightsDirDir', @LightsDirDir[0], LightsData.LightsCountDir);
    Shaders.SetVectorArray('LightsDirColor', @LightsDirColor[0], LightsData.LightsCountDir);
    PSID := 4 + LightsData.LightsCountPoint * 3 + LightsData.LightsCountDir;
    Shaders.SetInt('PS_Index', PSID);
    for j := 0 to Query.Chars[i].Mesh.GeomCount - 1 do
    if Query.Chars[i].MeshInst.GeomProperties[j].Visible then
    begin
      VSID := Query.Chars[i].Mesh.Geoms[j].MaxWeights + 1;
      Shaders.SetInt('VS_Index', VSID);
      if Query.Chars[i].MeshInst.Mesh.Geoms[j].Skinned then
      begin
        Shaders.SetMatrixArray('g_SkinPallete', PD3DXMatrix(Query.Chars[i].MeshInst.SkinTransforms[j]), Query.Chars[i].Mesh.Geoms[j].BoneCount);
        W := Query.Chars[i].Transform;
      end
      else
      W := Query.Chars[i].MeshInst.NodeTransforms[Query.Chars[i].Mesh.Geoms[j].NodeID].TransformRen * Query.Chars[i].Transform;
      WVP := W * VP;
      Shaders.SetMatrix('g_W', W);
      Shaders.SetMatrix('g_WVP', WVP);
      for g := 0 to Query.Chars[i].Mesh.Geoms[j].MaterialCount - 1 do
      begin
        Shaders.SetTexture('TexDiffuse0', Query.Chars[i].MeshInst.Materials[Query.Chars[i].Mesh.Geoms[j].Materials[g]].MapDiffuse.Texture);
        Shaders.CommitChanges;
        Query.Chars[i].MeshInst.Mesh.Geoms[j].Mesh.DrawSubset(g);
      end;
    end;
  end;
  for i := 0 to ExtraItems.Count - 1 do
  if TExtraItem(ExtraItems[i]).Active then
  begin
    m.SetTranslation(TExtraItem(ExtraItems[i]).Pos);
    LightsData := TExtraItem(ExtraItems[i]).LightsData;
    for l := 0 to LightsData.LightsCountPoint - 1 do
    begin
      LightsPointPos[l] := LightsData.LightsPoint[l].Pos;
      LightsPointAtt[l] := LightsData.LightsPoint[l].Att;
      LightsPointColor[l] := LightsData.LightsPoint[l].Color;
    end;
    for l := 0 to LightsData.LightsCountDir - 1 do
    begin
      LightsDirDir[l] := LightsData.LightsDir[l].Dir;
      LightsDirColor[l] := LightsData.LightsDir[l].Color;
    end;
    Shaders.SetVectorArray('LightsPointPos', @LightsPointPos[0], LightsData.LightsCountPoint);
    Shaders.SetVectorArray('LightsPointAtt', @LightsPointAtt[0], LightsData.LightsCountPoint);
    Shaders.SetVectorArray('LightsPointColor', @LightsPointColor[0], LightsData.LightsCountPoint);
    Shaders.SetVectorArray('LightsDirDir', @LightsDirDir[0], LightsData.LightsCountDir);
    Shaders.SetVectorArray('LightsDirColor', @LightsDirColor[0], LightsData.LightsCountDir);
    PSID := 4 + LightsData.LightsCountPoint * 3 + LightsData.LightsCountDir;
    Shaders.SetInt('PS_Index', PSID);
    for j := 0 to TExtraItem(ExtraItems[i]).MeshInst.Mesh.GeomCount - 1 do
    begin
      VSID := TExtraItem(ExtraItems[i]).MeshInst.Mesh.Geoms[j].MaxWeights + 1;
      Shaders.SetInt('VS_Index', VSID);
      if TExtraItem(ExtraItems[i]).MeshInst.Mesh.Geoms[j].Skinned then
      begin
        Shaders.SetMatrixArray('g_SkinPallete', PD3DXMatrix(TExtraItem(ExtraItems[i]).MeshInst.SkinTransforms[j]), TExtraItem(ExtraItems[i]).MeshInst.Mesh.Geoms[j].BoneCount);
        W := m;
      end
      else
      W := TExtraItem(ExtraItems[i]).MeshInst.NodeTransforms[TExtraItem(ExtraItems[i]).MeshInst.Mesh.Geoms[j].NodeID].TransformRen * m;
      WVP := W * VP;
      Shaders.SetMatrix('g_W', W);
      Shaders.SetMatrix('g_WVP', WVP);
      for g := 0 to TExtraItem(ExtraItems[i]).MeshInst.Mesh.Geoms[j].MaterialCount - 1 do
      begin
        Shaders.SetTexture('TexDiffuse0', TExtraItem(ExtraItems[i]).MeshInst.Materials[TExtraItem(ExtraItems[i]).MeshInst.Mesh.Geoms[j].Materials[g]].MapDiffuse.Texture);
        Shaders.CommitChanges;
        TExtraItem(ExtraItems[i]).MeshInst.Mesh.Geoms[j].Mesh.DrawSubset(g);
      end;
    end;
  end;
  Shaders.EndPass;
  Shaders.EndEffect;

  {m.SetIdentity;
  Gfx.Transforms.W[0] := m;
  Gfx.Transforms.ApplyW(0);
  Render.TextureClear(0);
  for i := 0 to Query.CharCount - 1 do
  if Query.Chars[i].Mesh <> nil then
  for j := 0 to Query.Chars[i].Mesh.GeomCount - 1 do
  begin
    if (TG2SGChar(Query.Chars[i]) is TMob)
    and TMob(Query.Chars[i]).Killed then
    TMob(Query.Chars[i]).Ragdoll.Render(Prim3D);
    if Query.Chars[i].MeshInst.GeomProperties[j].Visible then
    begin
      v := PG2Vec3(@Query.Chars[i].Transform.e30)^ - Query.Chars[i].Step;
      Prim3D.DrawSphere(v.x, v.y, v.z, Query.Chars[i].Radius, $80ff0000);
      Prim3D.DrawLine(v, v + Query.Chars[i].Step, $800000ff);
      AABox := (Query.Chars[i].MeshInst.GeomBBox[j] * Query.Chars[i].Transform).AABox;
      Prim3D.DrawBox(
        AABox.MinV.x, AABox.MinV.y, AABox.MinV.z,
        AABox.MaxV.x - AABox.MinV.x, AABox.MaxV.y - AABox.MinV.y, AABox.MaxV.z - AABox.MinV.z,
        $80ffffff
      );
    end;
  end;//}

  Gfx.RenderStates.ZEnable := False;
  Gfx.RenderStates.AlphaTestEnable := False;
  Gfx.RenderStates.AlphaBlendEnable := True;
  RenderModes.FilteringPoint();
  Gfx.Transforms.PopW;
  if Wireframe then
  Gfx.RenderStates.FillMode := D3DFILL_SOLID;
end;

procedure TMyApp.RenderWaypoints;
  var MaxDist: Single;
  var i, j, x, y, z, xmin, xmax, ymin, ymax, zmin, zmax: Integer;
  var VPos: TG2Vec3;
  var m: TG2Mat;
begin
  Gfx.RenderStates.ZEnable := True;
  m.SetIdentity;
  Gfx.Transforms.W[0] := m;
  Gfx.Transforms.ApplyW(0);
  Gfx.Transforms.V := Cam.View;
  Gfx.Transforms.ApplyV;
  Gfx.Transforms.P := Cam.Proj;
  Gfx.Transforms.ApplyP;
  VPos := Gfx.Transforms.Vpos;
  MaxDist := 30;
  Waypoints.GetWaypointBlock(Gfx.Transforms.Vpos, x, y, z);
  xmin := x - Round(MaxDist / Waypoints.CellSizeX); if xmin < 0 then xmin := 0;
  xmax := x + Round(MaxDist / Waypoints.CellSizeX); if xmax > Waypoints.TableSizeX - 1 then xmax := Waypoints.TableSizeX - 1;
  ymin := y - Round(MaxDist / Waypoints.CellSizeY); if ymin < 0 then ymin := 0;
  ymax := y + Round(MaxDist / Waypoints.CellSizeY); if ymax > Waypoints.TableSizeY - 1 then ymax := Waypoints.TableSizeY - 1;
  zmin := z - Round(MaxDist / Waypoints.CellSizeZ); if zmin < 0 then zmin := 0;
  zmax := z + Round(MaxDist / Waypoints.CellSizeZ); if zmax > Waypoints.TableSizeZ - 1 then zmax := Waypoints.TableSizeZ - 1;
  for x := xmin to xmax do
  for y := ymin to ymax do
  for z := zmin to zmax do
  for i := 0 to Waypoints.Table[x, y, z].Waypoints.Count - 1 do
  if (PWaypoint(Waypoints.Table[x, y, z].Waypoints[i])^.Pos - VPos).Len < MaxDist then
  begin
    Prim3D.DrawSphere(
      PWaypoint(Waypoints.Table[x, y, z].Waypoints[i])^.Pos.x,
      PWaypoint(Waypoints.Table[x, y, z].Waypoints[i])^.Pos.y,
      PWaypoint(Waypoints.Table[x, y, z].Waypoints[i])^.Pos.z,
      0.1, $ffff0000, 3, 3
    );
    for j := 0 to PWaypoint(Waypoints.Table[x, y, z].Waypoints[i])^.Links.Count - 1 do
    Prim3D.DrawLine(
      PWaypoint(Waypoints.Table[x, y, z].Waypoints[i])^.Pos,
      PWaypoint(PWaypoint(Waypoints.Table[x, y, z].Waypoints[i])^.Links[j])^.Pos,
      $ff0000ff
    );
  end;
  Gfx.RenderStates.ZEnable := False;
end;

procedure TMyApp.RenderIntro;
  var Scale: Single;
begin
  Render.Clear(False, False, True, $ff000000);
  Scale := Gfx.Params.Height / 512;
  RenderModes.FilteringLinear();
  Render2D.DrawRect(
    (Gfx.Params.Width - 512) div 2, (Gfx.Params.Height - 512) div 2,
    512, 512, $ffffffff, TexLogoIGDC, 0, Scale, Scale, False, False
  );
  RenderModes.FilteringPoint();
end;

procedure TMyApp.RenderMenu;
  var v: TG2Vec2;
begin
  Render2D.DrawRect(
    0, 0, Gfx.Params.Width, Gfx.Params.Height,
    Rect(0, 0, Gfx.Params.Width, Gfx.Params.Height),
    TexMenuBG
  );
  Render.TextureClear();
  Prim2D.DrawRect(
    0, 0, Gfx.Params.Width, Gfx.Params.Height, $80000000
  );
  Render2D.DrawRect((Gfx.Params.Width - 512) div 2, 0, TexTitle);
  Menu.Render;
  Render.TextureClear();
  v := PlugInput.MousePos;
  Prim2D.DrawTriangle(
    v, v + G2Vec2(20, 15), v + G2Vec2(5, 22),
    $ffaaaaaa
  );
end;

procedure TMyApp.RenderGame;
  var m: TG2Mat;
  var s: Single;
  var TimerStr: AnsiString;
  var TimeSeconds, TimeMinutes: Integer;
begin
  Gfx.Transforms.V := Cam.View;
  Gfx.Transforms.ApplyV;
  Gfx.Transforms.P := Cam.Proj;
  Gfx.Transforms.ApplyP;
  m.SetIdentity;
  Gfx.Transforms.W[0] := m;
  Gfx.Transforms.ApplyW(0);

  //RenderSceneDebug;
  RenderScene;

  Gfx.RenderStates.ZEnable := True;
  m.SetIdentity;
  Gfx.Transforms.W[0] := m;
  Gfx.Transforms.ApplyW(0);
  Gfx.Transforms.V := Cam.View;
  Gfx.Transforms.ApplyV;
  Gfx.Transforms.P := Cam.Proj;
  Gfx.Transforms.ApplyP;
  RenderModes.FilteringLinear();
  Gfx.RenderStates.ZWriteEnable := False;
  Particles.Render;
  Gfx.RenderStates.ZWriteEnable := True;
  RenderModes.FilteringPoint();
  Gfx.RenderStates.ZEnable := False;

  //RenderWaypoints;

  Player.RenderGun;

  if not Player.Dead then
  RenderCrossair(Player.Accuracy);

  if AppTime - TimeStarted < TimeToHardcore then
  begin
    TimeSeconds := (TimeToHardcore - (AppTime - TimeStarted)) div 1000;
    TimeMinutes := TimeSeconds div 60;
    TimeSeconds := TimeSeconds - TimeMinutes * 60;
    if TimeSeconds > 9 then
    TimerStr := IntToStr(TimeSeconds)
    else
    TimerStr := '0' + IntToStr(TimeSeconds);
    TimerStr := IntToStr(TimeMinutes) + ':' + TimerStr;
    Font2.Print(Gfx.Params.Width - Font2.GetTextWidth(TimerStr) - 4, 4, $ffff0000, TimerStr);
  end;

  Font2.Print(4, 4, $ffff0000, 'Score: ' + IntToStr(Score));

  {Font1.Print(10, 10, $ff000000, AnsiString('FPS: ' + IntToStr(Tmr.FPS)));
  Font1.Print(10, 30, $ff000000, AnsiString('ObjectsCulled: ' + IntToStr(Query.Stats.ObjectsCulled)));
  Font1.Print(10, 50, $ff000000, AnsiString('ObjectsRendered: ' + IntToStr(Query.Stats.ObjectsRendered)));
  Font1.Print(10, 70, $ff000000, AnsiString('OcTreeDepth: ' + IntToStr(Scene.Depth)));
  Font1.Print(10, 90, $ff000000, AnsiString('ZombieCount: ' + IntToStr(TMob.ZombieCount)));
  Font1.Print(10, 110, $ff000000, AnsiString('LightTraces: ' + IntToStr(TLightTracing.TotalLightTraces)));  }
end;

procedure TMyApp.RenderGameOver;
  var s: AnsiString;
begin
  Render2D.DrawRect(
    0, 0, Gfx.Params.Width, Gfx.Params.Height,
    Rect(0, 0, Gfx.Params.Width, Gfx.Params.Height),
    TexMenuBG
  );
  Render2D.DrawRect((Gfx.Params.Width - 512) div 2, 0, TexGameOver);
  s := 'Your score: ' + IntToStr(Score);
  Font2.Print((Gfx.Params.Width - Font2.GetTextWidth(s)) div 2, 320, $ffff0000, s);
end;

procedure TMyApp.UpdateIntro;
begin
  if AppTime - TimeIntroStart > 4000 then
  GameState := gsMenu;
end;

procedure TMyApp.UpdateMenu;
begin

end;

procedure TMyApp.UpdateGame;
  var s: TG2Sphere;
  var i, j: Integer;
begin
  if not TMob.HardcoreMode
  and (AppTime - TimeStarted >= TimeToHardcore) then
  begin
    TMob.HardcoreMode := True;
  end;
  if ((AppTime - ZombieTimeSpawn) > 2000)
  and (TMob.ZombieCount < 40) then
  SpawnZombie;
  if TMob.RagdollCount > 0 then
  PhysUpdater.IncreaseUpdates;
  UpdatePlayer;
  for i := 0 to ExtraItems.Count - 1 do
  TExtraItem(ExtraItems[i]).Update;
  for i := 0 to Scene.Chars.Count - 1 do
  if TG2SGChar(Scene.Chars[i]).Collide then
  begin
    TG2SGChar(Scene.Chars[i]).Vel := TG2SGChar(Scene.Chars[i]).Vel + Gravity;
    for j := i + 1 to Scene.Chars.Count - 1 do
    if TG2SGChar(Scene.Chars[j]).Collide then
    CollideChars(TG2SGChar(Scene.Chars[i]), TG2SGChar(Scene.Chars[j]));
  end;
  Scene.Update;
  Cam.MoveTo(Player.Transform.GetTranslation - Player.Step + G2Vec3(0, Player.Radius * 0.5, 0));
  Particles.Update;
  if TimeToEndGame then
  GameEnd;
end;

procedure TMyApp.UpdateGameOver;
begin

end;

procedure TMyApp.UpdatePlayer;
  var CamMoveVec: TG2Vec3;
begin
  Cam.UpdateDir := not Player.Dead;
  Cam.UpdatePos := Player.Grounded and not Player.Dead;
  Cam.Update;
  CamMoveVec := Cam.Pos^ - (Player.Transform.GetTranslation - Player.Step + G2Vec3(0, Player.Radius * 0.5, 0));
  Player.Vel := Player.Vel + CamMoveVec.Normalized * 0.02;
end;

procedure TMyApp.GameBegin;
  var v: TG2Vec3;
  var i, j: Integer;
  var ParamArr: TStrArr;
  var NodeProps: PNodeExtraData;
  var TmpTex0, TmpTex1: TG2Texture2D;
  var LevelData: Pointer;
  var LevelDataSize: DWord;
  const TexNoMip: array[0..4] of AnsiString = (
    'Leaves.png',
    'Fence02.png',
    'Grass03D.png',
    'Cage01.png',
    'Cage02.png'
  );
begin
  Tmr.Enabled := False;
  TMob.HardcoreMode := False;
  Scene := TG2SceneGraph.Create(g2);
  Query := Scene.QueryCreate;
  Query^.DistanceSortEnable := True;
  Query^.OcclusionCullQuality := cqLow;
  Query^.OcclusionCullEnable := False;
  RenderLoading('Loading level');
  Scene.SearchPathAdd('LightMaps');
  Scene.SearchPathAdd('Textures');
  g2.PackLinker.GetFileData('Levels', 'Level01', LevelData, LevelDataSize);
  Scene.LoadG2M(LevelData, LevelDataSize);//('Level01.g2m');
  LightTracer := TLightTracing.Create;
  LightTracer.Resume;
  PhysUpdater := TPhysUpdateThread.Create;
  PhysUpdater.Resume;
  for i := 0 to Scene.Geoms.Count - 1 do
  begin
    ParamArr := StrExplode(TG2SGGeom(Scene.Geoms[i]).Name, '_');
    for j := 0 to High(ParamArr) do
    begin
      if ParamArr[j] = '[r]' then
      TG2SGGeom(Scene.Geoms[i]).Render := True
      else if ParamArr[j] = '[c]' then
      TG2SGGeom(Scene.Geoms[i]).Collide := True
      else if ParamArr[j] = 'Leaves' then
      TG2SGGeom(Scene.Geoms[i]).Occluder := False
      else if ParamArr[j] = 'Grass' then
      TG2SGGeom(Scene.Geoms[i]).Occluder := False
      else if ParamArr[j] = '[tm]' then
      begin
        New(NodeProps);
        NodeProps^.ObjectType := otIrrelevant;
        NodeProps^.TexMove := True;
        TG2SGGeom(Scene.Geoms[i]).UserData := NodeProps;
      end;
    end;
  end;
  for i := 0 to Scene.Nodes.Count - 1 do
  begin
    ParamArr := StrExplode(TG2SGNode(Scene.Nodes[i]).Name, '_');
    for j := 0 to High(ParamArr) do
    begin
      if ParamArr[j] = 'ZombieSpawn' then
      begin
        SetLength(ZombieSpawn, Length(ZombieSpawn) + 1);
        ZombieSpawn[High(ZombieSpawn)] := TG2SGNode(Scene.Nodes[i]).Transform.GetTranslation;
      end
      else if ParamArr[j] = 'ControlPoint' then
      begin
        SetLength(ControlPoints, Length(ControlPoints) + 1);
        ControlPoints[High(ControlPoints)] := TG2SGNode(Scene.Nodes[i]).Transform.GetTranslation;
      end
      else if ParamArr[j] = 'ItemArmor' then
      begin
        TArmor.Create(TG2SGNode(Scene.Nodes[i]).Transform.GetTranslation);
      end
      else if ParamArr[j] = 'ItemAmmo1' then
      begin
        TAmmo1.Create(TG2SGNode(Scene.Nodes[i]).Transform.GetTranslation);
      end;
    end;
  end;
  TmpTex0 := TG2Texture2D.Create;
  TmpTex0.Initialize(g2);
  for i := 0 to High(TexNoMip) do
  begin
    TmpTex1 := TG2Texture2D(Scene.MgrTextures.FindTexture(TexNoMip[i]));
    TmpTex0.LoadFromTexture2D(TmpTex1, 1, TmpTex1.Format);
    TmpTex1.LoadFromTexture2D(TmpTex0, 1, TmpTex1.Format);
  end;
  TmpTex0.Finalize;
  TmpTex0.Free;
  v := Scene.FindNode('Player_01').Transform.GetTranslation;
  Cam.SetView(v.x, v.y, v.z, v.x, v.y, v.z - 1, 0, 1, 0);
  Cam.Speed := 0.1;
  Cam.Sensitivity := 0.2;
  Phys.Init;

  RenderLoading('Generating Waypoints');
  Waypoints.Initialize(Scene);
  if FileExists('Level01.wpt') then
  Waypoints.LoadWaypoints('Level01.wpt')
  else
  begin
    g2.PackLinker.GetFileData('Levels', 'Level01WPT', LevelData, LevelDataSize);
    if Assigned(LevelData) then
    Waypoints.LoadWaypoints(LevelData, LevelDataSize)
    else
    Waypoints.GenerateWaypoints(ZombieSpawn[0]);
  end;

  RenderLoading('Initializing');
  MeshPlayerInst := MeshPlayer.InstanceCreate;
  LoadTextures(MeshPlayerInst);
  Scene.MgrCharMeshes.CreateMeshFromPack('Zombie', 'Meshes', 'Zombie');
  TMob.InitTargetGeoms;
  for i := 0 to High(TMob.TargetGeoms) do
  Scene.MgrCharMeshes.FindMesh('Zombie').Geoms[TMob.TargetGeoms[i].GeomID].Visible := False;

  Player := TPlayer.Create(Scene);

  ZombieTimeSpawn := AppTime;
  SpawnZombie;

  TimeStarted := App.AppTime;
  TimeToHardcore := 60000 * 3;

  Score := 0;

  TimeToEndGame := False;
  GameLoaded := True;
  GameState := gsGame;
  Music.Volume := 0.9;
  Tmr.Enabled := True;
end;

procedure TMyApp.GameEnd;
  var i: Integer;
begin
  Tmr.Enabled := False;
  Music.Volume := 1;
  ZombieSpawn := nil;
  ControlPoints := nil;
  PhysUpdater.Terminate;
  PhysUpdater.WaitFor;
  PhysUpdater.Free;
  LightTracer.Terminate;
  LightTracer.WaitFor;
  LightTracer.Free;
  while ExtraItems.Count > 0 do
  TExtraItem(ExtraItems[0]).Free;
  for i := 0 to Scene.Geoms.Count - 1 do
  if TG2SGGeom(Scene.Geoms[i]).UserData <> nil then
  Dispose(PNodeExtraData(TG2SGGeom(Scene.Geoms[i]).UserData));
  Waypoints.Finalize;
  Waypoints.Table := nil;
  MeshPlayerInst.Free;
  Scene.QueryDestroy(Query);
  Scene.Free;
  Phys.UnInit;
  GameLoaded := False;
  GameState := gsGameOver;
  Tmr.Enabled := True;
end;

procedure TMyApp.LoadTextures(const MeshInst: TG2MeshInst);
  var i: Integer;
begin
  for i := 0 to MeshInst.Mesh.MaterialCount - 1 do
  begin
    MeshInst.Materials[i].MapDiffuse := Scene.FindTextureDiffuse(WideString(MeshInst.Mesh.Materials[i].DiffuseMap));
    MeshInst.Materials[i].MapSpecular := Scene.FindTextureSpecular(WideString(MeshInst.Mesh.Materials[i].SpecularMap));
    MeshInst.Materials[i].MapNormals := Scene.FindTextureNormals(WideString(MeshInst.Mesh.Materials[i].NormalMap));
  end;
end;

procedure TMyApp.RenderCrossair(const Radius: Single);
  var x, y: Single;
  const Col = $ffffffff;
begin
  x := Gfx.Params.Width * 0.5;
  y := Gfx.Params.Height * 0.5;
  Render.TextureClear();
  Gfx.RenderStates.SrcBlend := D3DBLEND_INVDESTCOLOR;
  Gfx.RenderStates.DestBlend := D3DBLEND_INVSRCCOLOR;
  Prim2D.DrawRect(x - 16 - Radius, y - 1, 16, 2, Col);
  Prim2D.DrawRect(x + Radius, y - 1, 16, 2, Col);
  Prim2D.DrawRect(x - 1, y - 16 - Radius, 2, 16, Col);
  Prim2D.DrawRect(x - 1, y + Radius, 2, 16, Col);
  RenderModes.BlendModeNormal;
end;

procedure TMyApp.RenderLoading(const LoadMessage: AnsiString = 'Loading...');
begin
  Render.RenderStart;
  Render.Clear(False, False, True, $ff000000);
  if TexMenuBG <> nil then
  Render2D.DrawRect(
    0, 0, Gfx.Params.Width, Gfx.Params.Height,
    Rect(0, 0, Gfx.Params.Width, Gfx.Params.Height),
    TexMenuBG
  );
  Font1.Print(
    Gfx.Params.Width - Font1.GetTextWidth(LoadMessage) - 8,
    Gfx.Params.Height - Font1.GetTextHeight(LoadMessage) - 8,
    $ffffffff, LoadMessage
  );
  Render.RenderStop;
  Render.Present;
end;

procedure TMyApp.CollideChars(const Char0, Char1: TG2SGChar);
  var Char0L, Char0H, Char1L, Char1H: Single;
  var Char0Pos, Char1Pos, Dir, MidPos: TG2Vec2;
begin
  Char0L := Char0.Transform.e31;
  Char1H := Char1.Transform.e31 + Char1.Step.Len + Char1.Radius;
  if Char0L > Char1H then Exit;
  Char1L := Char1.Transform.e31;
  Char0H := Char0.Transform.e31 + Char0.Step.Len + Char0.Radius;
  if Char1L > Char0H then Exit;
  Char0Pos.SetValue(Char0.Transform.e30, Char0.Transform.e32);
  Char1Pos.SetValue(Char1.Transform.e30, Char1.Transform.e32);
  Dir := Char1Pos - Char0Pos;
  if Dir.Len > Char0.Radius + Char1.Radius then Exit;
  Dir.Normalize;
  MidPos := (Char0Pos + Char1Pos) * 0.5;
  Char0.Transform.e30 := MidPos.x - Dir.x * Char0.Radius;
  Char0.Transform.e32 := MidPos.y - Dir.y * Char0.Radius;
  Char1.Transform.e30 := MidPos.x + Dir.x * Char1.Radius;
  Char1.Transform.e32 := MidPos.y + Dir.y * Char1.Radius;
end;

procedure TMyApp.SpawnZombie;
  var Zombie: TMob;
begin
  //ZombieTimeSpawn := AppTime;
  //Exit;
  Zombie := TMob.Create(Scene);
  Zombie.Transform.SetTranslation(ZombieSpawn[Random(Length(ZombieSpawn))]);
  ZombieTimeSpawn := AppTime;
end;

procedure TMyApp.MenuBtnQuit;
begin
  Running := False;
end;
//TMyApp END

//TMob BEGIN
class procedure TMob.InitTargetGeoms;
  var i: Integer;
  var Mesh: TG2Mesh;
  var ParamArr: TStrArr;
begin
  TargetGeoms := nil;
  Mesh := App.Scene.MgrCharMeshes.FindMesh('Zombie');
  for i := 0 to Mesh.GeomCount - 1 do
  begin
    ParamArr := StrExplode(Mesh.Nodes[Mesh.Geoms[i].NodeID].Name, '_');
    if ParamArr[0] = 'Target' then
    begin
      SetLength(TargetGeoms, Length(TargetGeoms) + 1);
      TargetGeoms[High(TargetGeoms)].GeomID := i;
      if ParamArr[1] = 'Head' then
      TargetGeoms[High(TargetGeoms)].DamageMod := 5
      else if ParamArr[1] = 'Arm' then
      TargetGeoms[High(TargetGeoms)].DamageMod := 0.4
      else if ParamArr[1] = 'Leg' then
      TargetGeoms[High(TargetGeoms)].DamageMod := 0.6
      else
      TargetGeoms[High(TargetGeoms)].DamageMod := 1;
    end;
  end;
end;

constructor TMob.Create(const SceneGraph: TG2SceneGraph);
  var v2: TG2Vec2;
  var i: Integer;
begin
  inherited Create(SceneGraph);
  State := msIdle;
  Health := 100;
  Anim := 'Idle';
  AnimSpeed := 5000;
  AnimSeed := Random(50000);
  Speed := 0.005;
  Radius := 1.2;
  Step.SetValue(0, -2, 0);
  Scale := 0.15;
  Dir := G2RandomCirclePoint;
  Collide := True;
  Render := False;
  ResetWayPoint := True;
  Killed := False;
  Ragdoll := nil;
  WaypointReachTime := App.AppTime;
  Mesh := App.Scene.MgrCharMeshes.FindMesh('Zombie');
  MeshInst.FrameSetFast('Idle', 0);
  TimeAI := App.AppTime + Random(1000);
  TimeBeenShot := App.AppTime - 5000;
  TimeLastHit := App.AppTime;
  CurWaypoint := -1;
  Inc(ZombieCount);
end;

destructor TMob.Destroy;
begin
  if Ragdoll <> nil then
  begin
    Ragdoll.Free;
    Dec(RagdollCount);
  end;
  inherited Destroy;
  Dec(ZombieCount);
end;

procedure TMob.Update;
  var AnimInd: Integer;
  var Ang: Single;
  var Pos, PathTarget: TG2Vec3;
  var PlayerDir: TG2Vec3;
  var r: TG2Ray;
  var NewDir: TG2Vec3;
  var x, y, z, MinX, MinY, MinZ, MaxX, MaxY, MaxZ, i: Integer;
  var GeomID: Integer;
  var FaceID: Word;
  var U, V, D: Single;
begin
  inherited Update;
  if not Killed then
  begin
    Pos := Transform.GetTranslation - Step;
    NewDir.SetValue(Dir.x, 0, Dir.y);
    if App.AppTime - TimeAI > 1000 then
    begin
      TimeAI := App.AppTime;
      r.Origin := Pos;
      PlayerDir := (App.Player.Transform.GetTranslation - App.Player.Step) - r.Origin;
      r.Dir := PlayerDir.Normalized;
      if App.AppTime - TimeBeenShot > 5000 then
      begin
        if HardcoreMode
        or (
          (NewDir.Dot(r.Dir) > 0.6)
          and (PlayerDir.Len < 30)
        ) then
        begin
          if App.Scene.CollideRay(r, GeomID, FaceID, U, V, D) then
          begin
            if D > PlayerDir.Len then
            State := msAttack
            else
            State := msIdle;
          end
          else
          State := msAttack;
        end
        else
        State := msIdle;
      end
      else
      State := msAttack;
    end;
    if not Grounded then State := msIdle;
    case State of
      msIdle:
      begin
        if (App.AppTime - WaypointReachTime) > 5000 then
        ResetWayPoint := True;
        if ResetWayPoint or ((Pos - NextWayPoint).Len < 3) then
        begin
          WaypointReachTime := App.AppTime;
          ResetWayPoint := False;
          if HardcoreMode then
          PathTarget := App.Player.Transform.GetTranslation - App.Player.Step
          else
          PathTarget := App.ControlPoints[Random(Length(App.ControlPoints))];
          if (CurWaypoint = -1)
          and (App.Waypoints.FindPath(Pos, PathTarget, Waypoints)) then
          begin
            if Waypoints.Count > 0 then
            begin
              CurWaypoint := 0;
              NextWayPoint := PWaypoint(Waypoints[CurWaypoint])^.Pos;
            end
            else
            CurWaypoint := -1;
          end
          else
          if (CurWaypoint > -1) then
          begin
            Inc(CurWaypoint);
            if CurWaypoint >= Waypoints.Count then
            CurWaypoint := -1
            else
            NextWayPoint := PWaypoint(Waypoints[CurWaypoint])^.Pos;
          end;
          if CurWaypoint = -1 then
          begin
            App.Waypoints.GetWaypointBlock(Pos, x, y, z);
            MinX := x - 1; MinY := y - 1; MinZ := z - 1; MaxX := x + 1; MaxY := y + 1; MaxZ := z + 1;
            if MinX < 0 then MinX := 0; if MinY < 0 then MinY := 0; if MinZ < 0 then MinZ := 0;
            if MaxX > App.Waypoints.TableSizeX - 1 then MaxX := App.Waypoints.TableSizeX - 1;
            if MaxY > App.Waypoints.TableSizeY - 1 then MaxY := App.Waypoints.TableSizeY - 1;
            if MaxZ > App.Waypoints.TableSizeZ - 1 then MaxZ := App.Waypoints.TableSizeZ - 1;
            WaypointList0.Clear;
            for x := MinX to MaxX do
            for y := MinY to MaxY do
            for z := MinZ to MaxZ do
            for i := 0 to App.Waypoints.Table[x, y, z].Waypoints.Count - 1 do
            WaypointList0.Add(
              PWaypoint(App.Waypoints.Table[x, y, z].Waypoints[i]),
              (PWaypoint(App.Waypoints.Table[x, y, z].Waypoints[i])^.Pos - Pos).Len
            );
            if WaypointList0.Count > 0 then
            begin
              WaypointList1.Clear;
              for i := 0 to PWaypoint(WaypointList0[0]).Links.Count - 1 do
              WaypointList1.Add(
                PWaypoint(WaypointList0[0]).Links[i],
                (PWaypoint(PWaypoint(WaypointList0[0]).Links[i])^.Pos - Pos).Normalized.Dot(NewDir) + (Random(101) - 50) * 0.01
              );
              NextWaypoint := PWaypoint(WaypointList1[WaypointList1.Count - 1])^.Pos;
            end
            else
            NextWayPoint := Pos;
          end;
        end;
        NewDir := (NextWaypoint - Pos);
        if NewDir.Len < 1 then
        begin
          Anim := 'Idle';
          AnimSpeed := 3000;
        end
        else
        begin
          if HardcoreMode then
          begin
            Anim := 'Run';
            AnimSpeed := 500;
            Dir := G2LerpVec2(Dir, G2Vec2(NewDir.x, NewDir.z), 0.05);
            Dir.Normalize;
            if Grounded then
            Vel := Vel + G2Vec3(Dir.x, 0, Dir.y) * Speed * 5;
          end
          else
          begin
            Anim := 'Walk';
            AnimSpeed := 1000;
            Dir := G2LerpVec2(Dir, G2Vec2(NewDir.x, NewDir.z), 0.002);
            Dir.Normalize;
            if Grounded then
            Vel := Vel + G2Vec3(Dir.x, 0, Dir.y) * Speed;
          end;
        end;
      end;
      msAttack:
      begin
        CurWaypoint := -1;
        NextWaypoint := App.Player.Transform.GetTranslation - App.Player.Step;
        PlayerDir := (NextWaypoint - Pos);
        if HardcoreMode
        or (
          (NewDir.Dot(PlayerDir.Normalized) > 0.6)
          and (PlayerDir.Len < 30)
        ) then
        begin
          if (App.AppTime - TimeLastHit > 1000)
          and (PlayerDir.Len < 3) then
          begin
            TimeLastHit := App.AppTime;
            App.SndClawStrike[Random(3)].Play;
            App.Player.Hit(30);
          end;
          Anim := 'Run';
          AnimSpeed := 500;
          Dir := G2LerpVec2(Dir, G2Vec2(PlayerDir.x, PlayerDir.z), 0.05);
          Dir.Normalize;
          if Grounded then
          Vel := Vel + G2Vec3(Dir.x, 0, Dir.y) * Speed * 5;
        end
        else
        begin
          Anim := 'Walk';
          AnimSpeed := 1000;
          Dir := G2LerpVec2(Dir, G2Vec2(PlayerDir.x, PlayerDir.z), 0.005);
          Dir.Normalize;
          Vel := Vel + G2Vec3(Dir.x, 0, Dir.y) * Speed;
        end;
      end;
    end;
    Transform.SetScaling(Scale);
    Ang := G2Vec2AngleOY(G2Vec2(0, 0), Dir) + Pi;
    Transform.RotateY(Ang);
    Transform.Translate(Pos + Step);
    AnimInd := Mesh.AnimIndex(Anim);
    if AnimInd > -1 then
    MeshInst.FrameSet(Anim, G2TimeInterval(AnimSpeed, App.AppTime + AnimSeed) * Mesh.Anims[AnimInd].FrameCount);
  end
  else
  begin
    if ((App.AppTime - TimeKilled) > 3000)
    and (Ragdoll <> nil) then
    begin
      Ragdoll.Free;
      Ragdoll := nil;
      Dec(RagdollCount);
    end;
    if Ragdoll <> nil then
    Ragdoll.Apply;
    Transform.SetIdentity;
    if (App.AppTime - TimeKilled) > 10000 then
    begin
      Free;
      Exit;
    end;
  end;
  if not Render then Render := True;
end;

procedure TMob.Kill;
  var m: TG2Mat;
begin
  if Killed then Exit;
  if TMob.HardcoreMode then
  App.Score := App.Score + 2
  else
  App.Score := App.Score + 1;
  if App.AppTime - App.TimeStarted < App.TimeToHardcore then
  App.TimeToHardcore := App.TimeToHardcore + 5000;
  m.SetRotationY(G2Vec2AngleOY(G2Vec2(0, 0), Dir) + Pi);
  m.Translate(Transform.GetTranslation);
  Collide := False;
  Killed := True;
  Ragdoll := TG2MeshRagdoll.Create(Phys.World, MeshInst, ForceAndTorqueCallback, Scale);
  Ragdoll.Load;
  Ragdoll.Transform(m);
  Ragdoll.Apply;
  TimeKilled := App.AppTime;
  Transform.SetIdentity;
  Inc(RagdollCount);
end;
//TMob END

begin
  App := TMyApp.Create;       
  App.Initialize;
  if App.g2.Initialized then
  try
    App.Loop;
  finally
    App.Free;
  end;
  ExitCode := 0;
end.
