program Project75;

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

{$R *.res}

type
  TCharacter = class;

  TExtraGeom = record
  public
    MeshInst: TG2MeshInst;
    Transform: TG2Mat;
    Shadow: Boolean;
  end;
  PExtraGeom = ^TExtraGeom;

  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; const Ang: Single = 0);
    procedure Update; override;
    procedure OnUpdate; virtual;
  end;

  TParticleFlame = class (TParticleFixed)
  public
    r: Single;
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TFlare = class (TParticle)
  public
    Vel: TG2Vec3;
    Acc: TG2Vec3;
    Dump: Single;
    ScaleFirst: Single;
    ScaleLast: Single;
    FadeIn: Boolean;
    FadeOut: Boolean;
    constructor Create; override;
    procedure OnUpdate; override;
  end;

  TShot = class (TParticleAxis)
  public
    var Vel: TG2Vec3;
    var Pos: TG2Vec3;
    var Dir: TG2Vec3;
    var Len: Single;
    var Damage: Integer;
    var Force: Single;
    var Piercing: Boolean;
    constructor Create; override;
    procedure OnUpdate; override;
    procedure OnHitWall(const HitPos, HitNorm: TG2Vec3); virtual;
    procedure OnHitChar(const HitChar: TCharacter); virtual;
  end;

  TPlasmaShot = class (TShot)
  private
    LastFlare: DWord;
  public
    constructor Create; override;
    procedure OnUpdate; override;
    procedure OnHitWall(const HitPos, HitNorm: TG2Vec3); override;
    procedure OnHitChar(const HitChar: TCharacter); override;
  end;

  TMissile = class (TShot)
  private
    Geom: TExtraGeom;
    MR: TG2Mat;
    MP: TG2Mat;
  public
    constructor Create; override;
    destructor Destroy; override;
    procedure SetTransform;
    procedure OnUpdate; override;
    procedure OnHitWall(const HitPos, HitNorm: TG2Vec3); override;
    procedure OnHitChar(const HitChar: TCharacter); override;
  end;

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

  TParticleMuzzleFlash = class (TParticleAxis)
  public
    var Dir: TG2Vec3;
    var Len: Single;
    var RefPos: PG2Vec3;
    var RefDir: PG2Vec3;
    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;

  TParticleBillboard = class (TParticle)
  public
    var Vel: TG2Vec3;
    var VelAng: Single;
    var Dump: Single;
    var DumpAng: Single;
    var ScaleFirst: Single;
    var ScaleLast: Single;
    var FadeIn: Boolean;
    var FadeOut: Boolean;
    constructor Create; override;
    procedure OnUpdate; override;
  end;

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

  TKillShot = record
  public
    Pos: TG2Vec3;
    Dir: TG2Vec3;
    Force: Single;
  end;

  TCharacter = class (TG2SGChar)
  protected
    Scale: Single;
    Dieing: Boolean;
    Dead: Boolean;
    DeadTime: DWord;
    Ragdoll: TG2MeshRagdoll;
    KillTime: DWord;
    Health: Integer;
    KillShotCount: Integer;
    KillShots: array[0..31] of TKillShot;
    procedure MakeDust(const NodeName: AnsiString; const CurFrame, MinFrame, MaxFrame: Single); overload;
    procedure MakeDust(const NodeName: AnsiString; const Amount: Integer); overload;
  public
    var LightAmount: Single;
    procedure Update; override;
    procedure Kill; virtual;
    procedure SetTransforms(const Rag: Boolean = False); virtual; abstract;
    procedure AddKillShot(const Pos, Dir: TG2Vec3; const Force: Single);
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; override;
  end;

  TPlayerWeapon = record
  public
    Name: AnsiString;
    MFID: Integer;
    ShotLag: DWord;
    Damage: Integer;
    ShotCount: Integer;
    AimAnim: DWord;
    Spread: Single;
    Force: Single;
    Ammo: Integer;
    ReloadTime: DWord;
    Cost: Integer;
  end;

  TGrenade = class (TG2GroupItem)
  public
    Geom: TExtraGeom;
    Body: TNewtonBody;
    TimeCreated: DWord;
    constructor Create(const Group: TG2Group); override;
    destructor Destroy; override;
    procedure Init(const Pos: TG2Vec3; const Vel: TG2Vec3);
    procedure Update; override;
  end;

  TMedkit = class (TG2GroupItem)
  public
    Geom: TExtraGeom;
    PosW: TG2Vec3;
    Rotation: Single;
    constructor Create(const Group: TG2Group); override;
    destructor Destroy; override;
    procedure Init(const Pos: TG2Vec3);
    procedure Update; override;
  end;

  TGrenBox = class (TG2GroupItem)
  public
    Geom: TExtraGeom;
    PosW: TG2Vec3;
    Rotation: Single;
    constructor Create(const Group: TG2Group); override;
    destructor Destroy; override;
    procedure Init(const Pos: TG2Vec3);
    procedure Update; override;
  end;

  TPlayer = class (TCharacter)
  private
    Anim: AnsiString;
    AnimFrame: Single;
    AnimFrameCount: Integer;
    AnimSpeed: Single;
    MoveLeft, MoveRight, MoveUp, MoveDown: Boolean;
    LookAt: TG2Vec3;
    AngLook, AngMove, AngLegs: Single;
    BoneGroupLegs: TG2MeshBoneGroup;
    BoneGroupTorso: TG2MeshBoneGroup;
    BoneGroupPelvis: TG2MeshBoneGroup;
    TorsoNodeID: Integer;
    TimeLastShot: DWord;
    TimeLastGren: DWord;
    MFPos: TG2Vec3;
    MFDir: TG2Vec3;
    CurWeaponID: Integer;
    WpnSlot: Integer;
    ThrowingGrenade: Boolean;
    GrenadeCount: Integer;
    Reloading: Boolean;
    ReloadTime: DWord;
    SlotAmmo: array[0..7] of Integer;
    Ammo: Integer;
    Healing: Boolean;
    HealTime: DWord;
    HealLength: DWord;
    MedKitCount: Integer;
    procedure SetWeapon(const ID: Integer);
    procedure SetAnim(const AnimName: AnsiString);
    procedure CreateMuzzleFlash;
    procedure CreateShot;
    procedure ThrowGrenade;
    procedure Reload;
    procedure Heal;
    procedure SwapSlots;
  public
    procedure Update; override;
    procedure SetTransforms(const Rag: Boolean = False); override;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; override;
  end;

  TMonster = class (TCharacter)
  private
    Ang: Single;
    Anim: AnsiString;
    AnimFrame: Single;
    AnimFrameCount: Integer;
    AnimSpeed: Single;
    procedure SetAnimSpeed(const AnimName: AnsiString); virtual; abstract;
    procedure SetAnim(const AnimName: AnsiString);
  protected
    MonsterReward: Integer;
  public
    class var NumMonsters: Integer;
    procedure Kill; override;
    procedure Update; override;
    procedure SetTransforms(const Rag: Boolean = False); override;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; override;
  end;

  TZombie = class (TMonster)
  private
    TimeDust: DWord;
    Attacking: Boolean;
    AttackHit: Boolean;
    LastAttack: DWord;
    procedure SetAnimSpeed(const AnimName: AnsiString); override;
  public
    procedure Update; override;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; override;
  end;

  TJason = class (TMonster)
  private
    Attacking: Boolean;
    AttackHit: Boolean;
    LastAttack: DWord;
    procedure SetAnimSpeed(const AnimName: AnsiString); override;
  public
    procedure Update; override;
    constructor Create(const SceneGraph: TG2SceneGraph); override;
    destructor Destroy; 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;

  TStore = record
  public
    PlayerMoney: Integer;
    PlayerGrenades: Integer;
    PlayerMedkits: Integer;
    PlayerWeapons: array[0..9] of Boolean;
    WeaponTexture: array[0..9] of TG2Texture2D;
    GUI: TG2UI;
    CurPage: Integer;
    RectDone: TRect;
    RectStore: TRect;
    RectInventory: TRect;
    PlayerSlots: array[0..1] of Integer;
    LastMessage: AnsiString;
    MessageTime: DWord;
    Desc: AnsiString;
    procedure SetMessage(const Mes: AnsiString);
    procedure SetDesc(const WeaponID: Integer);
    procedure Init;
    procedure Reset;
    procedure TextOut(const Text: AnsiString; const R: TRect);
    procedure Render;
    procedure Click;
  end;

  TLevelWave = record
  public
    Monsters: array of Integer;
    MonsterCount: Integer;
    CurMonster: Integer;
    SpawnInterval: DWord;
    procedure AddMonsters(const m0, m1, m2, m3, m4, m5, m6, m7, m8, m9: Integer);
    procedure Reset;
  end;

  TGameState = (gsInit, gsLogo, gsMenu, gsGame, gsGameOver);

  TMyApp = class (TG2App)
  public
    GameState: TGameState;
    Font1: TG2Font;
    MgrMeshes: TG2MeshMgr;
    MgrTextures: TG2TextureMgr;
    MgrSurfaces: TG2SurfaceMgr;
    MgrEffects: TG2EffectMgr;
    MgrMusic: TG2MusicMgr;
    MgrSounds: TG2SoundMgr;
    Particles: TG2Particles3D;
    Shaders: TG2Effect;
    ShaderUtils: TG2Effect;
    MeshMissile: TG2Mesh;
    MeshGrenade: TG2Mesh;
    MeshMedkit: TG2Mesh;
    MeshGrenBox: TG2Mesh;
    TexLogo: TG2Texture2D;
    TexArrow: TG2Texture2D;
    TexMuzzleFlash: TG2Texture2D;
    TexPlasmaShot: TG2Texture2D;
    TexPlasmaFlare: TG2Texture2D;
    TexFireFlare: TG2Texture2D;
    TexFlame: TG2Texture2D;
    TexSmoke: TG2Texture2D;
    TexDust: TG2Texture2D;
    TexBlood1: TG2Texture2D;
    TexBlood2: TG2Texture2D;
    TexGrenade: TG2Texture2D;
    TexBullet: TG2Texture2D;
    TexHealthBarBG: TG2Texture2D;
    TexHealthBar: TG2Texture2D;
    TexMedkit: TG2Texture2D;
    TexHeal: TG2Texture2D;
    TexTitle: TG2Texture2D;
    TexGameOver: TG2Texture2D;
    TexMenuBG: TG2Texture2D;
    TexBtnNewGame: TG2Texture2D;
    TexBtnExtras: TG2Texture2D;
    TexBtnQuit: TG2Texture2D;
    TexBtnContinue: TG2Texture2D;
    TexWpnHandgun: TG2Texture2D;
    TexWpnSMG1: TG2Texture2D;
    TexWpnSMG2: TG2Texture2D;
    TexWpnShotgun1: TG2Texture2D;
    TexWpnShotgun2: TG2Texture2D;
    TexWpnPlasma: TG2Texture2D;
    TexWpnRifle: TG2Texture2D;
    TexWpnRocketLauncher: TG2Texture2D;
    TexShadowDepthRT: TG2Texture2DRT;
    SurfShadowDepthDS: TG2SurfaceDS;
    SndHandgun: TG2SoundInst;
    SndSMG1: TG2SoundInst;
    SndSMG2: TG2SoundInst;
    SndShotgun1: TG2SoundInst;
    SndShotgun2: TG2SoundInst;
    SndPlasma: TG2SoundInst;
    SndRifle: TG2SoundInst;
    SndRocketLauncher: TG2SoundInst;
    SndRic: TG2SoundInst;
    SndExpl: TG2SoundInst;
    Music: TG2Music;
    Menu: TMenu;
    InGameMenu: TMenu;
    InGameMenuIsOpen: Boolean;
    Scene: TG2SceneGraph;
    Query: PG2SGQuery;
    UpdateQuery: PG2SGQuery;
    LastSpawn: DWord;
    Player: TPlayer;
    RagdollCount: Integer;
    PhysObjectCount: Integer;
    CharsToFree: TG2QuickList;
    ExtraGeoms: TG2QuickList;
    Medkits: TG2QuickList;
    GrenBoxes: TG2QuickList;
    GroupItems: TG2Group;
    MonsterSpawn: array of TG2Vec3;
    PlayerDead: Boolean;
    PlayerDeadTime: DWord;
    LevelOpen: Boolean;
    Store: TStore;
    StoreIsOpen: Boolean;
    StoreCloseTime: DWord;
    UISkin: TG2UISkin;
    Weapons: array of TPlayerWeapon;
    MonsterWaves: array[0..9] of TLevelWave;
    CurWave: Integer;
    WaveTime: DWord;
    WaveStoreOpened: Boolean;
    WaveFinished: Boolean;
    WaveFinishedTime: DWord;
    MonstersLeft: Integer;
    LastFlameTime: DWord;
    TimeLogo: DWord;
    procedure OnRender; override;
    procedure OnUpdate; override;
    procedure OnKeyDown(const Key: Byte); override;
    procedure OnMouseDown(const Button: Byte); override;
    procedure OnWheelMove(const Shift: Integer); override;
    procedure Initialize; override;
    procedure Finalize; override;
    procedure CollideChars(const Char0, Char1: TG2SGChar);
    procedure CreateBloodSplash(const Pos: TG2Vec3; const Amount: Integer = 8);
    procedure CreateExplosion(const Pos: TG2Vec3; const Damage: Integer; const Force: Single);
    procedure LoadTextures(const MeshInst: TG2MeshInst);
    procedure RenderLoading;
    procedure RenderMenu;
    procedure UpdateMenu;
    procedure RenderGame;
    procedure UpdateGame;
    procedure RenderGameOver;
    procedure UpdateGameOver;
    procedure RenderLogo;
    procedure UpdateLogo;
    procedure RenderCrossair;
    procedure RenderScene;
    procedure LevelBegin;
    procedure LevelEnd;
    procedure InGameMenuOpen;
    procedure InGameMenuClose;
    procedure StoreOpen;
    procedure StoreClose;
    procedure BtnNewGame;
    procedure BtnExtras;
    procedure BtnQuit;
    procedure BtnContinue;
    procedure AddWeapon(
      const Name: AnsiString;
      const MFID: Integer;
      const ShotLag: DWord;
      const Damage: Integer;
      const ShotCount: Integer;
      const AimAnim: DWord;
      const Spread: Single;
      const Force: Single;
      const Ammo: Integer;
      const ReloadTime: DWord;
      const Cost: Integer
    );
  end;

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

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

  TPhys = record
  public
    var World: TNewtonWorld;
    var SceneBody: TNewtonBody;
    var PhysUpdater: TPhysUpdateThread;
    var Changing: Boolean;
    var CanChange: Boolean;
    procedure Init;
    procedure UnInit;
    procedure StartThread;
    procedure StopThread;
    procedure ChangeBegin;
    procedure ChangeEnd;
  end;

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, -80, 0) * Mass;
  NewtonBodyAddForce(Body, @Force);
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; const Ang: Single = 0);
  var dx, dy: TG2Vec3;
  var m: TG2Mat;
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;
  m.SetRotation(DirZ, Ang);
  dx := dx.Transform4x3(m);
  dy := dy.Transform4x3(m);
  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

//TParticleFlame BEGIN
constructor TParticleFlame.Create;
begin
  inherited Create;
  Texture := App.TexFlame;
  BlendMode.SetAdd;
  r := G2Random2Pi;
  LifeLength := 3000;
end;

procedure TParticleFlame.OnUpdate;
  var b: Byte;
begin
  Scale := 8 + t * 12;
  Y := Y - 0.02;
  SetDir(G2Vec3(0, 1, 0), r);
  if t > 0.5 then
  b := Round(((1 - t) / 0.5) * 255)
  else
  b := 255;
  Color := G2Color(b, b, b, 255);
end;
//TPArticleFlame END

//TFlare BEGIN
constructor TFlare.Create;
begin
  inherited Create;
  Vel.SetValue(0, 0, 0);
  Acc.SetValue(0, 0, 0);
  Dump := 1;
  ScaleFirst := 1;
  ScaleLast := 1;
  FadeIn := False;
  FadeOut := False;
end;

procedure TFlare.OnUpdate;
  var b: TG2Color;
begin
  Vel := (Vel + Acc) * Dump;
  X := X + Vel.x;
  Y := Y + Vel.y;
  Z := Z + Vel.z;
  Scale := G2LerpFloat(ScaleFirst, ScaleLast, t);
  if FadeIn and FadeOut then
  b := Round(Sin(t * Pi) * 255)
  else if FadeIn then
  begin
    if t < 0.5 then
    b := Round((t / 0.5) * 255)
    else
    b := 255;
  end
  else if FadeOut then
  begin
    if t > 0.5 then
    b := Round(((1 - t) / 0.5) * 255)
    else
    b := 255;
  end
  else b := 255;
  Color := G2Color(b, b, b);
end;
//TFlare END

//TShot BEGIN
constructor TShot.Create;
begin
  inherited Create;
  Texture := App.TexMuzzleFlash;
  Len := 6;
  Size := 1;
  LifeLength := 5000;
  DistanceSorted := True;
  Piercing := False;
end;

procedure TShot.OnUpdate;
  var GeomID: Integer;
  var FaceID: Word;
  var U, V, D: Single;
  var i, j: Integer;
  var r: TG2Ray;
  var v3: TG2Vec3;
  var v2: TG2Vec2;
  var InSegment: Boolean;
begin
  r.SetValue(Pos - Vel, Dir);
  if App.Scene.CollideRay(r, GeomID, FaceID, U, V, D) then
  begin
    if D < Vel.Len then
    begin
      Die;
      OnHitWall(r.Origin + r.Dir * D, TG2SGGeom(App.Scene.Geoms[GeomID]).Collider.Faces[FaceID].Plane.N);
    end;
  end;
  for i := 0 to App.Scene.Chars.Count - 1 do
  if (TG2SGChar(App.Scene.Chars[i]) is TMonster)
  and not TMonster(App.Scene.Chars[i]).Dieing
  and not TMonster(App.Scene.Chars[i]).Dead then
  begin
    v3 := TG2SGChar(App.Scene.Chars[i]).Transform.GetTranslation;
    if (Pos.y > v3.y) and (Pos.y < v3.y - TG2SGChar(App.Scene.Chars[i]).Step.y + TG2SGChar(App.Scene.Chars[i]).Radius + 3) then
    begin
      G2Vec2ToLine(G2Vec2(Pos.x, Pos.z), G2Vec2(Pos.x + Vel.x, Pos.z + Vel.z), G2Vec2(v3.x, v3.z), v2, InSegment);
      if (InSegment and ((G2Vec2(v3.x, v3.z) - v2).Len < TG2SGChar(App.Scene.Chars[i]).Radius))
      or ((G2Vec2(v3.x, v3.z) - G2Vec2(Pos.x, Pos.z)).Len < TG2SGChar(App.Scene.Chars[i]).Radius) then
      begin
        OnHitChar(TCharacter(App.Scene.Chars[i]));
      end;
    end;
  end;
  Pos := Pos + Vel;
  PosSrc := Pos;
  PosDst := Pos - Dir * Len;
end;

procedure TShot.OnHitWall(const HitPos, HitNorm: TG2Vec3);
  var b: TParticleBounce;
  var i: Integer;
begin
  for i := 0 to 3 do
  begin
    b := TParticleBounce.Create;
    b.PosSrc := HitPos;
    b.Dir := (HitNorm + G2RandomSpherePoint * 0.4).Normalized;
    b.Update;
    App.Particles.AddParticle(b);
  end;
  App.SndRic.SoundPosition := (HitPos - App.Cam.Pos^) * 0.05;
  App.SndRic.Play;
end;

procedure TShot.OnHitChar(const HitChar: TCharacter);
begin
  HitChar.Vel := HitChar.Vel + Vel.Normalized * (Force * 0.005);
  App.CreateBloodSplash(Pos, 8);
  HitChar.Health := HitChar.Health - Damage;
  HitChar.AddKillShot(Pos, Vel.Normalized, Force);
  if not Piercing then
  Die
  else
  Damage := Round(Damage * 0.8);
end;
//TShot END

//TPlasmaShot BEGIN
constructor TPlasmaShot.Create;
begin
  inherited Create;
  Texture := App.TexPlasmaShot;
  Len := 6;
  Size := 4;
  LastFlare := App.AppTime;
end;

procedure TPlasmaShot.OnUpdate;
  var FlareCount, i: Integer;
  var f: TFlare;
  var v: TG2Vec3;
begin
  inherited OnUpdate;
  FlareCount := (App.AppTime - LastFlare) div 10;
  LastFlare := LastFlare + DWord(FlareCount * 10);
  for i := 0 to FlareCount - 1 do
  begin
    f := TFlare.Create;
    f.Texture := App.TexPlasmaFlare;
    v := Pos;
    f.X := v.x;
    f.Y := v.y;
    f.Z := v.z;
    f.ScaleFirst := 6;
    f.ScaleLast := 4;
    f.Vel := G2RandomSpherePoint * 0.1;
    f.Acc := -f.Vel * 0.1;
    f.Rotation := G2Random2Pi;
    f.LifeLength := 500;
    //f.Dump := 0.9;
    f.FadeOut := True;
    App.Particles.AddParticle(f);
  end;
  if Dead then
  for i := 0 to 7 do
  begin
    f := TFlare.Create;
    f.Texture := App.TexPlasmaFlare;
    v := Pos + G2RandomSpherePoint;
    f.X := v.x;
    f.Y := v.y;
    f.Z := v.z;
    f.ScaleFirst := 15;
    f.ScaleLast := 13;
    f.Vel := G2RandomSpherePoint * 0.4;
    f.Rotation := G2Random2Pi;
    f.Dump := 0.9;
    f.FadeOut := True;
    f.LifeLength := 1000;
    App.Particles.AddParticle(f);
  end;
end;

procedure TPlasmaShot.OnHitWall(const HitPos, HitNorm: TG2Vec3);
begin
  Die;
end;

procedure TPlasmaShot.OnHitChar(const HitChar: TCharacter);
begin
  HitChar.Vel := HitChar.Vel + Vel.Normalized * (Force * 0.005);
  HitChar.Health := HitChar.Health - Damage;
  HitChar.AddKillShot(Pos, Vel.Normalized, Force);
  Die;
end;
//TPlasmaShot END

//TMissile BEGIN
constructor TMissile.Create;
begin
  inherited Create;
  Geom.MeshInst := App.MeshMissile.InstanceCreate;
  App.LoadTextures(Geom.MeshInst);
  Geom.Shadow := False;
  App.ExtraGeoms.Add(@Geom);
  Len := 6;
  Size := 4;
end;

destructor TMissile.Destroy;
begin
  App.ExtraGeoms.Remove(@Geom);
  Geom.MeshInst.Free;
end;

procedure TMissile.SetTransform;
  var q: TG2Quat;
begin
  q := G2Vec3Rotation(G2Vec3(0, 0, 1), Dir);
  MR.SetRotation(q);
  MR.PreScale(0.3, 0.3, 0.3);
  MP.SetTranslation(Pos);
  Geom.Transform := MR * MP;
end;

procedure TMissile.OnUpdate;
  var s: TParticleBillboard;
  var b: TParticleBounce;
  var v, vf, ImpactPos: TG2Vec3;
  var d: Single;
  var i: Integer;
begin
  inherited OnUpdate;
  PosSrc := Pos - Dir * 0.5;
  PosDst := PosSrc - Dir * Len;
  Len := 8 + (Random(101) * 0.01);
  Size := 5 + (Random(101) * 0.005);
  MP.SetTranslation(Pos);
  Geom.Transform := MR * MP;
  s := TParticleBillboard.Create;
  s.Texture := App.TexSmoke;
  s.X := Pos.x;
  s.Y := Pos.y;
  s.Z := Pos.z;
  s.Vel := G2RandomSpherePoint * 0.1;
  s.Dump := 0.9;
  s.FadeOut := True;
  s.LifeLength := 1000;
  s.ScaleFirst := 5;
  s.ScaleLast := 10;
  App.Particles.AddParticle(s);
  if Dead then
  App.CreateExplosion(PosSrc, Damage, Force);
end;

procedure TMissile.OnHitWall(const HitPos, HitNorm: TG2Vec3);
begin
  Die;
end;

procedure TMissile.OnHitChar(const HitChar: TCharacter);
begin
  Die;
end;
//TMissile END

//TParticleBounce BEGIN
constructor TParticleBounce.Create;
begin
  inherited Create;
  Texture := App.TexMuzzleFlash;
  Len := 10;
  LifeLength := 200;
  DistanceSorted := True;
  MaxSize := 2;
end;

procedure TParticleBounce.OnUpdate;
  var ti: Single;
  var b: Byte;
begin
  Size := t * MaxSize;
  PosDst := PosSrc + Dir * (Len * t);
  if t > 0.5 then
  b := Round(((1 - t) / 0.5) * 255)
  else
  b := 255;
  Color := G2Color(b, b, b, 255);
end;
//TParticleBounce END

//TParticleMuzzleFlash BEGIN
constructor TParticleMuzzleFlash.Create;
begin
  inherited Create;
  Texture := App.TexMuzzleFlash;
  Len := 6;
  RefPos := nil;
  RefDir := nil;
  LifeLength := 100;
  DistanceSorted := True;
end;

procedure TParticleMuzzleFlash.OnUpdate;
  var ti: Single;
  var b: Byte;
begin
  ti := 1 - t;
  Size := t * 2;
  if RefPos <> nil then PosSrc := RefPos^;
  if RefDir <> nil then Dir := RefDir^;
  PosDst := PosSrc + Dir * (Len * t);
  if t > 0.8 then
  b := Round((1 - (t - 0.8) / 0.2) * 255)
  else
  b := 255;
  Color := G2Color(b, b, b, 255);
end;
//TParticleMuzzleFlash END

//TParticleBloodSplash BEGIN
constructor TParticleBloodSplash.Create;
begin
  inherited Create;
  Texture := App.TexBlood1;
  Len := 6;
  LifeLength := 250;
  BlendMode.SetNormal;
  DistanceSorted := True;
end;

procedure TParticleBloodSplash.OnUpdate;
  var ti: Single;
begin
  ti := 1 - t;
  Size := t * 3;
  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 := 250;
  Texture := App.TexBlood2;
  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 := 3 + t * 5;
  Color := G2Color(255, 255, 255, Round(ti * 255));
end;
//TParticleBloodSplat END

//TParticleBillboard BEGIN
constructor TParticleBillboard.Create;
begin
  inherited Create;
  LifeLength := 1000;
  BlendMode.SetNormal;
  DistanceSorted := True;
  Vel := G2RandomSpherePoint * 0.01;
  VelAng := (Random(101) - 50) * 0.0001;
  Dump := 1;
  DumpAng := 1;
  Rotation := G2Random2Pi;
  ScaleFirst := 1;
  ScaleLast := 1;
  Scale := ScaleFirst;
  FadeIn := False;
  FadeOut := False;
end;

procedure TParticleBillboard.OnUpdate;
  var ti: Single;
  var b: Byte;
  var bm: TG2BlendMode;
begin
  ti := 1 - t;
  VelAng := VelAng * DumpAng;
  Rotation := Rotation + VelAng;
  Vel := Vel * Dump;
  X := X + Vel.x;
  Y := Y + Vel.y;
  Z := Z + Vel.z;
  Scale := G2LerpFloat(ScaleFirst, ScaleLast, t);
  if FadeIn and FadeOut then
  b := Round(Sin(t * Pi) * 255)
  else if FadeIn then
  begin
    if t < 0.5 then
    b := Round((t / 0.5) * 255)
    else
    b := 255;
  end
  else if FadeOut then
  begin
    if t > 0.5 then
    b := Round(((1 - t) / 0.5) * 255)
    else
    b := 255;
  end
  else b := 255;
  bm.SetAdd;
  if BlendMode = bm then
  Color := G2Color(b, b, b)
  else
  Color := G2Color(255, 255, 255, b);
end;
//TParticleBillboard END

//TParticleDust BEGIN
constructor TParticleDust.Create;
begin
  inherited Create;
  LifeLength := 500;
  Texture := App.TexDust;
  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 := 2 + t * 3;
  Color := G2Color(255, 255, 255, Round(ti * 255));
end;
//TParticleDust END

//TCharacter BEGIN
procedure TCharacter.MakeDust(const NodeName: AnsiString; const CurFrame, MinFrame, MaxFrame: Single);
begin
  if (MinFrame < CurFrame) and (MaxFrame >= CurFrame) then
  MakeDust(NodeName, 1);
end;

procedure TCharacter.MakeDust(const NodeName: AnsiString; const Amount: Integer);
  var v3: TG2Vec3;
  var i: Integer;
  var Dust: TParticleDust;
begin
  v3 := (MeshInst.NodeTransforms[Mesh.NodeIndex(NodeName)].TransformRen * Transform).GetTranslation + G2RandomSpherePoint;
  for i := 0 to Amount - 1 do
  begin
    Dust := TParticleDust.Create;
    Dust.X := v3.x;
    Dust.Y := v3.y;
    Dust.Z := v3.z;
    App.Particles.AddParticle(Dust);
  end;
end;

procedure TCharacter.Update;
  var GeomID: Integer;
  var FaceID: Word;
  var U, V, D: Single;
  var LightPos, LightTarget, LightDir, Center: TG2Vec3;
begin
  inherited Update;
  if not Dieing and not Dead then
  begin
    LightPos := App.Scene.FindNode('Direct01').Transform.GetTranslation;
    LightTarget := App.Scene.FindNode('Direct01Target').Transform.GetTranslation;
    LightDir := (LightTarget - LightPos).Normalized;
    Center := Transform.GetTranslation - Step;
    if App.Scene.CollideRay(G2Ray(Center, -LightDir), GeomID, FaceID, U, V, D) then
    begin
      LightAmount := LightAmount - 0.1;
      if LightAmount < 0 then LightAmount := 0;
    end
    else
    begin
      LightAmount := LightAmount + 0.1;
      if LightAmount > 1 then LightAmount := 1;
    end;
    if Health <= 0 then Kill
    else KillShotCount := 0;
  end;
  if Dieing then
  begin
    Ragdoll.Apply;
    Transform.SetIdentity;
    if App.AppTime - KillTime >= 4000 then
    begin
      Dieing := False;
      Dead := True;
      Phys.ChangeBegin;
      Ragdoll.Free;
      Ragdoll := nil;
      Phys.ChangeEnd;
      DeadTime := App.AppTime;
      Dec(App.RagdollCount);
    end;
  end;
  if Dead then
  begin
    Transform.Translate(0, -0.03, 0);
    if App.AppTime - DeadTime > 5000 then
    App.CharsToFree.Add(Self);
  end;
end;

procedure TCharacter.Kill;
  var m: TG2Mat;
  var i: Integer;
begin
  if (Mesh = nil) or Dieing or Dead then Exit;
  SetTransforms(True);
  m := Transform;
  Collide := False;
  Vel.SetValue(0, 0, 0);
  Dieing := True;
  Transform.SetIdentity;
  Phys.ChangeBegin;
  Ragdoll := TG2MeshRagdoll.Create(Phys.World, MeshInst, ForceAndTorqueCallback, Scale);
  Ragdoll.Load;
  Ragdoll.Transform(m);
  Ragdoll.Apply;
  for i := 0 to KillShotCount - 1 do
  Ragdoll.Impulse(KillShots[i].Pos, KillShots[i].Dir * KillShots[i].Force);
  Phys.ChangeEnd;
  KillTime := App.AppTime;
  Inc(App.RagdollCount);
end;

procedure TCharacter.AddKillShot(const Pos, Dir: TG2Vec3; const Force: Single);
begin
  if KillShotCount >= 32 then Exit;
  KillShots[KillShotCount].Pos := Pos;
  KillShots[KillShotCount].Dir := Dir;
  KillShots[KillShotCount].Force := Force;
  Inc(KillShotCount);
end;

constructor TCharacter.Create(const SceneGraph: TG2SceneGraph);
begin
  inherited Create(SceneGraph);
  LightAmount := 1;
  Scale := 1;
  Dieing := False;
  Dead := False;
  Ragdoll := nil;
  Health := 100;
  KillShotCount := 0;
end;

destructor TCharacter.Destroy;
begin
  if Ragdoll <> nil then
  begin
    Phys.ChangeBegin;
    Ragdoll.Free;
    Phys.ChangeEnd;
  end;
  inherited Destroy;
end;
//TCharacter END

//TGrenade BEGIN
constructor TGrenade.Create(const Group: TG2Group);
begin
  inherited Create(Group);
  Geom.MeshInst := App.MeshGrenade.InstanceCreate;
  App.LoadTextures(Geom.MeshInst);
  Geom.Shadow := True;
  App.ExtraGeoms.Add(@Geom);
  TimeCreated := App.AppTime;
  Inc(App.PhysObjectCount);
end;

destructor TGrenade.Destroy;
begin
  Phys.ChangeBegin;
  NewtonDestroyBody(Phys.World, Body);
  Phys.ChangeEnd;
  App.ExtraGeoms.Remove(@Geom);
  Geom.MeshInst.Free;
  Dec(App.PhysObjectCount);
  inherited Destroy;
end;

procedure TGrenade.Init(const Pos: TG2Vec3; const Vel: TG2Vec3);
  var Collision: TNewtonCollision;
  var Inertia: TG2Vec3;
  var m: TG2Mat;
  var Size, Mass: Single;
begin
  Size := 0.5;
  Mass := 1;
  Phys.ChangeBegin;
  //Collision := NewtonCreateBox(Phys.World, Size, Size, Size, SERIALIZE_ID_BOX, nil);
  Collision := NewtonCreateSphere(Phys.World, Size, Size, Size, SERIALIZE_ID_SPHERE, nil);
  Body := NewtonCreateBody(Phys.World, Collision);
  NewtonBodySetCollision(Body, Collision);
  NewtonReleaseCollision(Phys.World, Collision);
  Inertia.x := Mass * (Size * Size + Size * Size) / 12;
  Inertia.y := Mass * (Size * Size + Size * Size) / 12;
  Inertia.z := Mass * (Size * Size + Size * Size) / 12;
  NewtonBodySetMassMatrix(Body, Mass, Inertia.x, Inertia.y, Inertia.z);
  NewtonBodyGetMatrix(Body, @m);
  m[3, 0] := Pos.x;
  m[3, 1] := Pos.y;
  m[3, 2] := Pos.z;
  NewtonBodySetMatrix(Body, @m);
  NewtonBodySetForceAndTorqueCallBack(Body, ForceAndTorqueCallBack);
  NewtonBodySetVelocity(Body, @Vel);
  Phys.ChangeEnd;
end;

procedure TGrenade.Update;
begin
  NewtonBodyGetMatrix(Body, @Geom.Transform);
  Geom.Transform.PreScale(0.1, 0.1, 0.1);
  if App.AppTime - TimeCreated > 1700 then
  begin
    Die;
    App.CreateExplosion(Geom.Transform.GetTranslation, 140, 60);
  end;
end;
//TGrenade END

//TMedkit BEGIN
constructor TMedkit.Create(const Group: TG2Group);
begin
  inherited Create(Group);
  Geom.MeshInst := App.MeshMedkit.InstanceCreate;
  App.LoadTextures(Geom.MeshInst);
  Geom.Shadow := True;
  App.ExtraGeoms.Add(@Geom);
  App.Medkits.Add(Self);
end;

destructor TMedkit.Destroy;
begin
  App.Medkits.Remove(Self);
  App.ExtraGeoms.Remove(@Geom);
  Geom.MeshInst.Free;
  inherited Destroy;
end;

procedure TMedkit.Init(const Pos: TG2Vec3);
  var r: TG2Ray;
  var GeomID: Integer;
  var FaceID: Word;
  var U, V, D: Single;
begin
  r.SetValue(Pos, G2Vec3(0, -1, 0));
  if App.Scene.CollideRay(r, GeomID, FaceID, U, V, D) then
  PosW := r.Origin + r.Dir * D;
  Rotation := G2Random2Pi;
  Update;
end;

procedure TMedkit.Update;
begin
  Geom.Transform.SetScaling(0.3);
  Geom.Transform.RotateY(Rotation);
  Geom.Transform.Translate(PosW);
end;
//TMedkit END

//TGrenBox BEGIN
constructor TGrenBox.Create(const Group: TG2Group);
begin
  inherited Create(Group);
  Geom.MeshInst := App.MeshGrenBox.InstanceCreate;
  App.LoadTextures(Geom.MeshInst);
  Geom.Shadow := True;
  App.ExtraGeoms.Add(@Geom);
  App.GrenBoxes.Add(Self);
end;

destructor TGrenBox.Destroy;
begin
  App.GrenBoxes.Remove(Self);
  App.ExtraGeoms.Remove(@Geom);
  Geom.MeshInst.Free;
  inherited Destroy;
end;

procedure TGrenBox.Init(const Pos: TG2Vec3);
  var r: TG2Ray;
  var GeomID: Integer;
  var FaceID: Word;
  var U, V, D: Single;
begin
  r.SetValue(Pos, G2Vec3(0, -1, 0));
  if App.Scene.CollideRay(r, GeomID, FaceID, U, V, D) then
  PosW := r.Origin + r.Dir * D;
  Rotation := G2Random2Pi;
  Update;
end;

procedure TGrenBox.Update;
begin
  Geom.Transform.SetScaling(0.3);
  Geom.Transform.RotateY(Rotation);
  Geom.Transform.Translate(PosW);
end;
//TGrenBox END

//TPlayer BEGIN
procedure TPlayer.SetWeapon(const ID: Integer);
  var i, n: Integer;
begin
  if (CurWeaponID >= 0) and (CurWeaponID <= 7) then
  SlotAmmo[CurWeaponID] := Ammo;
  for i := 0 to High(App.Weapons) do
  if i <> ID then
  begin
    n := Mesh.GeomIndex(App.Weapons[i].Name);
    if n > -1 then
    MeshInst.GeomProperties[n].Visible := False;
  end;
  CurWeaponID := ID;
  Ammo := SlotAmmo[CurWeaponID];
  n := Mesh.GeomIndex(App.Weapons[ID].Name);
  if n > -1 then
  MeshInst.GeomProperties[n].Visible := True;
end;

procedure TPlayer.SetAnim(const AnimName: AnsiString);
begin
  if Anim <> AnimName then
  begin
    Anim := AnimName;
    AnimFrameCount := Mesh.Anims[Mesh.AnimIndex(Anim)].FrameCount;
    AnimFrame := 0;
    if Anim = 'Stand' then AnimSpeed := 0.1
    else if Anim = 'Run' then AnimSpeed := 0.6;
  end;
end;

procedure TPlayer.CreateMuzzleFlash;
  var mf0, mf1: TG2Vec3;
  var mf: TParticleMuzzleFlash;
begin
  mf0 := MeshInst.NodeTransforms[Mesh.NodeIndex('MuzzleFlashStart_' + IntToStr(App.Weapons[CurWeaponID].MFID))].TransformRen.GetTranslation;
  mf0 := mf0.Transform4x3(Transform);
  mf1 := MeshInst.NodeTransforms[Mesh.NodeIndex('MuzzleFlashEnd_' + IntToStr(App.Weapons[CurWeaponID].MFID))].TransformRen.GetTranslation;
  mf1 := mf1.Transform4x3(Transform);
  mf := TParticleMuzzleFlash.Create;
  mf.Dir := (mf1 - mf0).Normalized;
  mf.PosSrc := mf0;
  mf.RefPos := @MFPos;
  mf.RefDir := @MFDir;
  if CurWeaponID = 5 then
  mf.Texture := App.TexPlasmaShot;
  mf.Update;
  App.Particles.AddParticle(mf);
end;

procedure TPlayer.CreateShot;
  var mf0, mf1: TG2Vec3;
  var s: TShot;
  var i: Integer;
begin
  if Ammo <= 0 then Exit;
  Dec(Ammo);
  mf0 := MeshInst.NodeTransforms[Mesh.NodeIndex('MuzzleFlashStart_' + IntToStr(App.Weapons[CurWeaponID].MFID))].TransformRen.GetTranslation;
  mf0 := mf0.Transform4x3(Transform);
  G2SinCos(AngLook + Pi, mf1.x, mf1.z);
  case CurWeaponID of
    0: begin App.SndHandgun.PlayRate := 1 + (Random(201) - 100) * 0.001; App.SndHandgun.Play; end;
    1: begin App.SndSMG1.PlayRate := 1 + (Random(201) - 100) * 0.001; App.SndSMG1.Play; end;
    2: begin App.SndSMG1.PlayRate := 1 + (Random(201) - 100) * 0.001; App.SndSMG2.Play; end;
    3: begin App.SndShotgun1.PlayRate := 1 + (Random(201) - 100) * 0.001; App.SndShotgun1.Play; end;
    4: begin App.SndShotgun2.PlayRate := 1 + (Random(201) - 100) * 0.001; App.SndShotgun2.Play; end;
    5: begin App.SndPlasma.PlayRate := 1 + (Random(201) - 100) * 0.001; App.SndPlasma.Play; end;
    6: begin App.SndRifle.PlayRate := 1 + (Random(201) - 100) * 0.001; App.SndRifle.Play; end;
    7: begin App.SndRocketLauncher.PlayRate := 1 + (Random(201) - 100) * 0.001; App.SndRocketLauncher.Play; end;
  end;
  for i := 0 to App.Weapons[CurWeaponID].ShotCount - 1 do
  begin
    mf1.SetValue(mf1.x, 0, mf1.z);
    mf1 := (mf1 + G2RandomSpherePoint * (App.Weapons[CurWeaponID].Spread * 0.2) + G2Vec3(0, -0.05, 0)).Normalized;
    case CurWeaponID of
      5:
      begin
        s := TPlasmaShot.Create;
        s.Vel := s.Vel * 0.8;
      end;
      7:
      begin
        s := TMissile.Create;
        s.Vel := s.Vel * 0.7;
      end;
      else
      s := TShot.Create;
    end;
    s.Dir := mf1;
    s.Vel := s.Dir * 2;
    s.Pos := mf0 - mf1;
    s.Damage := App.Weapons[CurWeaponID].Damage;
    s.Force := App.Weapons[CurWeaponID].Force;
    if CurWeaponID = 6 then
    begin
      s.Piercing := True;
      s.Vel := s.Vel * 1.2;
      s.Len := s.Len * 1.5;
    end
    else if CurWeaponID = 7 then
    TMissile(s).SetTransform;
    s.Update;
    App.Particles.AddParticle(s);
  end;
end;

procedure TPlayer.ThrowGrenade;
  var Center, Dir, gp: TG2Vec3;
  var d: Single;
  var g: TGrenade;
begin
  if GrenadeCount <= 0 then Exit;
  Dec(GrenadeCount);
  gp := MeshInst.NodeTransforms[Mesh.NodeIndex('Gren')].TransformRen.GetTranslation;
  gp := gp.Transform4x3(Transform);
  Center := Transform.GetTranslation - Step;
  d := (LookAt - Center).Len;
  if d > 30 then d := 30;
  Dir.y := 0.5;
  G2SinCos(AngLook + Pi, Dir.x, Dir.z);
  Dir := Dir.Normalized * d * 2;
  g := TGrenade.Create(App.GroupItems);
  g.Init(gp, Dir);
end;

procedure TPlayer.Reload;
begin
  Reloading := True;
  ReloadTime := App.AppTime;
end;

procedure TPlayer.Heal;
begin
  if MedKitCount <= 0 then Exit;
  Healing := True;
  HealTime := App.AppTime;
  Dec(MedKitCount);
end;

procedure TPlayer.SwapSlots;
  var NextSlot: Integer;
begin
  if not Reloading and not Healing and not ThrowingGrenade then
  begin
    if WpnSlot = 0 then NextSlot := 1 else NextSlot := 0;
    if App.Store.PlayerSlots[NextSlot] > -1 then
    begin
      WpnSlot := NextSlot;
      SetWeapon(App.Store.PlayerSlots[WpnSlot]);
    end;
  end;
end;

procedure TPlayer.Update;
  var IsMoving: Boolean;
  var Center, Pos, CamX, CamY, CamZ, Acc: TG2Vec3;
  var r: TG2Ray;
  var d, f, AngLegsPrev: Single;
  var v2, vl, vm, vm2: TG2Vec2;
  var i: Integer;
begin
  inherited Update;
  if Dead or Dieing then
  begin
    if not App.PlayerDead then
    begin
      App.PlayerDead := True;
      App.PlayerDeadTime := App.AppTime;
    end;
  end
  else
  begin
    AngLegsPrev := AngLegs;
    Pos := Transform.GetTranslation;
    Center := Pos - Step;
    Acc.SetValue(0, -0.2, 0);
    r := App.Gfx.PointToRay(App.PlugInput.MousePos);
    r.IntersectPlane(G2Plane(Center + G2Vec3(0, 2, 0), G2Vec3(0, 1, 0)), False, d);
    LookAt := r.Origin + r.Dir * d;
    AngLook := G2Vec2AngleOY(G2Vec2(Center.x, Center.z), G2Vec2(LookAt.x, LookAt.z)) + Pi;
    MoveLeft := App.PlugInput.KeyDown[DIK_A];
    MoveRight := App.PlugInput.KeyDown[DIK_D];
    MoveUp := App.PlugInput.KeyDown[DIK_W];
    MoveDown := App.PlugInput.KeyDown[DIK_S];
    IsMoving := MoveLeft or MoveRight or MoveUp or MoveDown;
    if IsMoving then
    begin
      SetAnim('Run');
      AnimSpeed := (G2Vec2(Vel.x, Vel.z).Len / 0.6) * 0.6;
      App.Cam.GetViewSpaceVectors(CamX, CamY, CamZ);
      CamZ := CamX.Cross(G2Vec3(0, 1, 0));
      if MoveLeft then Acc := Acc + CamX * 0.1
      else if MoveRight then Acc := Acc - CamX * 0.1;
      if MoveUp then Acc := Acc - CamZ * 0.1
      else if MoveDown then Acc := Acc + CamZ * 0.1;
      vm.SetValue(Acc.x, Acc.z);
      vm := vm.Normalized * 0.1;
      Acc.x := vm.x; Acc.z := vm.y;
      AngMove := G2Vec2AngleOY(G2Vec2(0, 0), G2Vec2(Acc.x, Acc.z)) + Pi;
      AngLegs := AngMove;
    end
    else
    SetAnim('Stand');
    G2SinCos(AngLegs, vm.y, vm.x);
    G2SinCos(AngLook, vl.y, vl.x);
    if vm.Dot(vl) < 0 then
    begin
      if IsMoving then
      begin
        AngLegs := AngLegs - Pi;
        AnimSpeed := -Abs(AnimSpeed);
      end
      else
      begin
        vm2 := vm.Perp;
        if vl.Dot(vm2) < 0 then
        AngLegs := AngLook + HalfPi
        else
        AngLegs := AngLook - HalfPi;
        AngLegsPrev := AngLegs;
      end;
    end
    else
    begin
      AnimSpeed := Abs(AnimSpeed);
    end;
    G2SinCos(AngLegsPrev, vm.y, vm.x);
    G2SinCos(AngLegs, vm2.y, vm2.x);
    if vm.Dot(vm2) < 0.6 then
    begin
      if vm.Perp.Dot(vm2) > 0.5 then
      vm := vm.Perp
      else
      vm := -vm.Perp;
    end;
    vm2 := G2LerpVec2(vm, vm2, 0.2);
    AngLegs := G2Vec2AngleOX(G2Vec2(0, 0), vm2);
    AnimFrame := AnimFrame + AnimSpeed;
    if IsMoving then
    begin
      MakeDust('Bip01 R Foot', AnimFrame, 4, 7);
      MakeDust('Bip01 L Foot', AnimFrame, 14, 17);
    end;
    if AnimFrameCount < 1 then AnimFrameCount := 1;
    while AnimFrame > AnimFrameCount do AnimFrame := AnimFrame - AnimFrameCount;
    while AnimFrame < 0 do AnimFrame := AnimFrame + AnimFrameCount;
    MeshInst.FrameSet(Anim, AnimFrame);
    if Healing then
    begin
      f := (App.AppTime - HealTime) / HealLength;
      if f > 1 then f := 1;
      f := f * (Mesh.Anims[Mesh.AnimIndex('Heal')].FrameCount - 1);
      MeshInst.FrameSet('Heal', f, BoneGroupPelvis);
      MeshInst.FrameSet('Heal', f, BoneGroupTorso);
    end
    else if Reloading then
    begin
      f := (App.AppTime - ReloadTime) / 500;
      if f > 1 then f := 1;
      f := f * (Mesh.Anims[Mesh.AnimIndex('Reload')].FrameCount - 1);
      MeshInst.FrameSet('Reload', f, BoneGroupPelvis);
      MeshInst.FrameSet('Reload', f, BoneGroupTorso);
    end
    else if ThrowingGrenade then
    begin
      f := (App.AppTime - TimeLastShot) / 400;
      if f > 1 then f := 1;
      f := f * (Mesh.Anims[Mesh.AnimIndex('Grenade')].FrameCount - 1);
      MeshInst.FrameSet('Grenade', f, BoneGroupPelvis);
      MeshInst.FrameSet('Grenade', f, BoneGroupTorso);
    end
    else
    begin
      if (App.AppTime - TimeLastShot < App.Weapons[CurWeaponID].AimAnim) then
      f := (App.AppTime - TimeLastShot) / App.Weapons[CurWeaponID].AimAnim * Mesh.Anims[Mesh.AnimIndex('Aim' + IntToStr(CurWeaponID))].FrameCount
      else
      f := 0;
      MeshInst.FrameSet('Aim' + IntToStr(CurWeaponID), f, BoneGroupPelvis);
      MeshInst.FrameSet('Aim' + IntToStr(CurWeaponID), f, BoneGroupTorso);
    end;
    MeshInst.NodeTransforms[TorsoNodeID].TransformCur.SetRotationX(AngLook - AngLegs);
    MeshInst.ComputeTransforms(BoneGroupTorso);
    if ThrowingGrenade and (App.AppTime - TimeLastShot >= 400) then
    begin
      ThrowGrenade;
      ThrowingGrenade := False;
    end;
    if Reloading and (App.AppTime - ReloadTime >= App.Weapons[CurWeaponID].ReloadTime) then
    begin
      Reloading := False;
      Ammo := App.Weapons[CurWeaponID].Ammo;
    end;
    if Healing and (App.AppTime - HealTime >= HealLength) then
    begin
      Healing := False;
      Health := Health + 40;
      if Health > 100 then Health := 100;
    end;
    if G2Vec2(Vel.x, Vel.z).Len > 0.6 then Acc.SetValue(0, Acc.y, 0);
    Vel := Vel + Acc;
    for i := 0 to High(App.MonsterSpawn) do
    begin
      v2 := G2Vec2(Pos.x, Pos.z) - G2Vec2(App.MonsterSpawn[i].x, App.MonsterSpawn[i].z);
      if v2.Len < 10 then
      begin
        v2 := v2.Normalized * 10 + G2Vec2(App.MonsterSpawn[i].x, App.MonsterSpawn[i].z);
        Transform.Translate(v2.x - Pos.x, 0, v2.y - Pos.z);
        Pos.x := v2.x;
        Pos.z := v2.y;
      end;
    end;
    SetTransforms();
    if not ThrowingGrenade and not Reloading and not Healing then
    begin
      if App.PlugInput.MouseDown[0]
      and (App.AppTime - TimeLastShot > App.Weapons[CurWeaponID].ShotLag)
      and (Ammo > 0) then
      begin
        TimeLastShot := App.AppTime;
        if CurWeaponID <> 7 then
        CreateMuzzleFlash;
        CreateShot;
      end
      else if App.PlugInput.MouseDown[1]
      and (GrenadeCount > 0)
      and (App.AppTime - TimeLastShot > 200)
      and (App.AppTime - TimeLastGren > 800) then
      begin
        TimeLastShot := App.AppTime;
        TimeLastGren := App.AppTime;
        ThrowingGrenade := True;
      end
      else if App.PlugInput.KeyDown[DIK_R]
      and (Ammo < App.Weapons[CurWeaponID].Ammo) then
      begin
        Reload;
      end
      else if App.PlugInput.KeyDown[DIK_F]
      and (MedKitCount > 0)
      and (Health < 100) then
      begin
        Heal;
      end
      else if (App.AppTime - TimeLastShot > 400)
      and (Ammo = 0) then
      begin
        Reload;
      end;
    end;
    for i := 0 to App.Medkits.Count - 1 do
    if MedKitCount < 4 then
    begin
      if (Pos - TMedkit(App.Medkits[i]).PosW).Len < 3 then
      begin
        TMedkit(App.Medkits[i]).Die;
        Inc(MedKitCount);
      end;
    end;
    for i := 0 to App.GrenBoxes.Count - 1 do
    if GrenadeCount < 16 then
    begin
      if (Pos - TGrenBox(App.GrenBoxes[i]).PosW).Len < 3 then
      begin
        TGrenBox(App.GrenBoxes[i]).Die;
        Inc(GrenadeCount, 4);
        if GrenadeCount > 16 then
        GrenadeCount := 16;
      end;
    end;
    MFPos := MeshInst.NodeTransforms[Mesh.NodeIndex('MuzzleFlashStart_' + IntToStr(App.Weapons[CurWeaponID].MFID))].TransformRen.GetTranslation;
    MFPos := MFPos.Transform4x3(Transform);
    MFDir := MeshInst.NodeTransforms[Mesh.NodeIndex('MuzzleFlashEnd_' + IntToStr(App.Weapons[CurWeaponID].MFID))].TransformRen.GetTranslation;
    MFDir := MFDir.Transform4x3(Transform);
    MFDir := (MFDir - MFPos).Normalized;
  end;
end;

procedure TPlayer.SetTransforms(const Rag: Boolean = False);
  var Pos: TG2Vec3;
begin
  Pos := Transform.GetTranslation;
  if Rag then
  Transform.SetIdentity
  else
  Transform.SetScaling(Scale);
  Transform.RotateY(AngLegs);
  Transform.Translate(Pos);
end;

constructor TPlayer.Create(const SceneGraph: TG2SceneGraph);
  var v: TG2Vec3;
  var i: Integer;
begin
  inherited Create(SceneGraph);
  v := App.Scene.FindNode('Player_01').Transform.GetTranslation;
  App.Cam.SetView(v, v + G2Vec3(0, 0, 1), G2Vec3(0, 1, 0));
  Mesh := App.Scene.MgrCharMeshes.FindMesh('Player');
  Radius := 3;
  Step.SetValue(0, -3.5, 0);
  Scale := 0.1;
  Transform.SetScaling(Scale);
  Transform.Translate(v);
  Collide := True;
  Anim := 'Run';
  AnimFrame := 0;
  AnimFrameCount := Mesh.Anims[Mesh.AnimIndex(Anim)].FrameCount;
  AnimSpeed := 0.1;
  MoveLeft := False;
  MoveRight := False;
  MoveUp := False;
  MoveDown := False;
  AngLook := 0;
  AngMove := 0;
  AngLegs := 0;
  BoneGroupLegs := Mesh.BoneGroupCreate;
  BoneGroupLegs.IncludeAll;
  BoneGroupLegs.ExcludeBone('Bip01 Spine1', True);
  BoneGroupLegs.ExcludeBone('Bip01 Spine', False);
  BoneGroupTorso := Mesh.BoneGroupCreate;
  BoneGroupTorso.IncludeBone('Bip01 Spine', False);
  BoneGroupTorso.IncludeBone('Bip01 Spine1', True);
  BoneGroupPelvis := Mesh.BoneGroupCreate;
  BoneGroupPelvis.IncludeBone('Bip01 Pelvis');
  TorsoNodeID := Mesh.NodeIndex('Bip01 Spine');
  TimeLastShot := App.AppTime;
  TimeLastGren := App.AppTime;
  ReloadTime := App.AppTime;
  HealTime := App.AppTime;
  ThrowingGrenade := False;
  Reloading := False;
  Healing := False;
  SetWeapon(0);
  GrenadeCount := 6;
  MedKitCount := 2;
  HealLength := 3000;
  WpnSlot := 0;
  for i := 0 to 7 do
  SlotAmmo[i] := App.Weapons[i].Ammo;
  Ammo := App.Weapons[CurWeaponID].Ammo;
end;

destructor TPlayer.Destroy;
begin
  inherited Destroy;
end;
//TPlayer END

//TMonster BEGIN
procedure TMonster.SetAnim(const AnimName: AnsiString);
begin
  if Anim <> AnimName then
  begin
    Anim := AnimName;
    AnimFrame := 0;
    AnimFrameCount := Mesh.Anims[Mesh.AnimIndex(Anim)].FrameCount;
    SetAnimSpeed(Anim);
  end;
end;

procedure TMonster.Kill;
  var m: TMedkit;
  var g: TGrenBox;
begin
  if Random(101) >= 98 then
  begin
    case Random(2) of
      0:
      begin
        m := TMedkit.Create(App.GroupItems);
        m.Init(Transform.GetTranslation - Step);
      end;
      1:
      begin
        g := TGrenBox.Create(App.GroupItems);
        g.Init(Transform.GetTranslation - Step);
      end;
    end;
  end;
  App.Store.PlayerMoney := App.Store.PlayerMoney + MonsterReward;
  Dec(App.MonstersLeft);
  inherited Kill;
end;

procedure TMonster.Update;
  var v3: TG2Vec3;
  var PlayerDir: TG2Vec2;
begin
  inherited Update;
  if not Dead and not Dieing and not App.PlayerDead then
  begin
    v3 := App.Player.Transform.GetTranslation;
    v3 := v3 - Transform.GetTranslation;
    PlayerDir.SetValue(v3.x, v3.z);
    PlayerDir.Normalize;
    Ang := G2Vec2AngleOY(G2Vec2(0, 0), PlayerDir) + Pi;
  end;
end;

procedure TMonster.SetTransforms(const Rag: Boolean = False);
  var Pos: TG2Vec3;
begin
  Pos := Transform.GetTranslation;
  if Rag then
  Transform.SetIdentity
  else
  Transform.SetScaling(Scale);
  Transform.RotateY(Ang);
  Transform.Translate(Pos);
end;

constructor TMonster.Create(const SceneGraph: TG2SceneGraph);
begin
  inherited Create(SceneGraph);
  Inc(NumMonsters);
  MonsterReward := 10;
  Inc(App.MonstersLeft);
end;

destructor TMonster.Destroy;
begin
  Dec(NumMonsters);
  inherited Destroy;
end;
//TMoster END

//TZombie BEGIN
procedure TZombie.SetAnimSpeed(const AnimName: AnsiString);
begin
  if AnimName = 'Idle' then AnimSpeed := 0.05
  else if AnimName = 'Walk' then AnimSpeed := 0.2
  else if AnimName = 'Attack' then AnimSpeed := 0.3
  else AnimSpeed := 0.1;
end;

procedure TZombie.Update;
  var Acc: TG2Vec3;
  var Pos: TG2Vec3;
  var f: Single;
  var Speed, MaxSpeed: Single;
  var PlayerDir: TG2Vec3;
  var PlayerDist: Single;
begin
  inherited Update;
  if not Dead and not Dieing and not App.PlayerDead then
  begin
    Pos := Transform.GetTranslation;
    Acc.SetValue(0, -0.2, 0);

    PlayerDir := App.Player.Transform.GetTranslation - Pos;
    PlayerDist := (App.Player.Transform.GetTranslation - Pos).Len;
    if not Attacking
    and (PlayerDist < 10)
    and (App.AppTime - LastAttack > 1400) then
    begin
      LastAttack := App.AppTime;
      Attacking := True;
      AttackHit := False;
      SetAnim('Attack');
    end;
    if not Attacking then
    SetAnim('Walk');

    if Attacking then
    begin
      Speed := 0.04;
      MaxSpeed := 1;
      if not AttackHit
      and (PlayerDist < 7)
      and (AnimFrame / AnimFrameCount > 0.4)
      and (AnimFrame / AnimFrameCount < 0.7) then
      begin
        AttackHit := True;
        App.CreateBloodSplash(((Pos - Step) + (App.Player.Transform.GetTranslation - App.Player.Step)) * 0.5);
        App.Player.Vel := App.Player.Vel + PlayerDir.Normalized * 0.3;
        App.Player.Health := App.Player.Health - 10;
      end;
    end
    else
    begin
      Speed := 0.02;
      MaxSpeed := 0.7;
    end;

    if Grounded then
    begin
      G2SinCos(Ang, Acc.x, Acc.z);
      Acc.x := -Acc.x * Speed;
      Acc.z := -Acc.z * Speed;
    end;

    SetTransforms();
    f := AnimFrame;
    AnimFrame := AnimFrame + AnimSpeed;
    if Attacking and (AnimFrame > AnimFrameCount) then
    begin
      Attacking := False;
      SetAnim('Idle');
    end;
    if AnimFrameCount < 1 then AnimFrameCount := 1;
    while AnimFrame > AnimFrameCount do AnimFrame := AnimFrame - AnimFrameCount;
    while AnimFrame < 0 do AnimFrame := AnimFrame + AnimFrameCount;
    MeshInst.FrameSet(Anim, AnimFrame);

    if App.AppTime - TimeDust > 100 then
    begin
      TimeDust := App.AppTime;
      MakeDust('Bip01 L Foot', 1);
      MakeDust('Bip01 R Foot', 1);
    end;

    if G2Vec2(Vel.x, Vel.z).Len > MaxSpeed then Acc.SetValue(0, Acc.y, 0);
    Vel := Vel + Acc;
  end;
end;

constructor TZombie.Create(const SceneGraph: TG2SceneGraph);
  var v: TG2Vec3;
begin
  inherited Create(SceneGraph);
  v := App.MonsterSpawn[Random(Length(App.MonsterSpawn))];
  Mesh := App.Scene.MgrCharMeshes.FindMesh('Zombie');
  Radius := 3;
  Step.SetValue(0, -3.5, 0);
  Scale := 0.4;
  Transform.SetScaling(Scale);
  Transform.Translate(v);
  Collide := True;
  SetAnim('Idle');
  Attacking := False;
  TimeDust := App.AppTime;
  LastAttack := App.AppTime;
  MonsterReward := 15;
end;

destructor TZombie.Destroy;
begin
  inherited Destroy;
end;
//TZombie END

//TJason BEGIN
procedure TJason.SetAnimSpeed(const AnimName: AnsiString);
begin
  if AnimName = 'Idle' then AnimSpeed := 0.05
  else if AnimName = 'Run' then AnimSpeed := 0.5
  else if AnimName = 'Attack' then AnimSpeed := 0.6
  else AnimSpeed := 0.1;
end;

procedure TJason.Update;
  var Acc: TG2Vec3;
  var Pos: TG2Vec3;
  var f: Single;
  var Speed, MaxSpeed: Single;
  var PlayerDir: TG2Vec3;
  var PlayerDist: Single;
begin
  inherited Update;
  if not Dead and not Dieing and not App.PlayerDead then
  begin
    Pos := Transform.GetTranslation;
    Acc.SetValue(0, -0.2, 0);

    PlayerDir := App.Player.Transform.GetTranslation - Pos;
    PlayerDist := (App.Player.Transform.GetTranslation - Pos).Len;
    if not Attacking
    and (PlayerDist < 15)
    and (App.AppTime - LastAttack > 1400) then
    begin
      LastAttack := App.AppTime;
      Attacking := True;
      AttackHit := False;
      SetAnim('Attack');
    end;
    if not Attacking then
    SetAnim('Run');

    if Attacking then
    begin
      Speed := 0.08;
      MaxSpeed := 2;
      if not AttackHit
      and (PlayerDist < 10)
      and (AnimFrame / AnimFrameCount > 0.4)
      and (AnimFrame / AnimFrameCount < 0.7) then
      begin
        AttackHit := True;
        App.CreateBloodSplash(((Pos - Step) + (App.Player.Transform.GetTranslation - App.Player.Step)) * 0.5);
        App.Player.Vel := App.Player.Vel + PlayerDir.Normalized * 0.3;
        App.Player.Health := App.Player.Health - 10;
      end;
    end
    else
    begin
      Speed := 0.05;
      MaxSpeed := 1;
      MakeDust('Bip01 R Foot', AnimFrame, 4, 7);
      MakeDust('Bip01 L Foot', AnimFrame, 14, 17);
    end;

    G2SinCos(Ang, Acc.x, Acc.z);
    Acc.x := -Acc.x * Speed; Acc.z := -Acc.z * Speed;

    Transform.SetScaling(Scale);
    Transform.RotateY(Ang);
    Transform.Translate(Pos);
    f := AnimFrame;
    AnimFrame := AnimFrame + AnimSpeed;
    if Attacking and (AnimFrame > AnimFrameCount) then
    begin
      Attacking := False;
      SetAnim('Run');
    end;
    if AnimFrameCount < 1 then AnimFrameCount := 1;
    while AnimFrame > AnimFrameCount do AnimFrame := AnimFrame - AnimFrameCount;
    while AnimFrame < 0 do AnimFrame := AnimFrame + AnimFrameCount;
    MeshInst.FrameSet(Anim, AnimFrame);

    if G2Vec2(Vel.x, Vel.z).Len > MaxSpeed then Acc.SetValue(0, Acc.y, 0);
    Vel := Vel + Acc;
  end;
end;

constructor TJason.Create(const SceneGraph: TG2SceneGraph);
  var v: TG2Vec3;
begin
  inherited Create(SceneGraph);
  v := App.MonsterSpawn[Random(Length(App.MonsterSpawn))];
  Mesh := App.Scene.MgrCharMeshes.FindMesh('Jason');
  Radius := 3;
  Step.SetValue(0, -3.5, 0);
  Scale := 0.04;
  Transform.SetScaling(Scale);
  Transform.Translate(v);
  Collide := True;
  SetAnim('Idle');
  Attacking := False;
  Health := 80;
  LastAttack := App.AppTime;
  MonsterReward := 20;
end;

destructor TJason.Destroy;
begin
  inherited Destroy;
end;
//TJason END

//TMenu BEGIN
function TMenu.McInButton(const Index: Integer): Boolean;
  var x, y: Integer;
begin
  x := (App.Gfx.Params.Width - 256) div 2;
  y := (App.Gfx.Params.Height - 128 * Length(Buttons)) div 2;
  Result := PtInRect(
    Rect(x, y + 128 * Index, x + 256, y + 128 * Index + 128),
    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 := (App.Gfx.Params.Height - 128 * Length(Buttons)) div 2;
  for i := 0 to High(Buttons) do
  begin
    App.Render2D.DrawRect(
      x, y + i * 128,
      Buttons[i].Texture
    );
    if McInButton(i) then
    begin
      App.RenderModes.BlendModeAdd;
      App.Render2D.DrawRect(
        x, y + i * 128,
        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

//TStore BEGIN
procedure TStore.SetMessage(const Mes: AnsiString);
begin
  LastMessage := Mes;
  MessageTime := GetTickCount;
end;

procedure TStore.SetDesc(const WeaponID: Integer);
  var i, j: Integer;
begin
  case WeaponID of
    0:
    begin
      Desc := 'Handgun'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Damage: ' + IntToStr(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount);
      Desc := Desc + '(' + IntToStr(Round(App.Weapons[WeaponID].Damage * 1000 / App.Weapons[WeaponID].ShotLag)) + '/Sec)' + #$D;
      i := Round(10000 / App.Weapons[WeaponID].ShotLag); j := i mod 10; i := i div 10;
      Desc := Desc + 'Rate: ' + IntToStr(i) + '.' + IntToStr(j) + ' SPS'#$D;
      Desc := Desc + 'Mag: ' + IntToStr(App.Weapons[WeaponID].Ammo) + #$D;
      Desc := Desc + 'Accuracy: ' + IntToStr(Round((1 - App.Weapons[WeaponID].Spread) * 100)) + '%'#$D;
      i := Round(10 * App.Weapons[WeaponID].ReloadTime / 1000); j := i mod 10; i := i div 10;
      Desc := Desc + 'Reload: ' + IntToStr(i) + '.' + IntToStr(j) + ' Sec'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Cost: $' + IntToStr(App.Weapons[WeaponID].Cost);
    end;
    1:
    begin
      Desc := 'SMG'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Damage: ' + IntToStr(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount);
      Desc := Desc + '(' + IntToStr(Round(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount * 1000 / App.Weapons[WeaponID].ShotLag)) + '/Sec)' + #$D;
      i := Round(10000 / App.Weapons[WeaponID].ShotLag); j := i mod 10; i := i div 10;
      Desc := Desc + 'Rate: ' + IntToStr(i) + '.' + IntToStr(j) + ' SPS'#$D;
      Desc := Desc + 'Mag: ' + IntToStr(App.Weapons[WeaponID].Ammo) + #$D;
      Desc := Desc + 'Accuracy: ' + IntToStr(Round((1 - App.Weapons[WeaponID].Spread) * 100)) + '%'#$D;
      i := Round(10 * App.Weapons[WeaponID].ReloadTime / 1000); j := i mod 10; i := i div 10;
      Desc := Desc + 'Reload: ' + IntToStr(i) + '.' + IntToStr(j) + ' Sec'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Cost: $' + IntToStr(App.Weapons[WeaponID].Cost);
    end;
    2:
    begin
      Desc := 'SMG2'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Damage: ' + IntToStr(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount);
      Desc := Desc + '(' + IntToStr(Round(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount * 1000 / App.Weapons[WeaponID].ShotLag)) + '/Sec)' + #$D;
      i := Round(10000 / App.Weapons[WeaponID].ShotLag); j := i mod 10; i := i div 10;
      Desc := Desc + 'Rate: ' + IntToStr(i) + '.' + IntToStr(j) + ' SPS'#$D;
      Desc := Desc + 'Mag: ' + IntToStr(App.Weapons[WeaponID].Ammo) + #$D;
      Desc := Desc + 'Accuracy: ' + IntToStr(Round((1 - App.Weapons[WeaponID].Spread) * 100)) + '%'#$D;
      i := Round(10 * App.Weapons[WeaponID].ReloadTime / 1000); j := i mod 10; i := i div 10;
      Desc := Desc + 'Reload: ' + IntToStr(i) + '.' + IntToStr(j) + ' Sec'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Cost: $' + IntToStr(App.Weapons[WeaponID].Cost);
    end;
    3:
    begin
      Desc := 'Shotgun'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Damage: ' + IntToStr(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount);
      Desc := Desc + '(' + IntToStr(Round(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount * 1000 / App.Weapons[WeaponID].ShotLag)) + '/Sec)' + #$D;
      i := Round(10000 / App.Weapons[WeaponID].ShotLag); j := i mod 10; i := i div 10;
      Desc := Desc + 'Rate: ' + IntToStr(i) + '.' + IntToStr(j) + ' SPS'#$D;
      Desc := Desc + 'Mag: ' + IntToStr(App.Weapons[WeaponID].Ammo) + #$D;
      Desc := Desc + 'Accuracy: ' + IntToStr(Round((1 - App.Weapons[WeaponID].Spread) * 100)) + '%'#$D;
      i := Round(10 * App.Weapons[WeaponID].ReloadTime / 1000); j := i mod 10; i := i div 10;
      Desc := Desc + 'Reload: ' + IntToStr(i) + '.' + IntToStr(j) + ' Sec'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Cost: $' + IntToStr(App.Weapons[WeaponID].Cost);
    end;
    4:
    begin
      Desc := 'Auto-Shotgun'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Damage: ' + IntToStr(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount);
      Desc := Desc + '(' + IntToStr(Round(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount * 1000 / App.Weapons[WeaponID].ShotLag)) + '/Sec)' + #$D;
      i := Round(10000 / App.Weapons[WeaponID].ShotLag); j := i mod 10; i := i div 10;
      Desc := Desc + 'Rate: ' + IntToStr(i) + '.' + IntToStr(j) + ' SPS'#$D;
      Desc := Desc + 'Mag: ' + IntToStr(App.Weapons[WeaponID].Ammo) + #$D;
      Desc := Desc + 'Accuracy: ' + IntToStr(Round((1 - App.Weapons[WeaponID].Spread) * 100)) + '%'#$D;
      i := Round(10 * App.Weapons[WeaponID].ReloadTime / 1000); j := i mod 10; i := i div 10;
      Desc := Desc + 'Reload: ' + IntToStr(i) + '.' + IntToStr(j) + ' Sec'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Cost: $' + IntToStr(App.Weapons[WeaponID].Cost);
    end;
    5:
    begin
      Desc := 'Plasma Rifle'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Damage: ' + IntToStr(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount);
      Desc := Desc + '(' + IntToStr(Round(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount * 1000 / App.Weapons[WeaponID].ShotLag)) + '/Sec)' + #$D;
      i := Round(10000 / App.Weapons[WeaponID].ShotLag); j := i mod 10; i := i div 10;
      Desc := Desc + 'Rate: ' + IntToStr(i) + '.' + IntToStr(j) + ' SPS'#$D;
      Desc := Desc + 'Mag: ' + IntToStr(App.Weapons[WeaponID].Ammo) + #$D;
      Desc := Desc + 'Accuracy: ' + IntToStr(Round((1 - App.Weapons[WeaponID].Spread) * 100)) + '%'#$D;
      i := Round(10 * App.Weapons[WeaponID].ReloadTime / 1000); j := i mod 10; i := i div 10;
      Desc := Desc + 'Reload: ' + IntToStr(i) + '.' + IntToStr(j) + ' Sec'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Cost: $' + IntToStr(App.Weapons[WeaponID].Cost);
    end;
    6:
    begin
      Desc := 'Sniper Rifle'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Damage: ' + IntToStr(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount);
      Desc := Desc + '(' + IntToStr(Round(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount * 1000 / App.Weapons[WeaponID].ShotLag)) + '/Sec)' + #$D;
      i := Round(10000 / App.Weapons[WeaponID].ShotLag); j := i mod 10; i := i div 10;
      Desc := Desc + 'Rate: ' + IntToStr(i) + '.' + IntToStr(j) + ' SPS'#$D;
      Desc := Desc + 'Mag: ' + IntToStr(App.Weapons[WeaponID].Ammo) + #$D;
      Desc := Desc + 'Accuracy: ' + IntToStr(Round((1 - App.Weapons[WeaponID].Spread) * 100)) + '%'#$D;
      i := Round(10 * App.Weapons[WeaponID].ReloadTime / 1000); j := i mod 10; i := i div 10;
      Desc := Desc + 'Reload: ' + IntToStr(i) + '.' + IntToStr(j) + ' Sec'#$D;
      Desc := Desc + 'Piercing'#$D;
      Desc := Desc + 'Critical damage'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Cost: $' + IntToStr(App.Weapons[WeaponID].Cost);
    end;
    7:
    begin
      Desc := 'Rocket launcher'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Damage: ' + IntToStr(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount);
      Desc := Desc + '(' + IntToStr(Round(App.Weapons[WeaponID].Damage * App.Weapons[WeaponID].ShotCount * 1000 / App.Weapons[WeaponID].ShotLag)) + '/Sec)' + #$D;
      i := Round(10000 / App.Weapons[WeaponID].ShotLag); j := i mod 10; i := i div 10;
      Desc := Desc + 'Rate: ' + IntToStr(i) + '.' + IntToStr(j) + ' SPS'#$D;
      Desc := Desc + 'Mag: ' + IntToStr(App.Weapons[WeaponID].Ammo) + #$D;
      Desc := Desc + 'Accuracy: ' + IntToStr(Round((1 - App.Weapons[WeaponID].Spread) * 100)) + '%'#$D;
      i := Round(10 * App.Weapons[WeaponID].ReloadTime / 1000); j := i mod 10; i := i div 10;
      Desc := Desc + 'Reload: ' + IntToStr(i) + '.' + IntToStr(j) + ' Sec'#$D;
      Desc := Desc + 'Area damage'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Cost: $' + IntToStr(App.Weapons[WeaponID].Cost);
    end;
    8:
    begin
      Desc := 'Grenade'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Damage: 140'#$D;
      Desc := Desc + 'Area damage'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Cost: $60';
    end;
    9:
    begin
      Desc := 'Medkit'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Recovers some lost vitality.'#$D;
      Desc := Desc + ''#$D;
      Desc := Desc + 'Cost: $250';
    end;
  end;
end;

procedure TStore.Init;
  var x, y, w, h: Integer;
begin
  with App do
  begin
    WeaponTexture[0] := TexWpnHandgun;
    WeaponTexture[1] := TexWpnSMG1;
    WeaponTexture[2] := TexWpnSMG2;
    WeaponTexture[3] := TexWpnShotgun1;
    WeaponTexture[4] := TexWpnShotgun2;
    WeaponTexture[5] := TexWpnPlasma;
    WeaponTexture[6] := TexWpnRifle;
    WeaponTexture[7] := TexWpnRocketLauncher;
    WeaponTexture[8] := TexGrenade;
    WeaponTexture[9] := TexMedkit;
    CurPage := 0;
    w := 800;
    h := 600;
    x := (Gfx.Params.Width - w) div 2;
    y := (Gfx.Params.Height - h) div 2;
    x := x + 32; y := y + 32; w := w - 64; h := h - 64;
    RectDone := Rect(x + w - 128, y + h - 32, x + w, y + h);
    RectStore := Rect(x, y + h - 32, x + 200, y + h);
    RectInventory := Rect(x + 200 - 1, y + h - 32, x + 400 - 1, y + h);
  end;
end;

procedure TStore.Reset;
  var i: Integer;
begin
  PlayerWeapons[0] := True;
  for i := 1 to High(PlayerWeapons) do
  PlayerWeapons[i] := False;
  PlayerMoney := 280;
  PlayerGrenades := 6;
  PlayerMedkits := 2;
  PlayerSlots[0] := 0;
  PlayerSlots[1] := -1;
end;

procedure TStore.TextOut(const Text: AnsiString; const R: TRect);
begin
  App.Font1.Print(
    r.Left + (r.Right - r.Left - App.Font1.GetTextWidth(Text)) * 0.5,
    r.Top + (r.Bottom - r.Top - App.Font1.GetTextHeight(Text)) * 0.5,
    $ffffffff, Text
  );
end;

procedure TStore.Render;
  var r: TRect;
  var x, y, w, h, x1, y1, x2, y2, w1, h1, w2, h2: Integer;
  var mc: TPoint;
  var i, j, n: Integer;
  var b: Boolean;
begin
  with App do
  begin
    mc := PlugInput.MousePos;
    w := 800;
    h := 600;
    x := (Gfx.Params.Width - w) div 2;
    y := (Gfx.Params.Height - h) div 2;
    r := Rect(x, y, x + w, y + h);
    RenderModes.FilteringLinear();
    GUI.Skin.DrawTemplate('Frame0', r);
    RenderModes.FilteringPoint();
    x := x + 32; y := y + 32; w := w - 64; h := h - 64;
    r := Rect(x, y, x + w, y + h);
    GUI.Skin.DrawTemplate('Frame', r);
    r := Rect(x, y, x + w, y + 32);
    GUI.Skin.DrawTemplate('TitleBar', r);
    case CurPage of
      0: TextOut('STORE', r);
      1: TextOut('INVENTORY', r);
    end;
    r := RectDone;//(x + w - 128, y + h - 32, x + w, y + h);
    if PtInRect(r, mc) then
    GUI.Skin.DrawTemplate('BtnHover', r)
    else
    GUI.Skin.DrawTemplate('BtnNormal', r);
    TextOut('DONE', r);
    r := RectStore;//Rect(x, y + h - 32, x + 200, y + h);
    if CurPage = 0 then
    GUI.Skin.DrawTemplate('SwitchActive', r)
    else
    GUI.Skin.DrawTemplate('SwitchNormal', r);
    TextOut('STORE', r);
    r := RectInventory;//Rect(x + 200 - 1, y + h - 32, x + 400 - 1, y + h);
    if CurPage = 1 then
    GUI.Skin.DrawTemplate('SwitchActive', r)
    else
    GUI.Skin.DrawTemplate('SwitchNormal', r);
    TextOut('INVENTORY', r);
    Font1.Print(x + 8, y + h - 32 - 8 * 3, $ffffffff, '$' + IntToStr(PlayerMoney));
    if GetTickCount - MessageTime < 5000 then
    Font1.Print(x + (w - Font1.GetTextWidth(LastMessage)) * 0.5, y + h - 32 - 8 * 3, $ffffffff, LastMessage);
    case CurPage of
      0: //Store
      begin
        r := Rect(x + 8, y + 32 + 8, x + 8 * 30, y + 32 + 8 * 40);
        GUI.Skin.DrawTemplate('Frame', r);
        r := Rect(x + 8, y + 32 + 8 * 40 - 1, x + 8 * 30, y + 32 + 8 * 55);
        GUI.Skin.DrawTemplate('Frame', r);
        r := Rect(x + 8, y + 32 + 8, x + 8 * 30, y + 32 + 8 + 32);
        GUI.Skin.DrawTemplate('TitleBar', r);
        TextOut('INVENTORY', r);
        r := Rect(x + 8 * 31, y + 32 + 8, x + 8 * 61, y + 32 + 8 * 55);
        GUI.Skin.DrawTemplate('Frame', r);
        r := Rect(x + 8 * 31, y + 32 + 8, x + 8 * 61, y + 32 + 8 + 32);
        GUI.Skin.DrawTemplate('TitleBar', r);
        TextOut('STORE', r);
        r := Rect(x + 8 * 62, y + 32 + 8, x + 8 * 91, y + 32 + 8 * 55);
        GUI.Skin.DrawTemplate('Frame', r);
        if Length(Desc) > 0 then
        Font1.Paragraph(x + 8 * 63, y + 32 + 8 * 5, (90 - 62) * 8, 50 * 8, $ffffffff, Desc);
        r := Rect(x + 8 * 62, y + 32 + 8, x + 8 * 91, y + 32 + 8 + 32);
        GUI.Skin.DrawTemplate('TitleBar', r);
        TextOut('DESCRIPTION', r);
        Render2D.DrawRect(
          x + 8 * 3, y + 32 + 8 * 42,
          TexGrenade.Width * 0.5, TexGrenade.Height * 0.5,
          $ffffffff, TexGrenade
        );
        Font1.Print(x + 8 * 10, y + 32 + 8 * 42 + 4, $ffffffff, 'x ' + IntToStr(PlayerGrenades));
        Render2D.DrawRect(
          x + 8 * 3, y + 32 + 8 * 49,
          TexMedkit.Width * 0.5, TexMedkit.Height * 0.5,
          $ffffffff, TexMedkit
        );
        Font1.Print(x + 8 * 10, y + 32 + 8 * 49 + 4, $ffffffff, 'x ' + IntToStr(PlayerMedkits));
        x1 := x + 8 * 32 - 6;
        y1 := y + 32 + 8 * 5;
        w1 := 204;
        x2 := x + 8 + 2;
        y2 := y + 32 + 8 * 5;
        w2 := 228;
        j := 0;
        n := 0;
        for i := 0 to High(PlayerWeapons) do
        if PlayerWeapons[i] then
        begin
          r := Rect(x2, y2 + n * 32, x2 + w2, y2 + n * 32 + 32);
          if PtInRect(r, mc) then
          begin
            Render.TextureClear();
            Prim2D.DrawRect(r, $80ffffff);
          end;
          Render2D.DrawRect(
            x2 + (w2 - WeaponTexture[i].Width) * 0.5, y2 + n * 32,
            WeaponTexture[i].Width, WeaponTexture[i].Height,
            $ffffffff, WeaponTexture[i]
          );
          Inc(n);
        end
        else
        begin
          r := Rect(x1, y1 + j * 32, x1 + w1, y1 + j * 32 + 32);
          if PtInRect(r, mc) then
          begin
            Render.TextureClear();
            Prim2D.DrawRect(r, $80ffffff);
          end;
          if (WeaponTexture[i] = TexGrenade)
          or (WeaponTexture[i] = TexMedkit) then
          Render2D.DrawRect(
            x1 + (w1 - WeaponTexture[i].Width * 0.5) * 0.5, y1 + j * 32,
            WeaponTexture[i].Width * 0.5, WeaponTexture[i].Height * 0.5,
            $ffffffff, WeaponTexture[i]
          )
          else
          Render2D.DrawRect(
            x1 + (w1 - WeaponTexture[i].Width) * 0.5, y1 + j * 32,
            WeaponTexture[i].Width, WeaponTexture[i].Height,
            $ffffffff, WeaponTexture[i]
          );
          r := Rect(x1 + w1, y1 + j * 32, x1 + w1 + 32, y1 + j * 32 + 32);
          if PtInRect(r, mc) then
          GUI.Skin.DrawTemplate('BtnHover', r)
          else
          GUI.Skin.DrawTemplate('BtnNormal', r);
          TextOut('$', r);
          Inc(j);
        end;
      end;
      1: //Inventory
      begin
        r := Rect(x + 8 * 10, y + 32 + 8 * 4, x + 8 * 36, y + 32 + 8 * 12);
        GUI.Skin.DrawTemplate('Frame', r);
        r := Rect(x + 8 * 10, y + 32 + 8 * 12 - 1, x + 8 * 36, y + 32 + 8 * 16 - 1);
        GUI.Skin.DrawTemplate('Frame', r);
        for i := 0 to 1 do
        if PlayerSlots[i] > -1 then
        begin
          Render2D.DrawRect(
            x + 8 * 10 + ((8 * 32 - 8 * 10) - WeaponTexture[PlayerSlots[i]].Width) * 0.5,
            y + 32 + 8 * 8 + 31 * i,
            WeaponTexture[PlayerSlots[i]]
          );
          r := Rect(x + 8 * 32 - 2, y + 32 + 8 * 8 + 31 * i, x + 8 * 36 - 2, y + 32 + 8 * 12 + 31 * i);
          if PtInRect(r, mc) then
          GUI.Skin.DrawTemplate('BtnHover', r)
          else
          GUI.Skin.DrawTemplate('BtnNormal', r);
          r := Rect(r.Left + 4, r.Top + 4, r.Right - 4, r.Bottom - 4);
          GUI.Skin.DrawTemplate('ArrowDown', r);
        end;
        r := Rect(x + 8 * 10, y + 32 + 8 * 4, x + 8 * 36, y + 32 + 8 * 8);
        GUI.Skin.DrawTemplate('TitleBar', r);
        TextOut('WEAPONS', r);
        r := Rect(x + 8 * 62, y + 32 + 8, x + 8 * 91, y + 32 + 8 * 55);
        GUI.Skin.DrawTemplate('Frame', r);
        r := Rect(x + 8 * 62, y + 32 + 8, x + 8 * 91, y + 32 + 8 + 32);
        GUI.Skin.DrawTemplate('TitleBar', r);
        TextOut('INVENTORY', r);
        Render2D.DrawRect(
          x + 8 * 19, y + 32 + 8 * 22,
          TexGrenade.Width * 0.5, TexGrenade.Height * 0.5,
          $ffffffff, TexGrenade
        );
        Font1.Print(x + 8 * 25, y + 32 + 8 * 22 + 4, $ffffffff, 'x ' + IntToStr(PlayerGrenades));
        Render2D.DrawRect(
          x + 8 * 19, y + 32 + 8 * 29,
          TexMedkit.Width * 0.5, TexMedkit.Height * 0.5,
          $ffffffff, TexMedkit
        );
        Font1.Print(x + 8 * 25, y + 32 + 8 * 29 + 4, $ffffffff, 'x ' + IntToStr(PlayerMedkits));
        x1 := x + 8 * 62 + 2;
        y1 := y + 32 + 8 * 5;
        w1 := 196;
        j := 0;
        for i := 0 to High(PlayerWeapons) - 2 do
        if PlayerWeapons[i] then
        begin
          b := True;
          for n := 0 to 1 do
          if PlayerSlots[n] = i then
          b := False;
          if b then
          begin
            Render2D.DrawRect(x1 + (w1 - WeaponTexture[i].Width) * 0.5, y1 + 32 * j, WeaponTexture[i]);
            r := Rect(x1 + w1, y1 + 32 * j, x1 + w1 + 32, y1 + 32 * j + 32);
            if PtInRect(r, mc) then
            GUI.Skin.DrawTemplate('BtnHover', r)
            else
            GUI.Skin.DrawTemplate('BtnNormal', r);
            r := Rect(r.Left + 4, r.Top + 4, r.Right - 4, r.Bottom - 4);
            GUI.Skin.DrawTemplate('ArrowUp', r);
            Inc(j);
          end;
        end;
      end;
    end;
  end;
end;

procedure TStore.Click;
  var mc: TPoint;
  var x, y, w, h, x1, y1, w1, h1, x2, y2, w2, h2, i, j, n: Integer;
  var r: TRect;
  var b: Boolean;
begin
  with App do
  begin
    mc := PlugInput.MousePos;
    w := 800;
    h := 600;
    x := (Gfx.Params.Width - w) div 2;
    y := (Gfx.Params.Height - h) div 2;
    x := x + 32; y := y + 32; w := w - 64; h := h - 64;
    if PtInRect(RectStore, mc)
    and (CurPage <> 0) then
    CurPage := 0;
    if PtInRect(RectInventory, mc)
    and (CurPage <> 1) then
    CurPage := 1;
    case CurPage of
      0:
      begin
        x1 := x + 8 * 32 - 6;
        y1 := y + 32 + 8 * 5;
        w1 := 204;
        x2 := x + 8 + 2;
        y2 := y + 32 + 8 * 5;
        w2 := 228;
        j := 0;
        n := 0;
        for i := 0 to High(PlayerWeapons) do
        if PlayerWeapons[i] then
        begin
          r := Rect(x2, y2 + n * 32, x2 + w2, y2 + n * 32 + 32);
          if PtInRect(r, mc) then
          SetDesc(i);
          Inc(n);
        end
        else
        begin
          r := Rect(x1, y1 + j * 32, x1 + w1, y1 + j * 32 + 32);
          if PtInRect(r, mc) then
          SetDesc(i);
          r := Rect(x1 + w1, y1 + j * 32, x1 + w1 + 32, y1 + j * 32 + 32);
          if PtInRect(r, mc) then
          begin
            if i = 8 then
            begin
              if PlayerGrenades < 16 then
              begin
                if PlayerMoney >= 60 then
                begin
                  Inc(PlayerGrenades);
                  PlayerMoney := PlayerMoney - 60;
                end
                else
                SetMessage('Insufficient funds');
              end
              else
              SetMessage('Cannot buy any more grenades');
            end
            else if i = 9 then
            begin
              if PlayerMedkits < 4 then
              begin
                if PlayerMoney >= 250 then
                begin
                  Inc(PlayerMedkits);
                  PlayerMoney := PlayerMoney - 250;
                end
                else
                SetMessage('Insufficient funds');
              end
              else
              SetMessage('Cannot buy any more medkits');
            end
            else
            begin
              if PlayerMoney >= App.Weapons[i].Cost then
              begin
                PlayerWeapons[i] := True;
                PlayerMoney := PlayerMoney - App.Weapons[i].Cost;
              end
              else
              SetMessage('Insufficient funds');
            end;
          end;
          Inc(j);
        end;
      end;
      1:
      begin
        for i := 0 to 1 do
        if PlayerSlots[i] > -1 then
        begin
          r := Rect(x + 8 * 32 - 2, y + 32 + 8 * 8 + 31 * i, x + 8 * 36 - 2, y + 32 + 8 * 12 + 31 * i);
          if PtInRect(r, mc) then
          PlayerSlots[i] := -1;
        end;
        x1 := x + 8 * 62 + 2;
        y1 := y + 32 + 8 * 5;
        w1 := 196;
        j := 0;
        for i := 0 to High(PlayerWeapons) - 2 do
        if PlayerWeapons[i] then
        begin
          b := True;
          for n := 0 to 1 do
          if PlayerSlots[n] = i then
          b := False;
          if b then
          begin
            r := Rect(x1 + w1, y1 + 32 * j, x1 + w1 + 32, y1 + 32 * j + 32);
            if PtInRect(r, mc) then
            begin
              if PlayerSlots[0] = -1 then
              PlayerSlots[0] := i
              else if PlayerSlots[1] = -1 then
              PlayerSlots[1] := i;
            end;
            Inc(j);
          end;
        end;
      end;
    end;
    if PtInRect(RectDone, mc) then
    begin
      App.StoreClose;
    end;
  end;
end;
//TStore END

//TLevelWave BEGIN
procedure TLevelWave.AddMonsters(const m0, m1, m2, m3, m4, m5, m6, m7, m8, m9: Integer);
begin
  if MonsterCount + 10 >= Length(Monsters) then
  SetLength(Monsters, Length(Monsters) + 50);
  Monsters[MonsterCount] := m0; Inc(MonsterCount);
  Monsters[MonsterCount] := m1; Inc(MonsterCount);
  Monsters[MonsterCount] := m2; Inc(MonsterCount);
  Monsters[MonsterCount] := m3; Inc(MonsterCount);
  Monsters[MonsterCount] := m4; Inc(MonsterCount);
  Monsters[MonsterCount] := m5; Inc(MonsterCount);
  Monsters[MonsterCount] := m6; Inc(MonsterCount);
  Monsters[MonsterCount] := m7; Inc(MonsterCount);
  Monsters[MonsterCount] := m8; Inc(MonsterCount);
  Monsters[MonsterCount] := m9; Inc(MonsterCount);
end;

procedure TLevelWave.Reset;
begin
  CurMonster := 0;
  MonsterCount := 0;
  SpawnInterval := 2000;
end;
//TLevelWave END

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

  case GameState of
    gsLogo: RenderLogo;
    gsMenu: RenderMenu;
    gsGame: RenderGame;
    gsGameOver: RenderGameOver;
  end;

//  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)));
  if Gfx.Recording then
  Font1.Print(Gfx.Params.Width - 10 - Font1.GetTextWidth('* Recording *'), 10, $ffff0000, '* Recording *');

  Render.RenderStop;
  Render.Present;
end;

procedure TMyApp.OnUpdate;
begin
  case GameState of
    gsInit: GameState := gsMenu;
    gsLogo: UpdateLogo;
    gsMenu: UpdateMenu;
    gsGame: UpdateGame;
    gsGameOver: UpdateGameOver;
  end;
  if not Music.IsPlaying then
  Music.Play;
end;

procedure TMyApp.OnKeyDown(const Key: Byte);
begin
  if Key = DIK_ESCAPE then
  begin
    if GameState = gsGame then
    begin
      if InGameMenuIsOpen then
      InGameMenuClose
      else if StoreIsOpen then
      StoreClose
      else
      InGameMenuOpen;
    end
    else
    Running := False;
  end;
  if (Key = DIK_Q)
  and (GameState = gsGame)
  and (not PlayerDead) then
  begin
    Player.SwapSlots;
  end;
//  if Key = DIK_B then
//  begin
//    if not InGameMenuIsOpen
//    and not StoreIsOpen then
//    StoreOpen;
//  end;
  if Key = DIK_F12 then Gfx.SaveScreenshot('Screen.jpg');
  if Key = DIK_F11 then
  begin
    if Gfx.Recording then
    Gfx.RecStop
    else
    Gfx.RecStart('Recording.avi', 600, Round(600 * (Gfx.Params.Height / Gfx.Params.Width)), 10000);
  end;
  if GameState = gsGameOver then
  GameState := gsMenu;
end;

procedure TMyApp.OnMouseDown(const Button: Byte);
begin
  if Button = 0 then
  case GameState of
    gsMenu: Menu.Click;
    gsGame:
    begin
      if InGameMenuIsOpen then
      InGameMenu.Click
      else if StoreIsOpen then
      Store.Click;
    end;
    gsGameOver: GameState := gsMenu;
  end;
end;

procedure TMyApp.OnWheelMove(const Shift: Integer);
begin
  if (GameState = gsGame)
  and (not PlayerDead) then
  Player.SwapSlots;
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;
  inherited Initialize;
  ShowCursor(False);
  Font1 := Gfx.Shared.RequestFont('Times New Roman', 16);
  GameState := gsInit;
  RenderLoading;
  g2.PackLinker.LinkPack('Data.g2pk', 'Gen2 Key');
  g2.RequestMod(TG2MeshMgr, @MgrMeshes);
  g2.RequestMod(TG2TextureMgr, @MgrTextures);
  g2.RequestMod(TG2SurfaceMgr, @MgrSurfaces);
  g2.RequestMod(TG2EffectMgr, @MgrEffects);
  g2.RequestMod(TG2Particles3D, @Particles);
  g2.RequestMod(TG2MusicMgr, @MgrMusic);
  g2.RequestMod(TG2SoundMgr, @MgrSounds);
  Shaders := MgrEffects.CreateEffectFromPack('Shaders', 'Shader', 'Shaders');
  ShaderUtils := Gfx.ShaderLib.RequestEffect('fx_Misc');
  MeshMissile := MgrMeshes.CreateMeshFromPack('Missile', 'Mesh', 'Missile');
  MeshGrenade := MgrMeshes.CreateMeshFromPack('Grenade', 'Mesh', 'Grenade');
  MeshMedkit := MgrMeshes.CreateMeshFromPack('Medkit', 'Mesh', 'Medkit');
  MeshGrenBox := MgrMeshes.CreateMeshFromPack('GrenBox', 'Mesh', 'GrenBox');
  TexLogo := MgrTextures.CreateTexture2DFromPack('Logo', 'Texture', 'LogoIGDC');
  TexArrow := MgrTextures.CreateTexture2DFromPack('Arrow', 'Texture', 'Arrow');
  TexMuzzleFlash := MgrTextures.CreateTexture2DFromPack('MuzzleFlash', 'Texture', 'MuzzleFlash');
  TexPlasmaShot := MgrTextures.CreateTexture2DFromPack('PlasmaShot', 'Texture', 'PlasmaShot');
  TexPlasmaFlare := MgrTextures.CreateTexture2DFromPack('PlasmaFlare', 'Texture', 'PlasmaFlare');
  TexFlame := MgrTextures.CreateTexture2DFromPack('Flame', 'Texture', 'Flame');
  TexFireFlare := MgrTextures.CreateTexture2DFromPack('FireFlare', 'Texture', 'RadialFlare');
  TexSmoke := MgrTextures.CreateTexture2DFromPack('Smoke', 'Texture', 'Smoke');
  TexDust := MgrTextures.CreateTexture2DFromPack('Dust', 'Texture', 'Dust');
  TexBlood1 := MgrTextures.CreateTexture2DFromPack('Blood1', 'Texture', 'Blood01');
  TexBlood2 := MgrTextures.CreateTexture2DFromPack('Blood2', 'Texture', 'Blood02');
  TexGrenade := MgrTextures.CreateTexture2DFromPack('Grenade', 'Texture', 'Grenade');
  TexBullet := MgrTextures.CreateTexture2DFromPack('Bullet', 'Texture', 'Bullet');
  TexHealthBarBG := MgrTextures.CreateTexture2DFromPack('HealthBarBG', 'Texture', 'HealthBarBG');
  TexHealthBar := MgrTextures.CreateTexture2DFromPack('HealthBar', 'Texture', 'HealthBar');
  TexMedkit := MgrTextures.CreateTexture2DFromPack('Medkit', 'Texture', 'Medkit');
  TexHeal := MgrTextures.CreateTexture2DFromPack('Heal', 'Texture', 'Heal');
  TexTitle := MgrTextures.CreateTexture2DFromPack('Title', 'Texture', 'Title');
  TexGameOver := MgrTextures.CreateTexture2DFromPack('GameOver', 'Texture', 'GameOver');
  TexMenuBG := MgrTextures.CreateTexture2DFromPack('MenuBG', 'Texture', 'MenuBG');
  TexBtnNewGame := MgrTextures.CreateTexture2DFromPack('BtnNewGame', 'Texture', 'BtnNewGame', 1);
  TexBtnExtras := MgrTextures.CreateTexture2DFromPack('BtnExtras', 'Texture', 'BtnExtras', 1);
  TexBtnQuit := MgrTextures.CreateTexture2DFromPack('BtnQuit', 'Texture', 'BtnQuit', 1);
  TexBtnContinue := MgrTextures.CreateTexture2DFromPack('BtnContinue', 'Texture', 'BtnContinue', 1);
  TexWpnHandgun := MgrTextures.CreateTexture2DFromPack('WpnHandgun', 'Texture', 'WpnHandgun', 1);
  TexWpnSMG1 := MgrTextures.CreateTexture2DFromPack('WpnSMG1', 'Texture', 'WpnSMG1', 1);
  TexWpnSMG2 := MgrTextures.CreateTexture2DFromPack('WpnSMG2', 'Texture', 'WpnSMG2', 1);
  TexWpnShotgun1 := MgrTextures.CreateTexture2DFromPack('WpnShotgun1', 'Texture', 'WpnShotgun1', 1);
  TexWpnShotgun2 := MgrTextures.CreateTexture2DFromPack('WpnShotgun2', 'Texture', 'WpnShotgun2', 1);
  TexWpnPlasma := MgrTextures.CreateTexture2DFromPack('WpnPlasma', 'Texture', 'WpnPlasma', 1);
  TexWpnRifle := MgrTextures.CreateTexture2DFromPack('WpnRifle', 'Texture', 'WpnRifle', 1);
  TexWpnRocketLauncher := MgrTextures.CreateTexture2DFromPack('WpnRocketLauncher', 'Texture', 'WpnRocketLauncher', 1);
  TexShadowDepthRT := MgrTextures.CreateTexture2DRT('ShadowDepth', 1024, 1024, D3DFMT_R32F);
  SurfShadowDepthDS := MgrSurfaces.CreateDepthStencilSurface('ShadowDepth', TexShadowDepthRT.Width, TexShadowDepthRT.Height);
  Music := MgrMusic.CreateMusic('Music');
  Music.OpenFile('sound\Music1.mp3');
  Music.Play;
  SndHandgun := MgrSounds.CreateSoundFromFile('Handgun', 'sound\Handgun.wav').CreateInstance;
  SndSMG1 := MgrSounds.CreateSoundFromFile('SMG1', 'sound\SMG1.wav').CreateInstance;
  SndSMG2 := MgrSounds.CreateSoundFromFile('SMG2', 'sound\SMG2.wav').CreateInstance;
  SndShotgun1 := MgrSounds.CreateSoundFromFile('Shotgun1', 'sound\Shotgun1.wav').CreateInstance;
  SndShotgun2 := MgrSounds.CreateSoundFromFile('Shotgun2', 'sound\Shotgun2.wav').CreateInstance;
  SndPlasma := MgrSounds.CreateSoundFromFile('Plasma', 'sound\Plasma.wav').CreateInstance;
  SndRifle := MgrSounds.CreateSoundFromFile('Rifle', 'sound\SniperRifle.wav').CreateInstance;
  SndRocketLauncher := MgrSounds.CreateSoundFromFile('RocketLauncher', 'sound\RocketLauncher.wav').CreateInstance;
  SndRic := MgrSounds.CreateSoundFromFile('Ric', 'sound\Ric.wav').CreateInstance;
  SndExpl := MgrSounds.CreateSoundFromFile('Expl', 'sound\Expl.wav').CreateInstance;
  Menu.AddButton(TexBtnNewGame, BtnNewGame);
  Menu.AddButton(TexBtnExtras, BtnExtras);
  Menu.AddButton(TexBtnQuit, BtnQuit);
  InGameMenu.AddButton(TexBtnContinue, BtnContinue);
  InGameMenu.AddButton(TexBtnQuit, BtnQuit);
  InGameMenuIsOpen := False;
  LevelOpen := False;
  InGameMenuIsOpen := False;
  StoreIsOpen := False;
  g2.RequestMod(TG2UI, @Store.GUI);
  UISkin := TG2UISkin.Create(Store.GUI);
  UISkin.LoadFromFile('UISkin.g2gui');
  Store.GUI.Skin := UISkin;
  StoreCloseTime := GetTickCount;
  AddWeapon('Handgun', 1, 400, 20, 1, 300, 0.3, 20, 16, 1000, 300);
  AddWeapon('SMG1', 2, 100, 9, 1, 200, 0.7, 15, 30, 1500, 800);
  AddWeapon('SMG2', 3, 150, 15, 1, 200, 0.4, 20, 25, 2000, 1000);
  AddWeapon('Shotgun1', 4, 800, 12, 6, 500, 0.5, 10, 8, 2000, 1000);
  AddWeapon('Shotgun2', 5, 300, 7, 5, 300, 0.6, 10, 12, 2000, 1200);
  AddWeapon('Plasma', 6, 800, 100, 1, 300, 0.5, 40, 4, 2500, 2000);
  AddWeapon('Rifle', 7, 1500, 80, 1, 300, 0.1, 40, 15, 2000, 2200);
  AddWeapon('RocketLauncher', 8, 1000, 120, 1, 300, 0.2, 50, 1, 2000, 3000);
  GameState := gsLogo;
  TimeLogo := App.AppTime;
  Tmr.Enabled := True;
end;

procedure TMyApp.Finalize;
begin
  UISkin.Free;
  if LevelOpen then
  LevelEnd;
  CS.Free;
  inherited Finalize;
end;

procedure TMyApp.CollideChars(const Char0, Char1: TG2SGChar);
  var Char0L, Char0H, Char1L, Char1H, L: Single;
  var Char0Pos, Char1Pos, Dir: 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;
  L := Dir.Len;
  if L > Char0.Radius + Char1.Radius then Exit;
  Dir.Normalize;
  L := (Char0.Radius + Char1.Radius - L) * 0.5;
  Char0.Transform.e30 := Char0Pos.x - Dir.x * L;
  Char0.Transform.e32 := Char0Pos.y - Dir.y * L;
  Char1.Transform.e30 := Char1Pos.x + Dir.x * L;
  Char1.Transform.e32 := Char1Pos.y + Dir.y * L;
end;

procedure TMyApp.CreateBloodSplash(const Pos: TG2Vec3; const Amount: Integer = 8);
  var i: Integer;
  var BloodSplat: TParticleBloodSplat;
  var BloodSplash: TParticleBloodSplash;
begin
  BloodSplat := TParticleBloodSplat.Create;
  BloodSplat.X := Pos.x;
  BloodSplat.Y := Pos.y;
  BloodSplat.Z := Pos.z;
  BloodSplat.Update;
  App.Particles.AddParticle(BloodSplat);
  for i := 0 to Amount - 1 do
  begin
    BloodSplash := TParticleBloodSplash.Create;
    BloodSplash.PosSrc := Pos;
    BloodSplash.Dir := G2RandomSpherePoint;
    BloodSplash.Update;
    App.Particles.AddParticle(BloodSplash);
  end;
end;

procedure TMyApp.CreateExplosion(const Pos: TG2Vec3; const Damage: Integer; const Force: Single);
  var i: Integer;
  var d: Single;
  var s: TParticleBillboard;
  var b: TParticleBounce;
  var v, vf, ImpactPos: TG2Vec3;
begin
  for i := 0 to 31 do
  begin
    s := TParticleBillboard.Create;
    s.Texture := App.TexSmoke;
    v := Pos + G2RandomSpherePoint * 2;
    s.X := v.x;
    s.Y := v.y;
    s.Z := v.z;
    s.Vel := G2RandomSpherePoint * 1;
    s.Dump := 0.9;
    s.FadeIn := True;
    s.FadeOut := True;
    s.LifeLength := 2000;
    s.ScaleFirst := 10;
    s.ScaleLast := 15;
    s.Rotation := G2Random2Pi;
    s.VelAng := (G2Random2Pi - Pi) * 0.003;
    App.Particles.AddParticle(s);
  end;
  for i := 0 to 15 do
  begin
    b := TParticleBounce.Create;
    b.PosSrc := Pos;
    b.MaxSize := 10;
    b.Len := 30;
    b.Dir := G2RandomSpherePoint;
    b.PosSrc := b.PosSrc - b.Dir * 2;
    b.LifeLength := 350 + Random(50);
    b.Update;
    App.Particles.AddParticle(b);
  end;
  s := TParticleBillboard.Create;
  s.Texture := App.TexFireFlare;
  s.BlendMode.SetAdd;
  v := Pos;
  s.X := v.x;
  s.Y := v.y;
  s.Z := v.z;
  s.FadeOut := True;
  s.LifeLength := 300;
  s.ScaleFirst := 5;
  s.ScaleLast := 30;
  App.Particles.AddParticle(s);
  for i := 0 to App.Scene.Chars.Count - 1 do
  if (TG2SGChar(App.Scene.Chars[i]) is TMonster)
  and (not TMonster(App.Scene.Chars[i]).Dead)
  and (not TMonster(App.Scene.Chars[i]).Dieing) then
  begin
    v := TMonster(App.Scene.Chars[i]).Transform.GetTranslation - TMonster(App.Scene.Chars[i]).Step;
    d := (v - Pos).Len;
    if d < 40 then
    begin
      if d < TMonster(App.Scene.Chars[i]).Radius * 2 then
      ImpactPos := v + (Pos - v).Normalized * TMonster(App.Scene.Chars[i]).Radius * 2
      else
      ImpactPos := Pos;
      vf := (v - ImpactPos + G2Vec3(0, 2, 0)).Normalized;
      d := 1 - d / 40;
      if vf.y < 0 then vf.y := - vf.y;
      TMonster(App.Scene.Chars[i]).Vel := TMonster(App.Scene.Chars[i]).Vel + vf * (d * Force * 0.05);
      TMonster(App.Scene.Chars[i]).Health := TMonster(App.Scene.Chars[i]).Health - Round(d * Damage);
      TMonster(App.Scene.Chars[i]).AddKillShot(ImpactPos, vf, Force);
    end;
  end;
  SndExpl.PlayRate := 1 + Random(201) * 0.001;
  SndExpl.SoundPosition := (Pos - Cam.Pos^) * 0.02;
  SndExpl.Play;
end;

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

procedure TMyApp.RenderLoading;
  var y0, y1: Single;
begin
  Render.Clear(False, True, True, $ff000000);
  Render.RenderStart;

  y1 := Gfx.Params.Height * 0.5;
  y0 := y1 - 64;
  Render.TextureClear();
  Prim2D.DrawRect4Col(0, y0, Gfx.Params.Width, 64, $ff000000, $ff000000, $ff800000, $ff800000);
  Prim2D.DrawRect4Col(0, y1, Gfx.Params.Width, 64, $ff800000, $ff800000, $ff000000, $ff000000);
  Font1.Print(
    (Gfx.Params.Width - Font1.GetTextWidth('Loading...')) * 0.5,
    (Gfx.Params.Height - Font1.GetTextHeight('Loading...')) * 0.5,
    $ffffffff, 'Loading...'
  );

  Render.RenderStop;
  Render.Present;
end;

procedure TMyApp.RenderMenu;
  var mc: TPoint;
  var tsx0, tsy0, tsx1, tsy1: Single;
begin
  Gfx.TextureStageStages.ColorOp[1] := D3DTOP_MODULATE;
  Gfx.TextureStageStages.ColorArg1[1] := D3DTA_CURRENT;
  Gfx.TextureStageStages.ColorArg2[1] := D3DTA_TEXTURE;
  tsx0 := -G2TimeInterval(110000);
  tsy0 := G2TimeInterval(18000);
  tsx1 := G2TimeInterval(110000);
  tsy1 := G2TimeInterval(18000) + 0.5;
  Render2D.DrawQuad2(
    G2Vec2(0, 0), G2Vec2(Gfx.Params.Width, 0),
    G2Vec2(0, Gfx.Params.Height), G2Vec2(Gfx.Params.Width, Gfx.Params.Height),
    $ffffffff, $ffffffff, $ff000000, $ff000000,
    TexMenuBG,
    G2Vec2(0 + tsx0, 0 + tsy0), G2Vec2(1 + tsx0, 0 + tsy0),
    G2Vec2(0 + tsx0, 1 + tsy0), G2Vec2(1 + tsx0, 1 + tsy0),
    TexMenuBG,
    G2Vec2(0 + tsx1, 0 + tsy1), G2Vec2(1 + tsx1, 0 + tsy1),
    G2Vec2(0 + tsx1, 1 + tsy1), G2Vec2(1 + tsx1, 1 + tsy1)
  );
  Gfx.TextureStageStages.SetDefaults;
  mc := PlugInput.MousePos;
  Render.TextureClear();
  {RenderModes.BlendModeMultiply;
  Prim2D.DrawQuad4Col(
    G2Vec2(0, 0), G2Vec2(Gfx.Params.Width, 0),
    G2Vec2(0, Gfx.Params.Height), G2Vec2(Gfx.Params.Width, Gfx.Params.Height),
    $ff000000, $ff000000, $ffffffff, $ffffffff
  );
  RenderModes.BlendModeNormal;}
  Render2D.DrawRect(
    (Gfx.Params.Width - TexTitle.Width) * 0.5, 0, TexTitle
  );
  Menu.Render;
  Render.TextureClear();
  Prim2D.DrawTriangle(
    mc, mc + G2Vec2(32, 16), mc + G2Vec2(8, 32),
    $ffaaaaaa
  );
  Font1.Print(
    Gfx.Params.Width - Font1.GetTextWidth('(c) DAN SOFT STUDIO'),
    Gfx.Params.Height - Font1.GetTextHeight('(c) DAN SOFT STUDIO'),
    $ffffffff, '(c) DAN SOFT STUDIO'
  );
end;

procedure TMyApp.UpdateMenu;
begin

end;

procedure TMyApp.RenderGame;
  var i: Integer;
  var f: Single;
  var mc: TPoint;
  var str: AnsiString;
begin
  Gfx.Transforms.V := Cam.View;
  Gfx.Transforms.ApplyV;
  Gfx.Transforms.P := Cam.Proj;
  Gfx.Transforms.ApplyP;

  RenderScene;
  Gfx.RenderStates.ZEnable := True;
  Gfx.RenderStates.ZWriteEnable := False;
  RenderModes.FilteringLinear();
  Particles.Render;
  RenderModes.FilteringPoint();
  Gfx.RenderStates.ZWriteEnable := True;
  Gfx.RenderStates.ZEnable := False;

  str := '$' + IntToStr(Store.PlayerMoney);
  Font1.Print(9, 49, $ff000000, str);
  Font1.Print(11, 51, $ff000000, str);
  Font1.Print(10, 50, $ffffffff, str);
  str := 'Progress: ' + IntToStr(MonsterWaves[CurWave].CurMonster - MonstersLeft) + ' / ' + IntToStr(MonsterWaves[CurWave].MonsterCount);
  Font1.Print(9, 69, $ff000000, str);
  Font1.Print(11, 71, $ff000000, str);
  Font1.Print(10, 70, $ffffffff, str);
  RenderModes.FilteringLinear();
  Render2D.DrawRect(10, 10, TexHealthBarBG);
  if not PlayerDead then
  begin
    f := (Player.Health * 0.01); if f < 0 then f := 0;
    Render2D.DrawRect(
      12, 12, Round((TexHealthBar.Width - 4) * f), TexHealthBar.Height - 4,
      Rect(0, 0, Round(TexHealthBar.Width * f), TexHealthBar.Height), TexHealthBar
    );
    Render2D.BatchBegin();
    for i := 0 to Player.GrenadeCount - 1 do
    Render2D.DrawRect(
      Gfx.Params.Width - 68 - 40 * i, Gfx.Params.Height - 68,
      64, 64, $ffffffff, TexGrenade, -Pi * 0.05, 1, 1, False, False
    );
    for i := 0 to Player.Ammo - 1 do
    Render2D.DrawRect(4 + i * 16, Gfx.Params.Height - 68, TexBullet);
    for i := 0 to Player.MedKitCount - 1 do
    Render2D.DrawRect(
      Gfx.Params.Width - 70 - 40 * i, 6,
      64, 64, $ffffffff, TexMedkit, -Pi * 0.1, 1, 1, False, False
    );
    Render2D.BatchEnd;
    if not Pause then
    RenderCrossair;
  end;
  RenderModes.FilteringPoint();

  if PlayerDead and (App.AppTime - PlayerDeadTime > 4000) then
  begin
    f := (App.AppTime - PlayerDeadTime - 4000) / 2000; if f > 1 then f := 1;
    i := Round(f * 255);
    Render.TextureClear();
    Prim2D.DrawRect(0, 0, Gfx.Params.Width, Gfx.Params.Height, G2Color(0, 0, 0, i));
  end;

  if App.AppTime - WaveTime < 8000 then
  begin
    if (App.AppTime - WaveTime > 3000)
    and ((App.AppTime - WaveTime < 6000)) then
    begin
      str := 'Wave ' + IntToStr(CurWave + 1);
      RenderModes.FilteringLinear();
      Font1.Print(
        (Gfx.Params.Width - Font1.GetTextWidth(str) * 2) * 0.5 - 1,
        (Gfx.Params.Height - Font1.GetTextHeight(str) * 2) * 0.5 - 1,
        2, 2, $ff000000, str
      );
      Font1.Print(
        (Gfx.Params.Width - Font1.GetTextWidth(str) * 2) * 0.5 + 1,
        (Gfx.Params.Height - Font1.GetTextHeight(str) * 2) * 0.5 + 1,
        2, 2, $ff000000, str
      );
      Font1.Print(
        (Gfx.Params.Width - Font1.GetTextWidth(str) * 2) * 0.5,
        (Gfx.Params.Height - Font1.GetTextHeight(str) * 2) * 0.5,
        2, 2, $ffffffff, str
      );
      RenderModes.FilteringPoint();
    end;
  end;

  if InGameMenuIsOpen then
  begin
    Render.TextureClear();
    Prim2D.DrawRect(0, 0, Gfx.Params.Width, Gfx.Params.Height, $44000000);
    InGameMenu.Render;
    mc := PlugInput.MousePos;
    Render.TextureClear();
    Prim2D.DrawTriangle(
      mc, mc + G2Vec2(32, 16), mc + G2Vec2(8, 32),
      $ffaaaaaa
    );
  end;
  if StoreIsOpen then
  begin
    Render.TextureClear();
    Prim2D.DrawRect(0, 0, Gfx.Params.Width, Gfx.Params.Height, $44000000);
    Store.Render;
    mc := PlugInput.MousePos;
    Render.TextureClear();
    Prim2D.DrawTriangle(
      mc, mc + G2Vec2(32, 16), mc + G2Vec2(8, 32),
      $ffaaaaaa
    );
  end;
end;

procedure TMyApp.UpdateGame;
  var v: TG2Vec3;
  var i, j: Integer;
  var f: TParticleFlame;
begin
  if not Pause and (getTickCount - StoreCloseTime > 200) then
  begin
    Scene.Update(UpdateQuery);
    for i := 0 to CharsToFree.Count - 1 do
    TG2SGChar(CharsToFree[i]).Free;
    CharsToFree.Clear;
    if (RagdollCount > 0)
    or (PhysObjectCount > 0) then
    Phys.PhysUpdater.IncreaseUpdates;
    GroupItems.Update;
    for i := 0 to Scene.Chars.Count - 1 do
    if (TG2SGChar(Scene.Chars[i]) is TCharacter)
    and (not TCharacter(Scene.Chars[i]).Dieing)
    and (not TCharacter(Scene.Chars[i]).Dead) then
    begin
      for j := i + 1 to Scene.Chars.Count - 1 do
      if (TG2SGChar(Scene.Chars[j]) is TCharacter)
      and (not TCharacter(Scene.Chars[j]).Dieing)
      and (not TCharacter(Scene.Chars[j]).Dead) then
      CollideChars(TG2SGChar(Scene.Chars[i]), TG2SGChar(Scene.Chars[j]));
    end;
    if App.AppTime - LastFlameTime > 200 then
    begin
      LastFlameTime := App.AppTime;
      for i := 0 to High(MonsterSpawn) do
      begin
        f := TParticleFlame.Create;
        f.X := MonsterSpawn[i].x;
        f.Y := MonsterSpawn[i].y + 3;
        f.Z := MonsterSpawn[i].z;
        f.Update;
        Particles.AddParticle(f);
      end;
    end;
    Particles.Update;
    if not PlayerDead then
    begin
      v := Player.Transform.GetTranslation;
      Cam.SetView(v + G2Vec3(-22, 60, -22), v + G2Vec3(0, -3, 0), G2Vec3(0, 1, 0));
    end;
    if (App.AppTime - WaveTime > 8000) then
    begin
      if not WaveFinished then
      begin
        if MonsterWaves[CurWave].CurMonster < MonsterWaves[CurWave].MonsterCount then
        begin
          if not PlayerDead
          and (App.AppTime - LastSpawn > MonsterWaves[CurWave].SpawnInterval)
          and (TMonster.NumMonsters < 20) then
          begin
            LastSpawn := App.AppTime;
            case MonsterWaves[CurWave].Monsters[MonsterWaves[CurWave].CurMonster] of
              0: TZombie.Create(Scene);
              1: TJason.Create(Scene);
            end;
            Inc(MonsterWaves[CurWave].CurMonster);
          end;
        end
        else if MonstersLeft = 0 then
        begin
          WaveFinished := True;
          WaveFinishedTime := App.AppTime;
        end;
      end;
    end
    else
    begin
      if (App.AppTime - WaveTime > 2000)
      and not WaveStoreOpened then
      begin
        if not InGameMenuIsOpen
        and not StoreIsOpen then
        StoreOpen;
        WaveStoreOpened := True;
      end;
    end;
    if WaveFinished and (App.AppTime - WaveFinishedTime > 3000) then
    begin
      if CurWave = High(MonsterWaves) then
      begin
        LevelEnd;
        GameState := gsGameOver;
      end
      else
      begin
        Inc(CurWave);
        WaveTime := App.AppTime;
        WaveStoreOpened := False;
        WaveFinished := False;
        MonstersLeft := 0;
      end;
    end;
    if PlayerDead and (App.AppTime - PlayerDeadTime > 6000) then
    begin
      LevelEnd;
      GameState := gsGameOver;
    end;
  end;
end;

procedure TMyApp.RenderGameOver;
  var tsx0, tsy0, tsx1, tsy1: Single;
begin
  Gfx.TextureStageStages.ColorOp[1] := D3DTOP_MODULATE;
  Gfx.TextureStageStages.ColorArg1[1] := D3DTA_CURRENT;
  Gfx.TextureStageStages.ColorArg2[1] := D3DTA_TEXTURE;
  tsx0 := -G2TimeInterval(110000);
  tsy0 := G2TimeInterval(18000);
  tsx1 := G2TimeInterval(110000);
  tsy1 := G2TimeInterval(18000) + 0.5;
  Render2D.DrawQuad2(
    G2Vec2(0, 0), G2Vec2(Gfx.Params.Width, 0),
    G2Vec2(0, Gfx.Params.Height), G2Vec2(Gfx.Params.Width, Gfx.Params.Height),
    $ffffffff, $ffffffff, $ff000000, $ff000000,
    TexMenuBG,
    G2Vec2(0 + tsx0, 0 + tsy0), G2Vec2(1 + tsx0, 0 + tsy0),
    G2Vec2(0 + tsx0, 1 + tsy0), G2Vec2(1 + tsx0, 1 + tsy0),
    TexMenuBG,
    G2Vec2(0 + tsx1, 0 + tsy1), G2Vec2(1 + tsx1, 0 + tsy1),
    G2Vec2(0 + tsx1, 1 + tsy1), G2Vec2(1 + tsx1, 1 + tsy1)
  );
  Gfx.TextureStageStages.SetDefaults;
  Render2D.DrawRect(
    (Gfx.Params.Width - TexGameOver.Width) * 0.5,
    (Gfx.Params.Height - TexGameOver.Height) * 0.5,
    TexGameOver
  );
end;

procedure TMyApp.UpdateGameOver;
begin

end;

procedure TMyApp.RenderLogo;
  var t: DWord;
  var f: Single;
  var b: Byte;
begin
  t := App.AppTime - TimeLogo;
  f := 0.5 + (t / 4000);
  Render.Clear(False, False, True, $ff000000);
  RenderModes.FilteringLinear();
  Render2D.DrawRect(
    (Gfx.Params.Width - TexLogo.Width) * 0.5,
    (Gfx.Params.Height - TexLogo.Height) * 0.5,
    TexLogo.Width, TexLogo.Height, $ffffffff, TexLogo,
    0, f, f, False, False
  );
  RenderModes.FilteringPoint();
  if (t <= 1000) then
  begin
    f := 1 - (t / 1000); if f > 1 then f := 1;
    b := Round(f * 255);
    Prim2D.DrawRect(0, 0, Gfx.Params.Width, Gfx.Params.Height, G2Color(0, 0, 0, b));
  end
  else if (t > 3000) then
  begin
    f := (t - 3000) / 1000; if f > 1 then f := 1;
    b := Round(f * 255);
    Prim2D.DrawRect(0, 0, Gfx.Params.Width, Gfx.Params.Height, G2Color(0, 0, 0, b));
  end;
end;

procedure TMyApp.UpdateLogo;
begin
  if (App.AppTime - TimeLogo > 4000) then
  GameState := gsMenu;
end;

procedure TMyApp.RenderCrossair;
  var Pos: TG2Vec2;
  var i: Integer;
  var Ang, a, s, c: Single;
begin
  Pos := PlugInput.MousePos;
  if Player.Healing then
  begin
    i := 50 + Round(Abs(Sin(G2PiTime(150))) * 100);
    Render2D.DrawRect(
      Pos.x - TexHeal.Width, Pos.y - TexHeal.Height, TexHeal.Width, TexHeal.Height,
      G2Color(255, 255, 255, i), TexHeal
    );
  end
  else
  begin
    if Player.Reloading then
    begin
      a := (App.AppTime - Player.ReloadTime) / App.Weapons[Player.CurWeaponID].ReloadTime;
      if a > 1 then a := 1;
      Render.TextureClear();
      Prim2D.DrawCircleHollow2Col(Pos, 20, 25, $6000ff00, $0000ff00);
      Prim2D.DrawCircleHollow(Pos, G2LerpFloat(20, 0, a), 20, $6000ff00);
      Prim2D.DrawCircleHollow(Pos, 0, G2LerpFloat(20, 0, a), $60ff0000);
    end;
    Ang := G2PiTime();
    for i := 0 to 3 do
    begin
      a := (i / 4) * TwoPi + Ang;
      G2SinCos(a, s, c);
      Render2D.DrawRect(
        Pos.x - TexArrow.Width * 0.5 + c * 20, Pos.y - TexArrow.Height * 0.5 + s * 20,
        TexArrow.Width, TexArrow.Height, $ffffffff,
        TexArrow, a, 1, 1, False, False
      );
    end;
  end;
end;

procedure TMyApp.RenderScene;
  type TVarDepthLinear = packed record
    W: TFloat4x3;
    WVP: TFloat4x4;
    DepthDirW: TG2Vec4;
    DepthMaxRcp: Single;
  end;
  var VarDepthLinear: TVarDepthLinear;
  var ShadowPos, ShadowTarget, ShadowDir: TG2Vec3;
  var DepthDir: TG2Vec4;
  var m, VP, W, V, P, WVP, ShadowVP, DepthWVPT, DepthWVP: TG2Mat;
  var i, j, g, l, VSID, PSID: Integer;
  var Geom: TG2SGGeom;
  var TexMod: TG2Vec2;
  var AABox: TG2AABox;
  var Frustum: TG2Frustum;
  var c: TG2SGChar;
  var v3: TG2Vec3;
begin
  ShadowPos := Scene.FindNode('Direct01').Transform.GetTranslation;
  ShadowTarget := Scene.FindNode('Direct01Target').Transform.GetTranslation;
  ShadowDir := ShadowTarget - ShadowPos;
  ShadowTarget := Cam.Target^ + ShadowDir * 0.5;// Player.Transform.GetTranslation + ShadowDir * 0.5;
  ShadowPos := ShadowTarget - ShadowDir;
  ShadowDir.Normalize;
  DepthDir := ShadowDir;
  DepthDir.w := ShadowDir.Dot(ShadowPos);
  W.SetIdentity;
  V.SetView(ShadowPos, ShadowTarget, G2Vec3(0, 1, 0));
  P.SetOrthogonal(100, 100, 1, 1000);
  ShadowVP := V * P;
  Frustum.RefV := @V;
  Frustum.RefP := @P;
  Frustum.Update;
  Gfx.SetRenderTargetTexture2D(TexShadowDepthRT);
  Gfx.SetDepthStencilSurface(SurfShadowDepthDS);
  Render.Clear(False, True, True, $ffff0000);

  Gfx.RenderStates.ZEnable := True;
  ShaderUtils.Technique := 'DepthLinear';
  VarDepthLinear.DepthDirW := DepthDir;
  VarDepthLinear.DepthMaxRcp := 1 / 1000;
  ShaderUtils.BeginEffect(nil);
  ShaderUtils.BeginPass(0);
  for i := 0 to Scene.Chars.Count - 1 do
  if (TG2SGChar(Scene.Chars[i]).Mesh <> nil)
  and (Frustum.BoxInFrustum(TG2SGChar(Scene.Chars[i]).AABox)) then
  begin
    c := TG2SGChar(Scene.Chars[i]);
    for j := 0 to c.Mesh.GeomCount - 1 do
    if c.MeshInst.GeomProperties[j].Visible then
    begin
      VSID := c.Mesh.Geoms[j].MaxWeights;
      ShaderUtils.SetInt('VS_Index', VSID);
      if c.MeshInst.Mesh.Geoms[j].Skinned then
      begin
        ShaderUtils.SetMatrixArray('g_SkinPallete', PD3DXMatrix(c.MeshInst.SkinTransforms[j]), c.Mesh.Geoms[j].BoneCount);
        W := c.Transform;
      end
      else
      W := c.MeshInst.NodeTransforms[c.Mesh.Geoms[j].NodeID].TransformRen * c.Transform;
      WVP := W * ShadowVP;
      VarDepthLinear.W := W;
      VarDepthLinear.WVP := WVP;
      ShaderUtils.SetValue('VarDepthLinear', @VarDepthLinear, SizeOf(VarDepthLinear));
      ShaderUtils.CommitChanges;
      for g := 0 to c.Mesh.Geoms[j].MaterialCount - 1 do
      c.Mesh.Geoms[j].Mesh.DrawSubset(g);
    end;
  end;
  for i := 0 to ExtraGeoms.Count - 1 do
  if PExtraGeom(ExtraGeoms[i])^.Shadow then
  begin
    for j := 0 to PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.GeomCount - 1 do
    if PExtraGeom(ExtraGeoms[i])^.MeshInst.GeomProperties[j].Visible then
    begin
      VSID := PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].MaxWeights;
      ShaderUtils.SetInt('VS_Index', VSID);
      if PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].Skinned then
      begin
        ShaderUtils.SetMatrixArray('g_SkinPallete', PD3DXMatrix(PExtraGeom(ExtraGeoms[i])^.MeshInst.SkinTransforms[j]), PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].BoneCount);
        W := PExtraGeom(ExtraGeoms[i])^.Transform;
      end
      else
      W := PExtraGeom(ExtraGeoms[i])^.MeshInst.NodeTransforms[PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].NodeID].TransformRen * PExtraGeom(ExtraGeoms[i])^.Transform;
      WVP := W * ShadowVP;
      VarDepthLinear.W := W;
      VarDepthLinear.WVP := WVP;
      ShaderUtils.SetValue('VarDepthLinear', @VarDepthLinear, SizeOf(VarDepthLinear));
      ShaderUtils.CommitChanges;
      for g := 0 to PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].MaterialCount - 1 do
      PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].Mesh.DrawSubset(g);
    end;
  end;
  ShaderUtils.EndPass;
  ShaderUtils.EndEffect;

  Gfx.SetRenderTargetDefault;
  Gfx.SetDepthStencilDefault;

  Scene.QueryFetch(Gfx.Transforms.Frustum, Query);
  RenderModes.FilteringLinear();
  RenderModes.FilteringPoint();
  Gfx.Transforms.PushW;
  VP := Gfx.Transforms.VP;
  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.SetVec3('g_CamPos', Gfx.Transforms.Vpos);
  Shaders.SetVec3('g_LightDir', -ShadowDir);
  Shaders.SetVector('g_DepthDir', DepthDir);
  Shaders.SetFloat('g_DepthMaxRcp', 1 / 1000);
  Shaders.SetVec2('g_DepthRes', G2Vec2(TexShadowDepthRT.Width, TexShadowDepthRT.Height));
  Shaders.SetVec2('g_DepthResRcp', G2Vec2(1 / TexShadowDepthRT.Width, 1 / TexShadowDepthRT.Height));
  Shaders.SetTexture('TexDepth', TexShadowDepthRT.Texture);
  Shaders.BeginPass(0);
  for i := 0 to Query.GeomCount - 1 do
  begin
    Geom := Query.Geoms[i];
    WVP := Geom.Transform * VP;
    DepthWVP := Geom.Transform * ShadowVP;
    DepthWVPT := DepthWVP * TexShadowDepthRT.ProjMatrix;
    Shaders.SetMatrix('g_W', Geom.Transform);
    Shaders.SetMatrix('g_WVP', WVP);
    Shaders.SetMatrix('g_DepthWVPT', DepthWVPT);
    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
        else
        PSID := 0;
      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;
  Shaders.SetVec3('g_LightDir', -ShadowDir);
  for i := 0 to Query.CharCount - 1 do
  if (Query.Chars[i].Mesh <> nil) then
  begin
    if Query.Chars[i] is TCharacter then
    Shaders.SetFloat('g_LightAmount', TCharacter(Query.Chars[i]).LightAmount)
    else
    Shaders.SetFloat('g_LightAmount', 1);
    PSID := 4;
    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;
  Shaders.SetFloat('g_LightAmount', 1);
  for i := 0 to ExtraGeoms.Count - 1 do
  begin
    PSID := 4;
    Shaders.SetInt('PS_Index', PSID);
    for j := 0 to PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.GeomCount - 1 do
    if PExtraGeom(ExtraGeoms[i])^.MeshInst.GeomProperties[j].Visible then
    begin
      VSID := PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].MaxWeights + 1;
      Shaders.SetInt('VS_Index', VSID);
      if PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].Skinned then
      begin
        Shaders.SetMatrixArray('g_SkinPallete', PD3DXMatrix(PExtraGeom(ExtraGeoms[i])^.MeshInst.SkinTransforms[j]), PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].BoneCount);
        W := PExtraGeom(ExtraGeoms[i])^.Transform;
      end
      else
      W := PExtraGeom(ExtraGeoms[i])^.MeshInst.NodeTransforms[PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].NodeID].TransformRen * PExtraGeom(ExtraGeoms[i])^.Transform;
      WVP := W * VP;
      Shaders.SetMatrix('g_W', W);
      Shaders.SetMatrix('g_WVP', WVP);
      for g := 0 to PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].MaterialCount - 1 do
      begin
        Shaders.SetTexture('TexDiffuse0', PExtraGeom(ExtraGeoms[i])^.MeshInst.Materials[PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].Materials[g]].MapDiffuse.Texture);
        Shaders.CommitChanges;
        PExtraGeom(ExtraGeoms[i])^.MeshInst.Mesh.Geoms[j].Mesh.DrawSubset(g);
      end;
    end;
  end;
  Shaders.EndPass;
  Shaders.EndEffect;
  Gfx.RenderStates.AlphaTestEnable := False;
  Gfx.RenderStates.AlphaBlendEnable := True;

  {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
    v3 := PG2Vec3(@Query.Chars[i].Transform.e30)^ - Query.Chars[i].Step;
    Prim3D.DrawLine(v3, v3 + Query.Chars[i].Step, $800000ff);
    Prim3D.DrawSphere(v3.x, v3.y, v3.z, Query.Chars[i].Radius, $80ff0000);
    if Query.Chars[i].MeshInst.GeomProperties[j].Visible then
    begin
      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;
  RenderModes.FilteringPoint();
  Gfx.Transforms.PopW;
  Query^.CheckOcclusion(Gfx.Transforms.Frustum);
end;

procedure TMyApp.LevelBegin;
  var i, j: Integer;
  var ParamArr: TG2StrArrA;
  var v3: TG2Vec3;
  var r: TG2Ray;
  var GeomID: Integer;
  var FaceID: Word;
  var U, V, D: Single;
  var MapBuffer: Pointer;
  var MapSize: DWord;
begin
  RagdollCount := 0;
  Scene := TG2SceneGraph.Create(g2);
  Scene.SearchPathAdd('MapTexture');
  Scene.SearchPathAdd('MapLight');
  Scene.SearchPathAdd('Texture');
  g2.PackLinker.GetFileData('MapMesh', 'Arena', MapBuffer, MapSize);
  Scene.LoadG2M(MapBuffer, MapSize);
  Scene.MgrCharMeshes.CreateMeshFromPack('Player', 'Mesh', 'Player');
  Scene.MgrCharMeshes.CreateMeshFromPack('Zombie', 'Mesh', 'Zombie');
  Scene.MgrCharMeshes.CreateMeshFromPack('Jason', 'Mesh', 'Jason');
  Query := Scene.QueryCreate;
  Query^.DistanceSortEnable := True;
  UpdateQuery := Scene.QueryCreate;
  for i := 0 to Scene.Geoms.Count - 1 do
  begin
    ParamArr := G2StrExplode(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;
    end;
  end;
  MonsterSpawn := nil;
  for i := 0 to Scene.Nodes.Count - 1 do
  begin
    ParamArr := G2StrExplode(TG2SGNode(Scene.Nodes[i]).Name, '_');
    for j := 0 to High(ParamArr) do
    begin
      if ParamArr[j] = 'Monster' then
      begin
        SetLength(MonsterSpawn, Length(MonsterSpawn) + 1);
        v3 := TG2SGNode(Scene.Nodes[i]).Transform.GetTranslation;
        r.SetValue(v3, G2Vec3(0, -1, 0));
        if Scene.CollideRay(r, GeomID, FaceID, U, V, D) then
        begin
          MonsterSpawn[High(MonsterSpawn)] := r.Origin + r.Dir * (D - 1);
        end
        else
        MonsterSpawn[High(MonsterSpawn)] := v3;
      end;
    end;
  end;
  Phys.Init;
  Phys.StartThread;
  TMonster.NumMonsters := 0;
  Player := TPlayer.Create(Scene);
  LastSpawn := App.AppTime;
  CharsToFree.Clear;
  ExtraGeoms.Clear;
  Medkits.Clear;
  GrenBoxes.Clear;
  GroupItems := TG2Group.Create;
  PlayerDead := False;
  LastFlameTime := App.AppTime;
  Store.Reset;
  MonsterWaves[0].Reset;
  MonsterWaves[0].SpawnInterval := 2500;
  MonsterWaves[0].AddMonsters(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  MonsterWaves[0].AddMonsters(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  MonsterWaves[0].AddMonsters(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  MonsterWaves[0].AddMonsters(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  MonsterWaves[1].Reset;
  MonsterWaves[1].SpawnInterval := 2500;
  MonsterWaves[1].AddMonsters(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  MonsterWaves[1].AddMonsters(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  MonsterWaves[1].AddMonsters(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  MonsterWaves[1].AddMonsters(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  MonsterWaves[1].AddMonsters(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
  MonsterWaves[1].AddMonsters(0, 0, 0, 0, 0, 0, 1, 1, 1, 1);
  MonsterWaves[2].Reset;
  MonsterWaves[2].SpawnInterval := 2000;
  MonsterWaves[2].AddMonsters(0, 0, 1, 0, 0, 1, 0, 0, 1, 0);
  MonsterWaves[2].AddMonsters(0, 0, 1, 0, 0, 1, 0, 0, 1, 0);
  MonsterWaves[2].AddMonsters(0, 0, 1, 0, 0, 1, 0, 0, 1, 0);
  MonsterWaves[2].AddMonsters(0, 0, 1, 0, 0, 1, 0, 0, 1, 0);
  MonsterWaves[2].AddMonsters(0, 0, 1, 0, 0, 1, 0, 0, 1, 0);
  MonsterWaves[2].AddMonsters(0, 0, 1, 0, 0, 1, 0, 0, 1, 0);
  MonsterWaves[2].AddMonsters(0, 0, 1, 0, 0, 1, 0, 0, 1, 0);
  MonsterWaves[3].Reset;
  MonsterWaves[3].SpawnInterval := 2000;
  MonsterWaves[3].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[3].AddMonsters(0, 1, 1, 0, 0, 1, 0, 0, 1, 0);
  MonsterWaves[3].AddMonsters(0, 0, 1, 0, 0, 1, 0, 1, 1, 0);
  MonsterWaves[3].AddMonsters(0, 0, 1, 1, 0, 1, 1, 0, 1, 0);
  MonsterWaves[3].AddMonsters(0, 0, 1, 0, 1, 1, 0, 0, 1, 0);
  MonsterWaves[3].AddMonsters(0, 0, 1, 0, 1, 1, 0, 0, 1, 0);
  MonsterWaves[3].AddMonsters(0, 0, 1, 1, 0, 1, 1, 0, 1, 0);
  MonsterWaves[3].AddMonsters(0, 0, 1, 0, 0, 1, 0, 1, 1, 0);
  MonsterWaves[4].Reset;
  MonsterWaves[4].SpawnInterval := 2000;
  MonsterWaves[4].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[4].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[4].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[4].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[4].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[4].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[4].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[4].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[4].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[5].Reset;
  MonsterWaves[5].SpawnInterval := 2000;
  MonsterWaves[5].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[5].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[5].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[5].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[5].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[5].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[5].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[5].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[5].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[5].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[6].Reset;
  MonsterWaves[6].SpawnInterval := 1500;
  MonsterWaves[6].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[6].AddMonsters(1, 1, 1, 0, 1, 1, 0, 1, 1, 1);
  MonsterWaves[6].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[6].AddMonsters(1, 1, 1, 0, 1, 1, 0, 1, 1, 1);
  MonsterWaves[6].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[6].AddMonsters(1, 1, 1, 0, 1, 1, 0, 1, 1, 1);
  MonsterWaves[6].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[6].AddMonsters(1, 1, 1, 0, 1, 1, 0, 1, 1, 1);
  MonsterWaves[6].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[6].AddMonsters(1, 1, 1, 0, 1, 1, 0, 1, 1, 1);
  MonsterWaves[6].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[6].AddMonsters(1, 1, 1, 0, 1, 1, 0, 1, 1, 1);
  MonsterWaves[7].Reset;
  MonsterWaves[7].SpawnInterval := 1500;
  MonsterWaves[7].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[7].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[7].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[7].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[7].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[7].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[7].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[7].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[7].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[7].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[7].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[7].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[7].AddMonsters(1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[7].AddMonsters(1, 0, 1, 0, 0, 1, 0, 0, 1, 1);
  MonsterWaves[8].Reset;
  MonsterWaves[8].SpawnInterval := 1000;
  MonsterWaves[8].AddMonsters(0, 1, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[8].AddMonsters(1, 0, 1, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[8].AddMonsters(1, 1, 0, 1, 1, 1, 1, 1, 1, 1);
  MonsterWaves[8].AddMonsters(1, 1, 1, 0, 1, 1, 1, 1, 1, 0);
  MonsterWaves[8].AddMonsters(1, 1, 1, 1, 0, 1, 1, 1, 0, 1);
  MonsterWaves[8].AddMonsters(1, 1, 1, 1, 1, 0, 1, 0, 1, 1);
  MonsterWaves[8].AddMonsters(1, 1, 1, 1, 1, 1, 0, 1, 1, 1);
  MonsterWaves[8].AddMonsters(1, 1, 1, 1, 1, 0, 1, 0, 1, 1);
  MonsterWaves[8].AddMonsters(1, 1, 1, 1, 0, 1, 1, 1, 0, 1);
  MonsterWaves[8].AddMonsters(1, 1, 1, 0, 1, 1, 1, 1, 1, 0);
  MonsterWaves[8].AddMonsters(1, 1, 0, 1, 1, 1, 1, 1, 0, 1);
  MonsterWaves[8].AddMonsters(1, 0, 1, 1, 1, 1, 1, 0, 1, 1);
  MonsterWaves[8].AddMonsters(0, 1, 1, 1, 1, 1, 0, 1, 1, 1);
  MonsterWaves[8].AddMonsters(1, 0, 1, 1, 1, 0, 1, 1, 1, 1);
  MonsterWaves[8].AddMonsters(1, 1, 0, 1, 0, 1, 1, 1, 1, 1);
  MonsterWaves[8].AddMonsters(1, 1, 1, 0, 1, 1, 1, 1, 1, 1);
  MonsterWaves[9].Reset;
  MonsterWaves[9].SpawnInterval := 1000;
  MonsterWaves[9].AddMonsters(1, 1, 1, 1, 1, 0, 1, 1, 1, 1);
  MonsterWaves[9].AddMonsters(1, 1, 1, 1, 0, 1, 0, 1, 1, 1);
  MonsterWaves[9].AddMonsters(1, 1, 1, 0, 1, 1, 1, 0, 1, 1);
  MonsterWaves[9].AddMonsters(1, 1, 0, 1, 1, 1, 1, 1, 0, 1);
  MonsterWaves[9].AddMonsters(1, 0, 1, 1, 1, 1, 1, 1, 1, 0);
  MonsterWaves[9].AddMonsters(0, 1, 1, 1, 1, 1, 1, 1, 0, 1);
  MonsterWaves[9].AddMonsters(1, 0, 1, 1, 1, 1, 1, 0, 1, 1);
  MonsterWaves[9].AddMonsters(1, 1, 0, 1, 1, 1, 0, 1, 1, 1);
  MonsterWaves[9].AddMonsters(1, 1, 1, 0, 1, 0, 1, 1, 1, 1);
  MonsterWaves[9].AddMonsters(1, 1, 1, 1, 0, 1, 1, 1, 1, 1);
  MonsterWaves[9].AddMonsters(1, 1, 1, 0, 1, 1, 1, 1, 1, 1);
  MonsterWaves[9].AddMonsters(1, 1, 0, 1, 0, 1, 1, 1, 1, 1);
  MonsterWaves[9].AddMonsters(1, 0, 1, 1, 1, 0, 1, 1, 1, 1);
  MonsterWaves[9].AddMonsters(0, 1, 1, 1, 1, 1, 0, 1, 1, 1);
  MonsterWaves[9].AddMonsters(1, 0, 1, 1, 1, 1, 1, 0, 1, 1);
  MonsterWaves[9].AddMonsters(1, 1, 0, 1, 1, 1, 1, 1, 0, 1);
  MonsterWaves[9].AddMonsters(1, 1, 1, 0, 1, 1, 1, 1, 1, 0);
  MonsterWaves[9].AddMonsters(1, 1, 1, 1, 0, 1, 1, 1, 0, 1);
  MonsterWaves[9].AddMonsters(1, 1, 1, 1, 1, 0, 1, 0, 1, 1);
  MonsterWaves[9].AddMonsters(1, 1, 1, 1, 1, 1, 0, 1, 1, 1);
  CurWave := 0;
  MonstersLeft := 0;
  WaveTime := App.AppTime;
  WaveStoreOpened := True;
  WaveFinished := False;
  LevelOpen := True;
end;

procedure TMyApp.LevelEnd;
begin
  GroupItems.FreeItems;
  GroupItems.Free;
  Phys.StopThread;
  Scene.QueryDestroy(UpdateQuery);
  Scene.QueryDestroy(Query);
  Scene.Free;
  Phys.UnInit;
  LevelOpen := False;
end;

procedure TMyApp.InGameMenuOpen;
begin
  Pause := True;
  InGameMenuIsOpen := True;
end;

procedure TMyApp.InGameMenuClose;
begin
  Pause := False;
  InGameMenuIsOpen := False;
end;

procedure TMyApp.StoreOpen;
begin
  Store.Init;
  Pause := True;
  StoreIsOpen := True;
  if not PlayerDead then
  begin
    Store.PlayerGrenades := Player.GrenadeCount;
    Store.PlayerMedkits := Player.MedKitCount;
  end;
end;

procedure TMyApp.StoreClose;
begin
  StoreCloseTime := GetTickCount;
  Pause := False;
  StoreIsOpen := False;
  if (Store.PlayerSlots[0] = -1) then
  begin
    if (Store.PlayerSlots[1] = -1) then
    Store.PlayerSlots[0] := 0
    else
    begin
      Store.PlayerSlots[0] := Store.PlayerSlots[1];
      Store.PlayerSlots[1] := -1;
    end;
  end;
  if not PlayerDead then
  begin
    Player.SetWeapon(Store.PlayerSlots[0]);
    Player.GrenadeCount := Store.PlayerGrenades;
    Player.MedKitCount := Store.PlayerMedkits;
  end;
end;

procedure TMyApp.BtnNewGame;
begin
  GameState := gsGame;
  RenderLoading;
  LevelBegin;
end;

procedure TMyApp.BtnExtras;
begin

end;

procedure TMyApp.BtnQuit;
begin
  Running := False;
end;

procedure TMyApp.BtnContinue;
begin
  if InGameMenuIsOpen then
  InGameMenuClose;
end;

procedure TMyApp.AddWeapon(
  const Name: AnsiString;
  const MFID: Integer;
  const ShotLag: DWord;
  const Damage: Integer;
  const ShotCount: Integer;
  const AimAnim: DWord;
  const Spread: Single;
  const Force: Single;
  const Ammo: Integer;
  const ReloadTime: DWord;
  const Cost: Integer
);
begin
  SetLength(Weapons, Length(Weapons) + 1);
  Weapons[High(Weapons)].Name := Name;
  Weapons[High(Weapons)].MFID := MFID;
  Weapons[High(Weapons)].ShotLag := ShotLag;
  Weapons[High(Weapons)].Damage := Damage;
  Weapons[High(Weapons)].ShotCount := ShotCount;
  Weapons[High(Weapons)].AimAnim := AimAnim;
  Weapons[High(Weapons)].Spread := Spread;
  Weapons[High(Weapons)].Force := Force;
  Weapons[High(Weapons)].ReloadTime := ReloadTime;
  Weapons[High(Weapons)].Ammo := Ammo;
  Weapons[High(Weapons)].Cost := Cost;
end;
//TMyApp END

//TPhysUpdateThread BEGIN
constructor TPhysUpdateThread.Create;
begin
  inherited Create(True);
  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;
      if UpdatesToDo > 0 then
      begin
        Phys.CanChange := False;
        while Phys.Changing do;
        for i := 0 to UpdatesToDo - 1 do
        NewtonUpdate(Phys.World, 1);
        Phys.CanChange := True;
      end;
    end;
  end;
end;

procedure TPhysUpdateThread.IncreaseUpdates;
begin
  CS.Enter;
  try
  Inc(UpdatesNeeded);
  finally
    CS.Leave;
  end;
end;
//TPhysUpdateThread END

//TPhys BEGIN
procedure TPhys.Init;
  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);
  NewtonSetThreadsCount(World, 2);
  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);
  CanChange := True;
  Changing := False;
end;

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

procedure TPhys.StartThread;
begin
  PhysUpdater := TPhysUpdateThread.Create;
  PhysUpdater.Priority := tpLower;
  PhysUpdater.Start;
end;

procedure TPhys.StopThread;
begin
  PhysUpdater.Terminate;
  PhysUpdater.WaitFor;
  PhysUpdater.Free;
end;

procedure TPhys.ChangeBegin;
begin
  while not CanChange do ;
  Changing := True;
end;

procedure TPhys.ChangeEnd;
begin
  Changing := False;
end;
//TPhys END

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