unit G2App;

interface

uses
  Classes,
  Gen2MP,
  G2Types,
  G2Math,
  G2Utils,
  G2DataManager,
  Types,
  SysUtils,
  Math;

type
  TShot = class;
  TShotParticle = class;
  TShotLaser = class;
  TShotMissile = class;
  TFactionObject = class;
  TTrace = class;
  TTraceMissile = class;
  TPLayerPowerup = class;
  TBoss = class;

  CShotParticle = class of TShotParticle;
  CShotLaser = class of TShotLaser;
  CShotMissile = class of TShotMissile;
  CPlayerPowerup = class of TPlayerPowerup;
  CBoss = class of TBoss;

  TLoadObjectClass = (loTexture2D, loFont);
  TLoadSourceType = (lsFile, lsBuffer);
  PLoadObject = ^TLoadObject;
  TLoadObject = record
    LoadSource: TLoadSourceType;
    ObjectClass: TLoadObjectClass;
    TargetObject: TObject;
    SourceFile: WideString;
    SourceBuffer: Pointer;
    SourceBufferSzie: Int64;
  end;

  TLoader = object
    Objects: TG2QuickList;
    TotalCount: Integer;
    procedure Add(var Target: TG2Texture2D; const Source: WideString); overload;
    procedure Add(var Target: TG2Texture2D; const Buffer: Pointer; const Size: Int64); overload;
    procedure Add(var Target: TG2Font; const Source: WideString); overload;
    procedure Add(var Target: TG2Font; const Buffer: Pointer; const Size: Int64); overload;
    procedure LoadBegin;
    procedure Next;
    function Progress: Single;
  end;

  TSound = object
  private
    function GetInst: TG2SoundInst;
  public
    Buffer: TG2SoundBuffer;
    Inst: TG2QuickList;
    procedure Load(const FileName: WideString); overload;
    procedure Load(const Buf: Pointer; const Size: Int64); overload;
    procedure Play;
    procedure Loop;
    procedure Free;
  end;
  PSound = ^TSound;

  TInputMethod = class
  public
    constructor Create; virtual;
    destructor Destroy; override;
    procedure Update; virtual;
    procedure DoMove(const v: TG2Vec2);
  end;

  TInputMethodKeyboard = class (TInputMethod)
  public
    procedure Update; override;
  end;

  TImg = object
  public
    Path: AnsiString;
    Name: AnsiString;
    Texture: TG2Texture2D;
    Loaded: Boolean;
    procedure Load;
    procedure Release;
  end;
  PImg = ^TImg;

  TImgList = object
  private
    _Path: FileString;
    _List: TG2QuickList;
    function GetImage(const Index: Integer): PImg;
    function GetImageCount: Integer;
  public
    property Images[const Index: Integer]: PImg read GetImage;
    property ImageCount: Integer read GetImageCount;
    procedure Initilaize(const Path: AnsiString);
    procedure Finalzie;
    procedure Release;
    procedure Load;
    function Find(const Name: AnsiString): PImg;
  end;
  PImgList = ^TImgList;

  TEnemyType = record
    ID: Integer;
    ImgName: AnsiString;
  end;
  PEnemyType = ^TEnemyType;

  TPathWaypoint = record
    Pos: TG2Vec2;
    Normal: TG2Vec2;
    Speed: Single;
  end;

  TPath = object
    Waypoints: array of TPathWaypoint;
    function GetPos(const Pt0, Pt1: Integer; const s: Single): TG2Vec2;
  end;
  PPath = ^TPath;

  TEnemyData = record
    EnemyType: PEnemyType;
    Count: Integer;
    Pos: Single;
    Spacing: Single;
    Velocity: Single;
    Path: PPath;
    CurEnemy: Integer;
    CurSpacing: Single;
  end;
  PEnemyData = ^TEnemyData;

  TEnemyDispatch = object
  public
    Enemies: array of TEnemyData;
    Sorted: TG2QuickSortList;
    Active: TG2QuickList;
    CurEnemy: Integer;
    procedure Initialize;
    procedure Update;
  end;

  TGroundLevel = record
    Img: PImg;
    Landscape: array[0..20, 0..400] of TG2Color;
    Visible: array[0..19, 0..399] of Boolean;
  end;

  TLevelState = (lsPlaying, lsBoss, lsFinished);

  TLevel = object
  public
    VSceneSize: TG2Vec2;
    VScreenSize: TG2Vec2;
    VGroundGridSize: TG2Vec2;
    VCamPos: TG2Vec2;
    VScreenSpeed: Single;
    Resolution: Single;
    EnemyTypes: array of TEnemyType;
    Ground: array of TGroundLevel;
    Paths: array of TPath;
    EnemyDispatch: TEnemyDispatch;
    GameObjects: TG2QuickList;
    GameObjectsDead: TG2QuickList;
    Shots: TG2QuickList;
    ObjectsSorted: TG2QuickSortList;
    Factions: array[0..1] of TG2QuickList;
    State: TLevelState;
    LevelEndTime: IntU32;
    BossClass: array[0..0] of CBoss;
    BossID: Integer;
    procedure Adjust;
    procedure Render;
    procedure RenderUI;
    procedure Update;
    procedure Initialize;
    procedure Finalize;
    procedure LoadLevel(const LevelID: Integer);
    procedure EndLevel;
    procedure Clear;
    function PosVirtualToScreen(const v: TG2Vec2; const Altitude: Single = 1): TG2Vec2;
    function PosScreenToVirtual(const v: TG2Vec2; const Altitude: Single = 1): TG2Vec2;
    function SizeVirtualToScreen(const s: Single; const Altitude: Single = 1): Single;
    function SizeScreenToVirtual(const s: Single; const Altitude: Single = 1): Single;
    procedure RenderGround;
    function EnemyTypeAdd(const ID: Integer): Integer;
    function EnemyTypeFind(const ID: Integer): PEnemyType;
    function CreateShotParticle(const ShotClass: CShotParticle; const Pos, Dir, Size: TG2Vec2; const Vel: Single): TShotParticle;
    function CreateShotLaser(const ShotClass: CShotLaser; const Pos, Dir: TG2Vec2; const Size: Single): TShotLaser;
    function CreateShotMissile(const ShotClass: CShotMissile; const Pos, Dir, Size: TG2Vec2; const Vel: Single): TShotMissile;
    procedure CreateExplosion(const Pos: TG2Vec2);
    procedure CreatePlasmaBlast(const Pos: TG2Vec2);
    procedure CreateSparks(const Pos: TG2Vec2);
    procedure CreatePowerup(const PowerupClass: CPlayerPowerup; const Pos: TG2Vec2);
  end;

  TProfile = object
    Name: AnsiString;
  end;
  PProfile = ^TProfile;

  TMenu = class
  public
    SubMenu: TMenu;
    constructor Create; virtual;
    destructor Destroy; override;
    procedure OnRender;
    procedure OnUpdate;
    procedure OnMouseDown(const x, y: Integer);
    procedure OnMouseUp(const x, y: Integer);
    procedure OnKeyDown(const Key: Integer);
    procedure OnPrint(const Char: AnsiChar);
    procedure Render; virtual;
    procedure Update; virtual;
    procedure Print(const Char: AnsiChar); virtual;
    procedure MouseDown(const x, y: Integer); virtual;
    procedure MouseUp(const x, y: Integer); virtual;
    procedure KeyDown(const Key: Integer); virtual;
    procedure Show; virtual; abstract;
  end;

  TMenuMainButton = record
    Caption: AnsiString;
    Proc: TG2ProcObj;
    Grad: Single;
    Enabled: Boolean;
  end;

  TMenuMain = class (TMenu)
  public
    TexBack: TG2Texture2D;
    TexMenu: TG2Texture2D;
    DRMenu: TG2Rect;
    DRButtons: TG2Rect;
    ButtonWidth: Single;
    ButtonHeight: Single;
    Buttons: array of TMenuMainButton;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
    procedure MouseUp(const x, y: Integer); override;
    procedure KeyDown(const Key: Integer); override;
    procedure Show; override;
    procedure AddButton(const Caption: AnsiString; const Proc: TG2ProcObj = nil);
    function PtInButton(const Pt: TG2Vec2): Integer;
    procedure BtnNewGame;
    procedure BtnExit;
  end;

  TMenuProfile = class (TMenu)
  public
    TexMenu: TG2Texture2D;
    TexMainMenu: TG2Texture2D;
    TexLoadProfile: TG2Texture2D;
    TexCreateProfile: TG2Texture2D;
    TexHighlight: TG2Texture2D;
    TexScroller: TG2Texture2D;
    r: TG2Rect;
    DRMainMenu: TG2Rect;
    DRLoadProfile: TG2Rect;
    DRCreateProfile: TG2Rect;
    DRHighlight: array [0..4] of TG2Rect;
    DRNames: array[0..4] of TG2Rect;
    DRNums: array[0..4] of TG2Rect;
    CRNames: array[0..4] of TG2Rect;
    Rects: array[0..2] of PG2Rect;
    CRMainMenu: TG2Rect;
    CRLoadProfile: TG2Rect;
    CRCreateProfile: TG2Rect;
    Grads: array[0..2] of PG2Float;
    GradMainMenu: Single;
    GradLoadProfile: Single;
    GradCreateProfile: Single;
    Pos: Integer;
    PosMax: Integer;
    Selection: Integer;
    ScrollerSize: TG2Vec2;
    ScrollerRect: TG2Rect;
    ScrollerDragging: Boolean;
    ScrollerDrag: Single;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
    procedure MouseDown(const x, y: Integer); override;
    procedure MouseUp(const x, y: Integer); override;
    procedure Show; override;
  end;

  TMenuCreateProfile = class (TMenu)
  public
    TexMenu: TG2Texture2D;
    TexCreate: TG2Texture2D;
    TexCancel: TG2Texture2D;
    r: TG2Rect;
    DRCreate: TG2Rect;
    DRCancel: TG2Rect;
    DRName: TG2Rect;
    CRCreate: TG2Rect;
    CRCancel: TG2Rect;
    Rects: array[0..1] of PG2Rect;
    Grads: array[0..1] of PG2Float;
    GradCreate: Single;
    GradCancel: Single;
    Carret: Integer;
    CurName: AnsiString;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
    procedure Print(const Char: AnsiChar); override;
    procedure MouseUp(const x, y: Integer); override;
    procedure KeyDown(const Key: Integer); override;
    procedure Show; override;
    function CommitCreate: Boolean;
  end;

  TGameObject = class
  public
    Alt: Single;
    Order: Single;
    Dead: Boolean;
    constructor Create; virtual;
    destructor Destroy; override;
    procedure Render; virtual; abstract;
    procedure Update; virtual; abstract;
    procedure Die;
    procedure OnDie; virtual;
  end;

  TClouds = class (TGameObject)
  public
    Img: PImg;
    BlendMode: Byte;
    Scale: Single;
    SpeedV: Single;
    SpeedH: Single;
    Color: TG2Color;
    Pos: TG2Vec2;
    procedure Render; override;
    procedure Update; override;
  end;

  TShot = class (TGameObject)
  public
    Faction: Integer;
    Damage: Integer;
    constructor Create; override;
    procedure ProcessHits; virtual; abstract;
    function CheckHit(const FactionObject: TFactionObject): Boolean; virtual; abstract;
    procedure OnHit; virtual;
  end;

  TShotColored = class (TShot)
  private
    _CurColor: Single;
    _Colors: array of TG2Color;
  public
    ColorInterval: IntU32;
    Brightness: Byte;
    constructor Create; override;
    procedure Update; override;
    procedure AddColor(const Color: TG2Color);
    function GetColor: TG2Color;
  end;

  TShotParticle = class (TShotColored)
  public
    Img: PImg;
    Size: TG2Vec2;
    Dir: TG2Vec2;
    Pos: TG2Vec2;
    Vel: Single;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
    procedure ProcessHits; override;
    function CheckHit(const FactionObject: TFactionObject): Boolean; override;
  end;

  TShotOmni = class (TShotParticle)
  public
    Angle: Single;
    VelAngular: Single;
    constructor Create; override;
    procedure Update; override;
    procedure Render; override;
  end;

  TLaserState = (lsIdle, lsPrime, lsFire);

  TShotLaser = class (TShotColored)
  public
    Img: PImg;
    ImgFlare: PImg;
    ImgDust: PImg;
    Pos: TG2Vec2;
    Dir: TG2Vec2;
    Size: Single;
    Dist: Single;
    State: TLaserState;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
    procedure ProcessHits; override;
  end;

  TShotMissile = class (TShot)
  public
    Img: PImg;
    ImgFlare: PImg;
    Size: TG2Vec2;
    Dir: TG2Vec2;
    Pos: TG2Vec2;
    Vel: Single;
    Trace: TTraceMissile;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
    procedure ProcessHits; override;
    function CheckHit(const FactionObject: TFactionObject): Boolean; override;
    procedure OnHit; override;
  end;

  TShotMissileDizzy = class (TShotMissile)
  private
    _IsDizzy: Boolean;
    _PrevDir: TG2Vec2;
    _NextDir: TG2Vec2;
    _TimeDizzy: IntU32;
    _TimeDizzyDuration: IntU32;
  public
    constructor Create; override;
    procedure Update; override;
  end;

  TShotBullet = class (TShotParticle)
  public
    constructor Create; override;
    procedure OnHit; override;
  end;

  TShotPlasma = class (TShotBullet)
  public
    constructor Create; override;
    procedure OnHit; override;
  end;

  TObjectFlip = (ofNone, ofVertical, ofHorizontal, ofBoth);

  TLevelObject = class (TGameObject)
  public
    Pos: TG2Vec2;
    Size: TG2Vec2;
    Ang: Single;
    Scale: Single;
    Color: TG2Color;
    Flip: TObjectFlip;
    Img: PImg;
    ShowStart: Single;
    ShowEnd: Single;
    procedure Render; override;
    procedure Update; override;
  end;

  TEffect = class (TGameObject)
  protected
    _TimeDelay: IntU32;
    _TimeCreated: IntU32;
    _TimeDuration: IntU32;
    t: Single;
  public
    Pos: TG2Vec2;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
    procedure SetTimes(const Delay, Duration: IntU32);
  end;

  CEffect = class of TEffect;

  TTrace = class (TEffect)
  public
    Vel: TG2Vec2;
    Acc: TG2Vec2;
    Restitution: Single;
    Dumping: Single;
    SpawnType: CEffect;
    SpawnInterval: IntU32;
    SpawnTime: IntU32;
    OrderInc: Single;
    CurOrder: Single;
    constructor Create; override;
    destructor Destroy; override;
    procedure Update; override;
    procedure OnCreateEffect(const Effect: TEffect); virtual;
  end;

  TTraceMissile = class (TTrace)
  public
    procedure OnCreateEffect(const Effect: TEffect); override;
  end;

  TEffectWave = class (TEffect)
  private
    Img: PImg;
  public
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
  end;

  TEffectExplosion = class (TEffect)
  private
    Img: PImg;
  public
    Scale: Single;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
  end;

  TEffectFlare = class (TEffect)
  private
    Img: PImg;
  public
    Size: Single;
    ScaleStart: Single;
    ScaleEnd: Single;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
  end;

  TEffectFlame = class (TEffect)
  private
    Img: PImg;
    fw, fh: Integer;
  public
    Ang: Single;
    Size: Single;
    ScaleStart: Single;
    ScaleEnd: Single;
    VelLinear: TG2Vec2;
    VelAngular: Single;
    Restitution: Single;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
  end;

  TFactionObject = class (TGameObject)
  private
    _Faction: Integer;
    procedure SetFaction(const Value: Integer);
  public
    xl, xh, yl, yh: Single;
    property Faction: Integer read _Faction write SetFaction;
    constructor Create; override;
    destructor Destroy; override;
    procedure Hit(const Amount: Integer); virtual; abstract;
  end;

  TEnemy = class (TFactionObject)
  private
    _Path: PPath;
    procedure SetPath(const Value: PPath);
  public
    Health: Integer;
    HealthMax: Integer;
    HitTime: IntU32;
    PathPos: TG2Vec2;
    PrevPos: TG2Vec2;
    Pos: TG2Vec2;
    Size: TG2Vec2;
    Speed: Single;
    Angle: Single;
    LookDir: TG2Vec2;
    Img: PImg;
    ImgFrame: Integer;
    ImgFlip: Boolean;
    Pt0, Pt1: Integer;
    g: Single;
    LastShotTime: IntU32;
    ShotLag: IntU32;
    ShotRounds: Integer;
    ShotRoundLag: Integer;
    ShotRoundCur: Integer;
    TracePlayer: Boolean;
    TracePlayerSpeed: Single;
    property Path: PPath read _Path write SetPath;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
    procedure OnDie; override;
    procedure OnShoot; virtual;
    procedure Hit(const Amount: Integer); override;
    procedure AdjustPos;
    procedure AdvancePos;
  end;

  TEnemyAlien01 = class (TEnemy)
  public
    constructor Create; override;
    procedure OnShoot; override;
  end;

  TEnemyAlien02 = class (TEnemy)
  public
    constructor Create; override;
    procedure OnShoot; override;
  end;

  TEnemyAlien03 = class (TEnemy)
  public
    constructor Create; override;
    procedure OnShoot; override;
  end;

  TEnemyAlien04 = class (TEnemy)
  public
    Laser: TShotLaser;
    LaserTimeState: IntU32;
    LaserDurationIdle: IntU32;
    LaserDurationPrime: IntU32;
    LaserDurationFire: IntU32;
    constructor Create; override;
    destructor Destroy; override;
    procedure Update; override;
  end;

  TBossHitProc = procedure (const Amount: Integer) of Object;

  TBossHitSpot = class (TFactionObject)
  public
    Boss: TBoss;
    R: TG2Rect;
    OnHitProc: TBossHitProc;
    HitTime: IntU32;
    constructor Create; override;
    procedure Render; override;
    procedure Update; override;
    procedure Hit(const Amount: Integer); override;
    procedure UpdateBounds;
    function GetHitGradient: Single;
  end;

  TBoss = class (TGameObject)
  private
    _HitSpots: TG2QuickList;
    _Collisions: TG2QuickList;
  public
    Faction: Integer;
    Pos: TG2Vec2;
    Vel: TG2Vec2;
    constructor Create; override;
    destructor Destroy; override;
    function HitSpotAdd(const R: TG2Rect; const Proc: TBossHitProc): TBossHitSpot;
    procedure HitSpotRemove(const HitSpot: TBossHitSpot);
    procedure CollisionAdd(const R: TG2Rect);
    procedure Update; override;
    procedure CheckCollisions;
  end;

  TBossExt = record
    Pos: TG2Vec2;
    Size: TG2Vec2;
    Img: PImg;
  end;

  TBossGun = record
    Pos: TG2Vec2;
    Size: TG2Vec2;
    Img: PImg;
    Pivot: TG2Vec2;
    Angle: Single;
    ShootTime: IntU32;
    ShootInterval: IntU32;
    ShootSide: Integer;
    MFImg: PImg;
    MFTime: IntU32;
    MFDuration: IntU32;
    MFSide: Integer;
  end;

  TBossLauncher = record
    Pos: TG2Vec2;
    Size: TG2Vec2;
    Img: PImg;
    ShootPos: array of TG2Vec2;
    ShootTime: IntU32;
    ShootInterval: IntU32;
    ShootID: Integer;
  end;

  TBoss0AIStage = (aiIntro, aiFight);

  TBoss0AI = object
  public
    Stage: TBoss0AIStage;
    StageTime: IntU32;
    procedure SetStage(const NewStage: TBoss0AIStage);
  end;

  TBoss0 = class (TBoss)
  public
    Size: TG2Vec2;
    Img: PImg;
    Ext0: TBossExt;
    Gun0: TBossGun;
    Launcher0: TBossLauncher;
    Launcher1: TBossLauncher;
    Laser0: TShotLaser;
    Laser0Pos: TG2Vec2;
    Laser1: TShotLaser;
    Laser1Pos: TG2Vec2;
    HitSpotGun0: TBossHitSpot;
    HitSpotLauncher0: TBossHitSpot;
    HitSpotLauncher1: TBossHitSpot;
    HitSpotBody: TBossHitSpot;
    HealthGun0: Integer;
    HealthMaxGun0: Integer;
    HealthLauncher0: Integer;
    HealthMaxLauncher0: Integer;
    HealthLauncher1: Integer;
    HealthMaxLauncher1: Integer;
    HealthBody: Integer;
    HealthMaxBody: Integer;
    LaserDurationIdle: IntU32;
    LaserDurationPrime: IntU32;
    LaserDurationFire: IntU32;
    LaserState: TLaserState;
    LaserStateTime: IntU32;
    AI: TBoss0AI;
    constructor Create; override;
    destructor Destroy; override;
    procedure Update; override;
    procedure Render; override;
    procedure DoAI;
    procedure CheckDamage;
    procedure HitGun0(const Amount: Integer);
    procedure HitLauncher0(const Amount: Integer);
    procedure HitLauncher1(const Amount: Integer);
    procedure HitBody(const Amount: Integer);
  end;

  TPlayerPowerup = class (TGameObject)
  public
    Pos: TG2Vec2;
    Size: Single;
    ImgBubble: PImg;
    Img: PImg;
    BubbleFrameWidth: Integer;
    BubbleFrameHeight: Integer;
    BubbleFrameCount: Integer;
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
    procedure OnPickup; virtual; abstract;
  end;

  TPlayerPowerupHeal = class (TPlayerPowerup)
  public
    constructor Create; override;
    procedure OnPickup; override;
  end;

  TPlayerPowerupAddHealth = class (TPlayerPowerup)
  public
    constructor Create; override;
    procedure OnPickup; override;
  end;

  TPlayerPowerupShield = class (TPlayerPowerup)
  public
    constructor Create; override;
    procedure OnPickup; override;
  end;

  TPlayerPowerupAddShield = class (TPlayerPowerup)
  public
    constructor Create; override;
    procedure OnPickup; override;
  end;

  TPlayerPowerupMissile = class (TPlayerPowerup)
  public
    constructor Create; override;
    procedure OnPickup; override;
  end;

  TPlayerPowerupLaser = class (TPlayerPowerup)
  public
    constructor Create; override;
    procedure OnPickup; override;
  end;

  TPlayerPowerupBlaster = class (TPlayerPowerup)
  public
    constructor Create; override;
    procedure OnPickup; override;
  end;

  TPlayerGun = class
  protected
    _PosRef: PG2Vec2;
    _GunFront: TG2Vec2;
    _GunSide: array[0..1] of TG2Vec2;
    _Level: Integer;
    _Faction: Integer;
    procedure SetLevel(const Value: Integer);
  public
    property Level: Integer read _Level write SetLevel;
    constructor Create; virtual;
    destructor Destroy; override;
    procedure Mount(const PosRef: PG2Vec2; const GunFront, GunLeft, GunRight: TG2Vec2; const Faction: Integer);
    procedure OnMount; virtual;
    procedure OnChangeLevel; virtual;
    procedure Fire(const Enable: Boolean); virtual; abstract;
  end;

  CPlayerGun = class of TPlayerGun;

  TPlayerGunBlaster = class (TPlayerGun)
  public
    ShotSide: Integer;
    ShotTime: IntU32;
    ShotLag: Integer;
    procedure OnMount; override;
    procedure OnChangeLevel; override;
    procedure Fire(const Enable: Boolean); override;
  end;

  TPlayerGunLaser = class (TPlayerGun)
  public
    Laser: TShotLaser;
    LaserLeft: TShotLaser;
    LaserRight: TShotLaser;
    LaserLeftScatter: TShotLaser;
    LaserRightScatter: TShotLaser;
    constructor Create; override;
    destructor Destroy; override;
    procedure OnMount; override;
    procedure OnChangeLevel; override;
    procedure Fire(const Enable: Boolean); override;
  end;

  TPlayerGunMissile = class (TPlayerGun)
  public
    ShotSide: Integer;
    ShotTime: IntU32;
    ShotLag: Integer;
    procedure OnMount; override;
    procedure OnChangeLevel; override;
    procedure Fire(const Enable: Boolean); override;
  end;

  TPlayer = class (TFactionObject)
  private
    HealthMax: Integer;
    Health: Integer;
    ShieldMax: Integer;
    Shield: Integer;
    Pos: TG2Vec2;
    Size: Single;
    Img: TG2Texture2D;
    ImgEngine: PImg;
    ImgShield: PImg;
    Vel: TG2Vec2;
    Speed: Single;
    SpeedMax: Single;
    InputMethod: TInputMethod;
    TimeHit: IntU32;
    TimeShieldHit: IntU32;
    Guns: TG2QuickList;
  public
    constructor Create; override;
    destructor Destroy; override;
    procedure Render; override;
    procedure Update; override;
    procedure Hit(const Amount: Integer); override;
    procedure Kill;
    procedure Accelerate(const v: TG2Vec2);
    procedure UpdateBounds;
    procedure AddGun(const GunClass: CPlayerGun);
  end;

  TGameOver = object
  public
    NextLevel: Integer;
    procedure Update;
    procedure Render;
  end;

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

  TG2App = object
  private
    _GameState: TGameState;
    procedure SetGameState(const Value: TGameState);
  public
    CurTime: IntU32;
    StateTime: IntU32;
    gs, gs2: Single;
    Sounds: TG2QuickList;
    Font0: TG2Font;
    Font1: TG2Font;
    Level: TLevel;
    Player: TPlayer;
    InputMethods: TG2QuickList;
    ImgListGround: TImgList;
    ImgListClouds: TImgList;
    ImgListObjects: TImgList;
    ImgListEnemies: TImgList;
    ImgListShots: TImgList;
    ImgListEffects: TImgList;
    TexBorder: TG2Texture2D;
    TexGradient0: TG2Texture2D;
    TexProgressBar: TG2Texture2D;
    TexLogoIGDC: TG2Texture2D;
    SndMusic: PSound;
    SndBlast: PSound;
    SndMissile: PSound;
    SndExplosion: PSound;
    Menu: TMenu;
    MenuMain: TMenuMain;
    MenuProfile: TMenuProfile;
    MenuCreateProfile: TMenuCreateProfile;
    ProfileCount: Integer;
    Profiles: array [0..34] of PProfile;
    ProfileSelected: Integer;
    GameOver: TGameOver;
    Score: Integer;
    LastKillTime: IntU32;
    KillStreak: Integer;
    LevelFirst: Integer;
    LevelLast: Integer;
    LevelCur: Integer;
    property GameState: TGameState read _GameState write SetGameState;
    procedure Initialize;
    procedure Finalize;
    procedure Update;
    procedure Render;
    procedure Print(const Char: AnsiChar);
    procedure KeyDown(const Key: Integer);
    procedure KeyUp(const Key: Integer);
    procedure MouseDown(const Button, x, y: Integer);
    procedure MouseUp(const Button, x, y: Integer);
    procedure DrawRing(
      const Pos: TG2Vec2; const Radius0, Radius1: Single; const Tex: TG2Texture2D; const Color: TG2Color;
      const BlendMode: TG2BlendMode = bmNormal; const TexSpan: Single = 1; const Segments: Integer = 32
    );
    procedure DrawBorder(const r: TG2Rect; const Border: Single; const Color: TG2Color);
    procedure PrintInRect(
      const Font: TG2Font; const PrintRect: TG2Rect;
      const Text: AnsiString; const Color: TG2Color;
      const Center: Boolean = False
    );
    function ProfileFind(const ProfName: AnsiString): Integer;
    procedure ProfileAdd(const ProfName: AnsiString);
    procedure ProfileDelete(const Index: Integer);
    procedure RenderBox(const r: TG2Rect; const bs: Single; const Color: TG2Color; const Tex: TG2Texture2D; const BlendMode: TG2BlendMode = bmNormal);
    procedure RenderBox(const x, y, w, h, bs: Single; const Color: TG2Color; const Tex: TG2Texture2D; const BlendMode: TG2BlendMode = bmNormal);
    procedure RenderButton(const r: TG2Rect; const bs: Single; const Caption: AnsiString; const Tex0, Tex1, Tex2: TG2Texture2D; const TextScaleX: Single = 1; const TextScaleY: Single = 1);
    procedure RenderButton(const x, y, w, h, bs: Single; const Caption: AnsiString; const Tex0, Tex1, Tex2: TG2Texture2D; const TextScaleX: Single = 1; const TextScaleY: Single = 1);
    procedure TextFitRect(const TextFont: TG2Font; const R: TG2Rect; const Color: TG2Color; const Text: AnsiString; const Center: Boolean = True);
    procedure RenderIntro;
    procedure AddScore(const Amount: Integer);
    function CreateSound(const FileName: WideString): PSound; overload;
    function CreateSound(const Buffer: Pointer; const Size: Int64): PSound; overload;
    procedure FreeSounds;
  end;

var App: TG2App;

implementation

//TLoader BEGIN
procedure TLoader.Add(var Target: TG2Texture2D; const Source: WideString);
  var Obj: PLoadObject;
begin
  Target := TG2Texture2D.Create;
  New(Obj);
  Obj^.ObjectClass := loTexture2D;
  Obj^.LoadSource := lsFile;
  Obj^.TargetObject := Target;
  if g2.TargetPlatform = tpAndroid then
  Obj^.SourceFile := Source
  else
  Obj^.SourceFile := g2.AppPath + Source;
  Objects.Add(Obj);
end;

procedure TLoader.Add(var Target: TG2Texture2D; const Buffer: Pointer; const Size: Int64);
  var Obj: PLoadObject;
begin
  Target := TG2Texture2D.Create;
  New(Obj);
  Obj^.ObjectClass := loTexture2D;
  Obj^.LoadSource := lsBuffer;
  Obj^.TargetObject := Target;
  Obj^.SourceBuffer := Buffer;
  Obj^.SourceBufferSzie := Size;
  Objects.Add(Obj);
end;

procedure TLoader.Add(var Target: TG2Font; const Source: WideString);
  var Obj: PLoadObject;
begin
  Target := TG2Font.Create;
  New(Obj);
  Obj^.ObjectClass := loFont;
  Obj^.LoadSource := lsFile;
  Obj^.TargetObject := Target;
  if g2.TargetPlatform = tpAndroid then
  Obj^.SourceFile := Source
  else
  Obj^.SourceFile := g2.AppPath + Source;
  Objects.Add(Obj);
end;

procedure TLoader.Add(var Target: TG2Font; const Buffer: Pointer; const Size: Int64);
  var Obj: PLoadObject;
begin
  Target := TG2Font.Create;
  New(Obj);
  Obj^.ObjectClass := loFont;
  Obj^.LoadSource := lsBuffer;
  Obj^.TargetObject := Target;
  Obj^.SourceBuffer := Buffer;
  Obj^.SourceBufferSzie := Size;
  Objects.Add(Obj);
end;

procedure TLoader.LoadBegin;
begin
  TotalCount := Objects.Count;
end;

procedure TLoader.Next;
  var Obj: PLoadObject;
begin
  if Objects.Count > 0 then
  begin
    Obj := Objects[0];
    Objects.Delete(0);
    case Obj^.ObjectClass of
      loTexture2D:
      begin
        case Obj^.LoadSource of
          lsFile: TG2Texture2D(Obj^.TargetObject).Load(Obj^.SourceFile);
          lsBuffer: TG2Texture2D(Obj^.TargetObject).Load(Obj^.SourceBuffer, Obj^.SourceBufferSzie);
        end;
      end;
      loFont:
      begin
        case Obj^.LoadSource of
          lsFile: TG2Font(Obj^.TargetObject).Load(Obj^.SourceFile);
          lsBuffer: TG2Font(Obj^.TargetObject).Load(Obj^.SourceBuffer, Obj^.SourceBufferSzie);
        end;
      end;
    end;
    Dispose(Obj);
  end;
end;

function TLoader.Progress: Single;
begin
  if TotalCount > 0 then
  Result := 1 - Objects.Count / TotalCount
  else
  Result := 1;
end;
//TLoader END

//TSound BEGIN
function TSound.GetInst: TG2SoundInst;
  var i: Integer;
begin
  Result := nil;
  for i := 0 to Inst.Count - 1 do
  if not TG2SoundInst(Inst[i]).IsPlaying then
  begin
    Result := TG2SoundInst(Inst[i]);
    Exit;
  end;
  Result := TG2SoundInst.Create(Buffer);
  Inst.Add(Result);
end;

procedure TSound.Load(const FileName: WideString);
begin
  Buffer := TG2SoundBuffer.Create;
  Buffer.Load(FileName);
  Inst.Clear;
end;

procedure TSound.Load(const Buf: Pointer; const Size: Int64);
begin
  Buffer := TG2SoundBuffer.Create;
  Buffer.Load(Buf, Size);
  Inst.Clear;
end;

procedure TSound.Play;
begin
  GetInst.Play;
end;

procedure TSound.Loop;
  var CurInst: TG2SoundInst;
begin
  CurInst := GetInst;
  CurInst.Loop := True;
  CurInst.Play;
end;

procedure TSound.Free;
  var i: Integer;
begin
  for i := 0 to Inst.Count - 1 do
  TG2SoundInst(Inst[i]).Free;
  Inst.Clear;
  Buffer.Free;
end;
//TSound END

//TInputMethod BEGIN
constructor TInputMethod.Create;
begin
  inherited Create;
  App.InputMethods.Add(Self);
end;

destructor TInputMethod.Destroy;
begin
  App.InputMethods.Remove(Self);
  inherited Destroy;
end;

procedure TInputMethod.Update;
begin

end;

procedure TInputMethod.DoMove(const v: TG2Vec2);
begin
  App.Player.Accelerate(v);
end;
//TInputMethod END

//TInputMethodKeyboard BEGIN
procedure TInputMethodKeyboard.Update;
  var v: TG2Vec2;
  var b: Boolean;
begin
  b := False;
  v.SetValue(0, 0);
  if g2.KeyDown[G2K_Left] then
  begin
    v.x := v.x - 1;
    b := True;
  end;
  if g2.KeyDown[G2K_Right] then
  begin
    v.x := v.x + 1;
    b := True;
  end;
  if g2.KeyDown[G2K_Up] then
  begin
    v.y := v.y + 1;
    b := True;
  end;
  if g2.KeyDown[G2K_Down] then
  begin
    v.y := v.y - 1;
    b := True;
  end;
  if b then DoMove(v);
end;
//TInputMethodKeyboard END

//TImg BEGIN
procedure TImg.Load;
  var pp: Boolean;
begin
  if not Loaded then
  begin
    pp := g2.Pause;
    g2.Pause := True;
    Texture := TG2Texture2D.Create;
    Texture.Load(Path + Name, tuUsage2D);
    Loaded := True;
    g2.Pause := pp;
  end;
end;

procedure TImg.Release;
begin
  if Loaded then
  begin
    Texture.Free;
    Loaded := False;
  end;
end;
//TImg END

//TImgList BEGIN
function TImgList.GetImage(const Index: Integer): PImg;
begin
  Result := PImg(_List[Index]);
  if not Result^.Loaded then
  Result^.Load;
end;

function TImgList.GetImageCount: Integer;
begin
  Result := _List.Count;
end;

procedure TImgList.Initilaize(const Path: AnsiString);
  var SR: TSearchRec;
  var SearchDir: AnsiString;
  var SearchPath: AnsiString;
  var Img: PImg;
begin
  SearchDir := g2.AppPath + 'gfx\' + Path + '\';
  if g2.TargetPlatform = tpAndroid then
  begin
    _Path := SearchDir;
    Exit;
  end;
  SearchPath := SearchDir + '*.png';
  if FindFirst(SearchPath, 0, SR) = 0 then
  repeat
    New(Img);
    Img^.Name := SR.Name;
    Img^.Path := SearchDir;
    Img^.Loaded := False;
    _List.Add(Img);
  until FindNext(SR) <> 0;
  FindClose(SR);
end;

procedure TImgList.Finalzie;
  var i: Integer;
begin
  for i := 0 to _List.Count - 1 do
  begin
    PImg(_List[i])^.Release;
    Dispose(PImg(_List[i]));
  end;
  _List.Clear;
end;

function TImgList.Find(const Name: AnsiString): PImg;
  var i: Integer;
begin
  for i := 0 to _List.Count - 1 do
  if PImg(_List[i])^.Name = Name then
  begin
    Result := PImg(_List[i]);
    if not Result^.Loaded then
    Result^.Load;
    Exit;
  end;
  New(Result);
  Result^.Name := Name;
  Result^.Path := _Path;
  Result^.Loaded := False;
  _List.Add(Result);
  Result^.Load;
end;

procedure TImgList.Release;
  var i: Integer;
begin
  for i := 0 to _List.Count - 1 do
  PImg(_List[i])^.Release;
end;

procedure TImgList.Load;
  var i: Integer;
begin
  for i := 0 to _List.Count - 1 do
  PImg(_List[i])^.Load;
end;
//TImgList END

//TPath BEGIN
function TPath.GetPos(const Pt0, Pt1: Integer; const s: Single): TG2Vec2;
  var cp0, cp1, cp2, cp3, d0, d1, d2, dd0, dd1: TG2Vec2;
begin
  cp0 := Waypoints[Pt0].Pos;
  cp1 := Waypoints[Pt0].Pos - Waypoints[Pt0].Normal * 2;
  cp2 := Waypoints[Pt1].Pos + Waypoints[Pt1].Normal * 2;
  cp3 := Waypoints[Pt1].Pos;
  d0 := G2LerpVec2(cp0, cp1, s);
  d1 := G2LerpVec2(cp1, cp2, s);
  d2 := G2LerpVec2(cp2, cp3, s);
  dd0 := G2LerpVec2(d0, d1, s);
  dd1 := G2LerpVec2(d1, d2, s);
  Result := G2LerpVec2(dd0, dd1, s);
end;
//TPath END

//TEnemyDispatch BEGIN
procedure TEnemyDispatch.Initialize;
  var i: Integer;
begin
  Active.Clear;
  Sorted.Clear;
  for i := 0 to High(Enemies) do
  begin
    Enemies[i].CurEnemy := 0;
    Enemies[i].CurSpacing := Enemies[i].Spacing;
    Sorted.Add(@Enemies[i], Enemies[i].Pos);
  end;
  CurEnemy := 0;
end;

procedure TEnemyDispatch.Update;
  var vs: Single;
  var i: Integer;
  var Enemy: TEnemy;
begin
  vs := App.Level.VCamPos.y - App.Level.VScreenSize.y * 0.5 + 0.75;
  while (CurEnemy < Sorted.Count)
  and (PEnemyData(Sorted[CurEnemy])^.Pos < vs) do
  begin
    Active.Add(Sorted[CurEnemy]);
    Inc(CurEnemy);
  end;
  for i := Active.Count - 1 downto 0 do
  if PEnemyData(Active[i])^.CurEnemy < PEnemyData(Active[i])^.Count then
  begin
    PEnemyData(Active[i])^.CurSpacing := PEnemyData(Active[i])^.CurSpacing + 0.1;
    if PEnemyData(Active[i])^.CurSpacing >= PEnemyData(Active[i])^.Spacing then
    begin
      Inc(PEnemyData(Active[i])^.CurEnemy);
      PEnemyData(Active[i])^.CurSpacing := 0;
      case PEnemyData(Active[i])^.EnemyType^.ID of
        0:
        begin
          Enemy := TEnemyAlien01.Create;
        end;
        1:
        begin
          Enemy := TEnemyAlien02.Create;
        end;
        2:
        begin
          Enemy := TEnemyAlien03.Create;
        end;
        3:
        begin
          Enemy := TEnemyAlien04.Create;
        end;
      end;
      Enemy.Path := PEnemyData(Active[i])^.Path;
      Enemy.Speed := PEnemyData(Active[i])^.Velocity;
      Enemy.PrevPos := Enemy.Pos;
    end;
  end
  else
  Active.Delete(i);
end;
//TEnemyDispatch END

//TLevel BEGIN
procedure TLevel.Adjust;
begin
  VSceneSize.SetValue(1, 20);
  VGroundGridSize.SetValue(0.05, 0.05);
  Resolution := g2.Params.Width;
  VScreenSize.SetValue(VSceneSize.x, g2.Params.Height / g2.Params.Width);
  VScreenSpeed := 0.0015;
end;

procedure TLevel.Render;
  var i: Integer;
  var g: Single;
begin
  RenderGround;
  ObjectsSorted.Clear;
  for i := 0 to GameObjects.Count - 1 do
  if TGameObject(GameObjects[i]) is TLevelObject then
  begin
    if (VCamPos.y + VScreenSize.y * 0.5 > TLevelObject(GameObjects[i]).ShowStart)
    and (VCamPos.y - VScreenSize.y * 0.5 < TLevelObject(GameObjects[i]).ShowEnd) then
    ObjectsSorted.Add(GameObjects[i], TGameObject(GameObjects[i]).Alt + TGameObject(GameObjects[i]).Order);
  end
  else
  ObjectsSorted.Add(GameObjects[i], TGameObject(GameObjects[i]).Alt + TGameObject(GameObjects[i]).Order);
  for i := 0 to ObjectsSorted.Count - 1 do
  TGameObject(ObjectsSorted[i]).Render;
  if (State = lsFinished)
  and (App.CurTime - LevelEndTime > 2000) then
  begin
    g := (App.CurTime - LevelEndTime - 2000) / 1000;
    if g > 1 then g := 1;
    g2.PrimRect(0, 0, g2.Params.Width, g2.Params.Height, G2Color(0, 0, 0, Round($ff * g)));
  end;
  RenderUI;
end;

procedure TLevel.RenderUI;
  var ScrPos: TG2Vec2;
  var ScrSize, ScrSpacing, g: Single;
  var i, n: Integer;
  var Str: AnsiString;
begin
  ScrSpacing := 0.0025 * App.Level.Resolution;
  if App.Player <> nil then
  begin
    ScrPos := G2Vec2(0.01, 0.01) * App.Level.Resolution;
    ScrSize := 0.02 * App.Level.Resolution;
    n := App.Player.Health div 100;
    for i := 0 to n - 1 do
    begin
      g2.PicRect(ScrPos, ScrSize, ScrSize, $ffff0000, App.TexProgressBar, bmNormal, tfLinear);
      ScrPos.x := ScrPos.x + ScrSize + ScrSpacing;
    end;
    n := App.Player.Health mod 100;
    if n > 0 then
    begin
      g := n * 0.01;
      g2.PicRect(ScrPos, ScrSize * g, ScrSize, G2Vec4(0, 0, g, 1), $ffff0000, App.TexProgressBar, bmNormal, tfLinear);
    end;
    ScrPos := G2Vec2(0.01, 0.0325) * App.Level.Resolution;
    n := App.Player.Shield div 100;
    for i := 0 to n - 1 do
    begin
      g2.PicRect(ScrPos, ScrSize, ScrSize, $ff0000ff, App.TexProgressBar, bmNormal, tfLinear);
      ScrPos.x := ScrPos.x + ScrSize + ScrSpacing;
    end;
    n := App.Player.Shield mod 100;
    if n > 0 then
    begin
      g := n * 0.01;
      g2.PicRect(ScrPos, ScrSize * g, ScrSize, G2Vec4(0, 0, g, 1), $ff0000ff, App.TexProgressBar, bmNormal, tfLinear);
    end;
  end;
  Str := 'Score: ' + IntToStr(App.Score);
  App.Font0.Print(
    g2.Params.Width - App.Font0.TextWidth(Str) * App.gs - ScrSpacing, ScrSpacing,
    App.gs, App.gs, $ffffffff, Str, bmNormal, tfLinear
  );
end;

procedure TLevel.Update;
  var i: Integer;
  var GameObject: TGameObject;
begin
  VCamPos.y := VCamPos.y + VScreenSpeed;
  if VCamPos.y > VSceneSize.y - VScreenSize.y * 0.5 then
  begin
    VCamPos.y := VSceneSize.y - VScreenSize.y * 0.5;
    if State = lsPlaying then
    begin
      if BossID > -1 then
      begin
        BossClass[BossID].Create;
        State := lsBoss;
      end
      else
      begin
        LevelEndTime := App.CurTime;
        State := lsFinished;
      end;
    end;
  end;
  EnemyDispatch.Update;
  for i := 0 to GameObjects.Count - 1 do
  TGameObject(GameObjects[i]).Update;
  for i := 0 to Shots.Count - 1 do
  TShot(Shots[i]).ProcessHits;
  while GameObjectsDead.Count > 0 do
  begin
    GameObject := TGameObject(GameObjectsDead.Pop);
    GameObject.Free;
  end;
  if (App.Player = nil)
  and (State <> lsFinished) then
  begin
    State := lsFinished;
    LevelEndTime := App.CurTime;
  end;
  if (State = lsFinished)
  and (App.CurTime - LevelEndTime > 3000) then
  EndLevel;
end;

procedure TLevel.Initialize;
  var i: Integer;
begin
  BossClass[0] := TBoss0;
  for i := 0 to High(Factions) do
  Factions[i].Clear;
  Shots.Clear;
  EnemyTypeAdd(0);
  EnemyTypeAdd(1);
  EnemyTypeAdd(2);
  EnemyTypeAdd(3);
end;

procedure TLevel.Finalize;
begin
  Clear;
end;

procedure TLevel.LoadLevel(const LevelID: Integer);
  var dm: TG2DataManager;
  var f, str: AnsiString;
  var i, j, x, y, n, nn: Integer;
  var s: Single;
  var lo: TLevelObject;
  var cl: TClouds;
  var buff: Pointer;
  var Buffer: Pointer;
  var BufferSize: IntS64;
  var ms: TMemoryStream;
begin
  g2.Pause := True;
  GameObjects.Clear;
  f := 'levels\level' + IntToStr(LevelID) + '.klv';
  dm := TG2DataManager.Create(f, dmAsset);
  BufferSize := dm.Size;
  GetMem(Buffer, BufferSize);
  try
    dm.ReadBuffer(Buffer, BufferSize);
  finally
    dm.Free;
  end;
  ms := TMemoryStream.Create;
  G2ZLibDecompress(Buffer, BufferSize, ms);
  FreeMem(Buffer, BufferSize);
  ms.Position := 0;
  dm := TG2DataManager.Create(ms, dmRead);
  try
    n := dm.ReadIntS32;
    n := dm.ReadIntU8;
    //Ground
    n := dm.ReadIntS32;
    SetLength(Ground, n);
    for i := 0 to n - 1 do
    begin
      Ground[i].Img := App.ImgListGround.Find(dm.ReadStringA);
      Ground[i].Img^.Load;
      for x := 0 to High(Ground[i].Landscape) do
      for y := 0 to High(Ground[i].Landscape[0]) do
      Ground[i].Landscape[x, y] := dm.ReadColor;
      for x := 0 to High(Ground[i].Visible) do
      for y := 0 to High(Ground[i].Visible[0]) do
      Ground[i].Visible[x, y] := (
        Ground[i].Landscape[x, y].a +
        Ground[i].Landscape[x + 1, y].a +
        Ground[i].Landscape[x, y + 1].a +
        Ground[i].Landscape[x + 1, y + 1].a > 1
      );
    end;
    //Clouds
    n := dm.ReadIntS32;
    for i := 0 to n - 1 do
    begin
      cl := TClouds.Create;
      cl.Img := App.ImgListClouds.Find(dm.ReadStringA);
      cl.BlendMode := dm.ReadIntU8;
      cl.Alt := dm.ReadIntS32;
      cl.Order := dm.ReadFloat;
      cl.Scale := dm.ReadFloat;
      cl.SpeedV := dm.ReadFloat;
      cl.SpeedH := dm.ReadFloat;
      cl.Color := dm.ReadColor;
      cl.Pos.SetValue(0, 0);
    end;
    //Skip Object Types
    GetMem(buff, 25);
    n := dm.ReadIntS32;
    for i := 0 to n - 1 do
    begin
      str := dm.ReadStringA;
      dm.ReadBuffer(buff, 25);
    end;
    FreeMem(buff, 25);
    //Objects
    n := dm.ReadIntS32;
    for i := 0 to n - 1 do
    begin
      lo := TLevelObject.Create;
      lo.Img := App.ImgListObjects.Find(dm.ReadStringA);
      lo.Pos := dm.ReadVec2;
      lo.Size := dm.ReadVec2;
      lo.Scale := dm.ReadFloat;
      lo.Ang := dm.ReadFloat;
      lo.Alt := dm.ReadIntS32;
      lo.Color := dm.ReadColor;
      lo.Flip := TObjectFlip(dm.ReadIntU8);
      lo.Order := dm.ReadFloat;
      lo.ShowStart := lo.Pos.y - lo.Size.Len * lo.Scale;
      lo.ShowEnd := lo.Pos.y + lo.Size.Len * lo.Scale;
      lo.Img^.Load;
    end;
    //Paths
    n := dm.ReadIntS32;
    SetLength(Paths, n);
    for i := 0 to n - 1 do
    begin
      nn := dm.ReadIntS32;
      SetLength(Paths[i].Waypoints, nn);
      for j := 0 to nn - 1 do
      begin
        Paths[i].Waypoints[j].Pos := dm.ReadVec2;
        Paths[i].Waypoints[j].Normal := dm.ReadVec2;
        Paths[i].Waypoints[j].Speed := dm.ReadFloat;
      end;
    end;
    //Enemies
    n := dm.ReadIntS32;
    SetLength(EnemyDispatch.Enemies, n);
    for i := 0 to n - 1 do
    begin
      EnemyDispatch.Enemies[i].EnemyType := EnemyTypeFind(dm.ReadIntS32);
      EnemyDispatch.Enemies[i].Pos := dm.ReadFloat;
      EnemyDispatch.Enemies[i].Count := dm.ReadIntS32;
      EnemyDispatch.Enemies[i].Spacing := dm.ReadFloat;
      EnemyDispatch.Enemies[i].Velocity := dm.ReadFloat;
      nn := dm.ReadIntS32;
      if nn > -1 then
      EnemyDispatch.Enemies[i].Path := @Paths[nn]
      else
      EnemyDispatch.Enemies[i].Path := nil;
    end;
    BossID := dm.ReadIntS32;
  finally
    dm.Free;
    ms.Free;
  end;
  VCamPos.SetValue(VScreenSize.x * 0.5, VScreenSize.y * 0.5);
  State := lsPlaying;
  App.Player := TPlayer.Create;
  App.Player.Img := TG2Texture2D.Create;
  App.Player.Img.Load('game\Ship01.png');
  App.LastKillTime := App.CurTime;
  EnemyDispatch.Initialize;
  g2.Pause := False;
end;

procedure TLevel.EndLevel;
begin
  if (App.Player <> nil)
  and (App.LevelCur < App.LevelLast) then
  begin
    App.LevelCur := App.LevelCur + 1;
    App.GameOver.NextLevel := App.LevelCur;
  end
  else
  App.GameOver.NextLevel := -1;
  Clear;
  App.GameState := gsGameOver;
  g2.ShowCursor := True;
end;

procedure TLevel.Clear;
begin
  while GameObjects.Count > 0 do
  TGameObject(GameObjects[0]).Free;
  GameObjects.Clear;
  GameObjectsDead.Clear;
end;

function TLevel.PosVirtualToScreen(const v: TG2Vec2; const Altitude: Single = 1): TG2Vec2;
begin
  Result.x := ((v.x - VCamPos.x) * Altitude + VScreenSize.x * 0.5) * Resolution;
  Result.y := g2.Params.Height - ((v.y - VCamPos.y) * Altitude + VScreenSize.y * 0.5) * Resolution;
end;

function TLevel.PosScreenToVirtual(const v: TG2Vec2; const Altitude: Single = 1): TG2Vec2;
begin
  Result.x := ((v.x) / Resolution - VScreenSize.x * 0.5) / Altitude + VCamPos.x;
  Result.y := ((g2.Params.Height - v.y) / Resolution - VScreenSize.y * 0.5) / Altitude + VCamPos.y;
end;

function TLevel.SizeVirtualToScreen(const s: Single; const Altitude: Single = 1): Single;
begin
  Result := s * Resolution * Altitude;
end;

function TLevel.SizeScreenToVirtual(const s: Single; const Altitude: Single = 1): Single;
begin
  Result := s / (Resolution * Altitude);
end;

procedure TLevel.RenderGround;
  var i, x, y: Integer;
  var LandMinX, LandMinY, LandMaxX, LandMaxY: Single;
  var LandGridStart, LandGridEnd: TPoint;
  var v: array[0..3] of TG2Vec2;
  var c: array[0..3] of TG2Color;
  var t: array[0..3] of TG2Vec2;
begin
  LandMinX := 0; LandMaxX := 1;
  LandMinY := VCamPos.y - VScreenSize.y * 0.5; LandMaxY := VCamPos.y + VScreenSize.y * 0.5;
  LandGridStart.X := Round(LandMinX / VGroundGridSize.x); LandGridStart.Y := Round(LandMinY / VGroundGridSize.y) - 1;
  LandGridEnd.X := Round(LandMaxX / VGroundGridSize.x) - 1; LandGridEnd.y := Round(LandMaxY / VGroundGridSize.y);
  if LandGridStart.Y < 0 then LandGridStart.Y := 0;
  if LandGridEnd.Y > 399 then LandGridEnd.Y := 399;
  for i := 0 to High(Ground) do
  for x := LandGridStart.X to LandGridEnd.X do
  for y := LandGridStart.Y to LandGridEnd.Y do
  if Ground[i].Visible[x, y] then
  begin
    v[0] := PosVirtualToScreen(G2Vec2(x * VGroundGridSize.x, (y + 1) * VGroundGridSize.y));
    v[1] := PosVirtualToScreen(G2Vec2((x + 1) * VGroundGridSize.x, (y + 1) * VGroundGridSize.y));
    v[2] := PosVirtualToScreen(G2Vec2(x * VGroundGridSize.x, y * VGroundGridSize.y));
    v[3] := PosVirtualToScreen(G2Vec2((x + 1) * VGroundGridSize.x, y * VGroundGridSize.y));
    c[0] := Ground[i].Landscape[x, y + 1];
    c[1] := Ground[i].Landscape[x + 1, y + 1];
    c[2] := Ground[i].Landscape[x, y];
    c[3] := Ground[i].Landscape[x + 1, y];
    t[0] := G2Vec2(x * VGroundGridSize.x, -(y + 1) * VGroundGridSize.y);
    t[1] := G2Vec2((x + 1) * VGroundGridSize.x, -(y + 1) * VGroundGridSize.y);
    t[2] := G2Vec2(x * VGroundGridSize.x, -y * VGroundGridSize.y);
    t[3] := G2Vec2((x + 1) * VGroundGridSize.x, -y * VGroundGridSize.y);
    g2.PicQuadCol(
      v[0], v[1], v[2], v[3],
      t[0], t[1], t[2], t[3],
      c[0], c[1], c[2], c[3],
      Ground[i].Img^.Texture,
      bmNormal, tfLinear
    );
  end;
end;

function TLevel.EnemyTypeAdd(const ID: Integer): Integer;
begin
  Result := Length(EnemyTypes);
  SetLength(EnemyTypes, Result + 1);
  EnemyTypes[Result].ID := ID;
  case ID of
    0: EnemyTypes[Result].ImgName := 'Enemy0.png';
    1: EnemyTypes[Result].ImgName := 'Enemy1.png';
    2: EnemyTypes[Result].ImgName := 'Enemy2.png';
    3: EnemyTypes[Result].ImgName := 'Enemy3.png';
  end;
end;

function TLevel.EnemyTypeFind(const ID: Integer): PEnemyType;
  var i: Integer;
begin
  for i := 0 to High(EnemyTypes) do
  if EnemyTypes[i].ID = ID then
  begin
    Result := @EnemyTypes[i];
    Exit;
  end;
end;

function TLevel.CreateShotParticle(const ShotClass: CShotParticle; const Pos, Dir, Size: TG2Vec2; const Vel: Single): TShotParticle;
begin
  Result := ShotClass.Create;
  Result.Pos := Pos;
  Result.Dir := Dir;
  Result.Size := Size;
  Result.Vel := Vel;
end;

function TLevel.CreateShotLaser(const ShotClass: CShotLaser; const Pos, Dir: TG2Vec2; const Size: Single): TShotLaser;
begin
  Result := ShotClass.Create;
  Result.Pos := Pos;
  Result.Dir := Dir;
  Result.Size := Size;
end;

function TLevel.CreateShotMissile(const ShotClass: CShotMissile; const Pos, Dir, Size: TG2Vec2; const Vel: Single): TShotMissile;
begin
  Result := ShotClass.Create;
  Result.Pos := Pos;
  Result.Dir := Dir;
  Result.Size := Size;
  Result.Vel := Vel;
end;

procedure TLevel.CreateExplosion(const Pos: TG2Vec2);
  var e: TEffect;
  var Trace: TTrace;
  var i: Integer;
begin
  for i := 0 to 5 do
  begin
    Trace := TTrace.Create;
    Trace.SpawnType := TEffectFlame;
    Trace.Pos := Pos;
    Trace.Vel := G2RandomCirclePoint * (0.001 + Random(50) * 0.0001);
    Trace.SpawnInterval := 20;
    Trace.Restitution := 0.97;
    Trace.Acc.SetValue(0, -0.00004);
    Trace.Dumping := 0.98;
    Trace.OrderInc := -1E-6;
    Trace.SetTimes(0, 500);
  end;
  e := TEffectWave.Create;
  e.Pos := Pos;
  e := TEffectFlare.Create;
  e.Pos := Pos;
  e := TEffectExplosion.Create;
  e.Pos := Pos;
  e.SetTimes(0, 500);
  TEffectExplosion(e).Scale := 1 + Random(101) * 0.01;
  for i := 0 to 3 do
  begin
    e := TEffectExplosion.Create;
    e.Pos := Pos + G2RandomCirclePoint * 0.02;
    TEffectExplosion(e).Scale := 1 + Random(51) * 0.01;
  end;
  App.SndExplosion^.Play;
end;

procedure TLevel.CreatePlasmaBlast(const Pos: TG2Vec2);
  var e: TEffectExplosion;
begin
  e := TEffectExplosion.Create;
  e.Pos := Pos;
  e.SetTimes(0, 250);
  e.Img := App.ImgListEffects.Find('PlasmaBlast' + IntToStr(Random(4)) + '.png');
  e.Scale := 1;// + Random(11) * 0.01;
end;

procedure TLevel.CreateSparks(const Pos: TG2Vec2);
  var e: TEffectExplosion;
begin
  e := TEffectExplosion.Create;
  e.Pos := Pos;
  e.SetTimes(0, 250);
  e.Img := App.ImgListEffects.Find('Sparks.png');
  e.Scale := 1;// + Random(11) * 0.01;
end;

procedure TLevel.CreatePowerup(const PowerupClass: CPlayerPowerup; const Pos: TG2Vec2);
  var Powerup: TPlayerPowerup;
begin
  Powerup := PowerupClass.Create;
  Powerup.Pos := Pos;
end;
//TLevel END

//TMenu BEGIN
constructor TMenu.Create;
begin
  inherited Create;
  SubMenu := nil;
end;

destructor TMenu.Destroy;
begin
  inherited Destroy;
end;

procedure TMenu.OnRender;
begin
  Render;
  if SubMenu <> nil then SubMenu.OnRender;
end;

procedure TMenu.OnUpdate;
begin
  if SubMenu <> nil then
  SubMenu.OnUpdate
  else
  Update;
end;

procedure TMenu.OnMouseDown(const x, y: Integer);
begin
  if SubMenu <> nil then
  SubMenu.OnMouseDown(x, y)
  else
  MouseDown(x, y);
end;

procedure TMenu.OnMouseUp(const x, y: Integer);
begin
  if SubMenu <> nil then
  SubMenu.OnMouseUp(x, y)
  else
  MouseUp(x, y);
end;

procedure TMenu.OnKeyDown(const Key: Integer);
begin
  if SubMenu <> nil then
  SubMenu.OnKeyDown(Key)
  else
  KeyDown(Key);
end;

procedure TMenu.OnPrint(const Char: AnsiChar);
  function CharInSet(const c: AnsiChar; const CharSet: array of AnsiChar): Boolean;
    var i: Integer;
  begin
    for i := Low(CharSet) to High(CharSet) do
    if CharSet[i] = c then
    begin
      Result := True;
      Exit;
    end;
    Result := False;
  end;
  var i, n: Integer;
  var c0, c1: AnsiChar;
  var CharRU: array of AnsiChar;
begin
  n := Byte(Utf8ToAnsi('я')[1]) - Byte(Utf8ToAnsi('а')[1]) + 1;
  SetLength(CharRU, n * 2);
  c0 := Utf8ToAnsi('А')[1];
  c1 := Utf8ToAnsi('а')[1];
  for i := 0 to n - 1 do
  begin
    CharRU[i] := AnsiChar(Byte(c0) + i);
    CharRU[n + i] := AnsiChar(Byte(c1) + i);
  end;
  if (Char in [' '])
  or (Char in ['A'..'Z'])
  or (Char in ['a'..'z'])
  or (Char in ['0'..'9'])
  or (CharInSet(Char, CharRU))then
  begin
    if SubMenu <> nil then
    SubMenu.OnPrint(Char)
    else
    Print(Char);
  end;
end;

procedure TMenu.Render;
begin

end;

procedure TMenu.Update;
begin

end;

procedure TMenu.Print(const Char: AnsiChar);
begin

end;

procedure TMenu.MouseDown(const x, y: Integer);
begin

end;

procedure TMenu.MouseUp(const x, y: Integer);
begin

end;

procedure TMenu.KeyDown(const Key: Integer);
begin

end;
//TMenu END

//TMenuMain BEGIN
constructor TMenuMain.Create;
  var s, sx, sy: Single;
  var i: Integer;
begin
  inherited Create;
  TexMenu := TG2Texture2D.Create;
  TexMenu.Load('game\MenuMain.png', tuUsage2D);
  TexBack := TG2Texture2D.Create;
  TexBack.Load('game\MenuMainBG.png', tuUsage2D);
  AddButton('CONTINUE');
  AddButton('NEW GAME', @BtnNewGame);
  AddButton('OPTIONS');
  AddButton('PROFILE');
  AddButton('EXIT', @BtnExit);
  Buttons[0].Enabled := False;
  Buttons[2].Enabled := False;
  Buttons[3].Enabled := False;
  ButtonWidth := 300 * App.gs;
  ButtonHeight := 45 * App.gs;
  DRMenu.h := g2.Params.Height;
  DRMenu.w := DRMenu.h;
  DRMenu.x := (g2.Params.Width - DRMenu.w) * 0.5;
  DRMenu.y := (g2.Params.Height - DRMenu.h) * 0.5;
  DRButtons.w := ButtonWidth;
  DRButtons.h := ButtonHeight * Length(Buttons);
  DRButtons.x := (g2.Params.Width - DRButtons.w) * 0.5;
  DRButtons.y := (g2.Params.Height - DRButtons.h) * 0.5;
  SubMenu := nil;
end;

destructor TMenuMain.Destroy;
begin
  TexBack.Free;
  TexMenu.Free;
  inherited Destroy;
end;

procedure TMenuMain.Render;
  var asp, aspd, bs: Single;
  var i: Integer;
  var DRBtn, DRText: TG2Rect;
  var c: TG2Color;
begin
  asp := g2.Params.Width / g2.Params.Height;
  aspd := (1 - asp) * 0.5;
  bs := 12 * App.gs;
  g2.PicRect(0, 0, g2.Params.Width, g2.Params.Height, aspd, 0, asp + aspd, 1, $ffffffff, TexBack, bmNormal, tfLinear);
  g2.PicRect(DRMenu.x, DRMenu.y, DRMenu.w, DRMenu.h, $ffffffff, TexMenu, bmNormal, tfLinear);
  DRBtn.l := DRButtons.l - bs;
  DRBtn.t := DRButtons.t - bs;
  DRBtn.r := DRButtons.r + bs;
  DRBtn.b := DRButtons.b + bs;
  App.DrawBorder(DRBtn, bs, $aa999999);
  DRText.x := DRButtons.x;
  DRText.y := DRButtons.y + ButtonHeight * 0.05;
  DRText.w := ButtonWidth;
  DRText.h := ButtonHeight * 0.9;
  for i := 0 to High(Buttons) do
  begin
    if Buttons[i].Enabled then c := $aa57b1fe else c := $aabbbbbb;
    g2.PicRect(DRButtons.x, DRButtons.y + ButtonHeight * i, ButtonWidth, ButtonHeight, c, App.TexGradient0, bmNormal, tfLinear);
    if Buttons[i].Enabled then c := G2LerpColor($ffa9d7ff, $ffffffff, Buttons[i].Grad) else c := $ff909090;
    App.TextFitRect(App.Font0, DRText, c, Buttons[i].Caption, True);
    DRText.y := DRText.y + ButtonHeight;
  end;
end;

procedure TMenuMain.Update;
  var i, n: Integer;
begin
  n := PtInButton(g2.MousePos);
  for i := 0 to High(Buttons) do
  begin
    if i = n then
    begin
      Buttons[i].Grad := Buttons[i].Grad + 0.1;
      if Buttons[i].Grad > 1 then Buttons[i].Grad := 1;
    end
    else
    begin
      Buttons[i].Grad := Buttons[i].Grad - 0.1;
      if Buttons[i].Grad < 0 then Buttons[i].Grad := 0;
    end;
  end;
end;

procedure TMenuMain.MouseUp(const x, y: Integer);
  var b0, b1: Integer;
begin
  b0 := PtInButton(g2.MousePos);
  if (b0 <> -1) then
  begin
    b1 := PtInButton(g2.MouseDownPos[G2MB_Left]);
    if (b0 = b1) and Assigned(Buttons[b0].Proc) then
    begin
      Buttons[b0].Proc;
    end;
  end;
end;

procedure TMenuMain.KeyDown(const Key: Integer);
begin

end;

procedure TMenuMain.Show;
  var i: Integer;
begin
  for i := 0 to High(Buttons) do
  Buttons[i].Grad := 0;
end;

procedure TMenuMain.AddButton(const Caption: AnsiString; const Proc: TG2ProcObj = nil);
begin
  SetLength(Buttons, Length(Buttons) + 1);
  Buttons[High(Buttons)].Caption := Caption;
  Buttons[High(Buttons)].Proc := Proc;
  Buttons[High(Buttons)].Grad := 0;
  Buttons[High(Buttons)].Enabled := True;
end;

function TMenuMain.PtInButton(const Pt: TG2Vec2): Integer;
  var i: Integer;
begin
  if (Pt.x <= DRButtons.l)
  or (Pt.y <= DRButtons.t)
  or (Pt.x >= DRButtons.r)
  or (Pt.y >= DRButtons.b) then
  begin
    Result := -1;
    Exit;
  end;
  Result := Trunc((Pt.y - DRButtons.t) / ButtonHeight);
  if Result > High(Buttons) then Result := High(Buttons);
end;

procedure TMenuMain.BtnNewGame;
begin
  App.Score := 0;
  App.Level.Clear;
  App.Level.LoadLevel(App.LevelFirst);
  App.GameState := gsGame;
  g2.ShowCursor := False;
end;

procedure TMenuMain.BtnExit;
begin
  g2.Stop;
end;
//TMenuMain END

//TMenuProfile BEGIN
constructor TMenuProfile.Create;
  var s: Single;
  var i: Integer;
begin
  inherited Create;
  TexMenu := TG2Texture2D.Create;
  TexMainMenu := TG2Texture2D.Create;
  TexLoadProfile := TG2Texture2D.Create;
  TexCreateProfile := TG2Texture2D.Create;
  TexHighlight := TG2Texture2D.Create;
  TexScroller := TG2Texture2D.Create;
  TexHighlight.Load('game\MenuProfileHighlight.png', tuUsage2D);
  TexScroller.Load('game\MenuProfileScroller.png', tuUsage2D);
  TexMenu.Load('game\MenuProfileRU.png', tuUsage2D);
  TexMainMenu.Load('game\MenuProfileMainMenuRU.png', tuUsage2D);
  TexLoadProfile.Load('game\MenuProfileLoadProfileRU.png', tuUsage2D);
  TexCreateProfile.Load('game\MenuProfileCreateProfileRU.png', tuUsage2D);
  r.h := g2.Params.Height * 0.9;
  r.w := r.h;
  r.x := (g2.Params.Width - r.w) * 0.5;
  r.y := (g2.Params.Height - r.h) * 0.5;
  s := r.h / TexMenu.Height;
  DRMainMenu.w := TexMainMenu.Width * s;
  DRMainMenu.h := TexMainMenu.Height * s;
  DRMainMenu.x := r.x + 272 * s;
  DRMainMenu.y := r.y + 446 * s;
  DRLoadProfile.w := TexLoadProfile.Width * s;
  DRLoadProfile.h := TexLoadProfile.Height * s;
  DRLoadProfile.x := r.x + 16 * s;
  DRLoadProfile.y := r.y + 451 * s;
  DRCreateProfile.w := TexCreateProfile.Width * s;
  DRCreateProfile.h := TexCreateProfile.Height * s;
  DRCreateProfile.x := r.x + (r.w - DRCreateProfile.w) * 0.5;
  DRCreateProfile.y := r.y + 76 * s;
  CRMainMenu.x := r.x + 295 * s;
  CRMainMenu.y := r.y + 466 * s;
  CRMainMenu.w := 210 * s;
  CRMainMenu.h := 34 * s;
  CRLoadProfile := DRLoadProfile;
  CRLoadProfile.x := r.x + 21 * s;
  CRLoadProfile.y := r.y + 466 * s;
  CRLoadProfile.w := 246 * s;
  CRLoadProfile.h := 34 * s;
  CRCreateProfile := DRCreateProfile;
  Rects[0] := @CRMainMenu;
  Rects[1] := @CRLoadProfile;
  Rects[2] := @CRCreateProfile;
  GradMainMenu := 0;
  GradLoadProfile := 0;
  GradCreateProfile := 0;
  Grads[0] := @GradMainMenu;
  Grads[1] := @GradLoadProfile;
  Grads[2] := @GradCreateProfile;
  for i := 0 to High(DRHighlight) do
  begin
    DRHighlight[i].x := r.x + 15 * s;
    DRHighlight[i].y := r.y + (151 + i * 60) * s;
    DRHighlight[i].w := TexHighlight.Width * s;
    DRHighlight[i].h := TexHighlight.Height * s;
  end;
  for i := 0 to High(DRNames) do
  begin
    DRNames[i].x := r.x + 85 * s;
    DRNames[i].y := r.y + (157 + i * 60) * s;
    DRNames[i].w := 380 * s;
    DRNames[i].h := 20 * s;
  end;
  for i := 0 to High(DRNums) do
  begin
    DRNums[i].x := r.x + 25 * s;
    DRNums[i].y := r.y + (157 + i * 60) * s;
    DRNums[i].w := 30 * s;
    DRNums[i].h := 20 * s;
  end;
  for i := 0 to High(CRNames) do
  begin
    CRNames[i].x := r.x + 13 * s;
    CRNames[i].y := r.y + (151 + i * 60) * s;
    CRNames[i].w := 465 * s;
    CRNames[i].h := 38 * s;
  end;
  ScrollerSize.x := TexScroller.Width * s;
  ScrollerSize.y := TexScroller.Height * s;
  ScrollerRect.w := ScrollerSize.x;
  ScrollerRect.h := 272 * s;
  ScrollerRect.x := r.x + 490 * s;
  ScrollerRect.y := r.y + 166 * s;
  Pos := 0;
  PosMax := 30;
end;

destructor TMenuProfile.Destroy;
begin
  TexHighlight.Free;
  TexCreateProfile.Free;
  TexLoadProfile.Free;
  TexMainMenu.Free;
  TexMenu.Free;
  inherited Destroy;
end;

procedure TMenuProfile.Render;
  var i, j, n0, n1: Integer;
  var sy: Single;
  var mc: TPoint;
begin
  g2.PicRect(r.x, r.y, r.w, r.h, $ffffffff, TexMenu, bmNormal, tfLinear);
  if GradMainMenu > 0 then
  g2.PicRect(
    DRMainMenu.x, DRMainMenu.y, DRMainMenu.w, DRMainMenu.h,
    $ffffff or (Round(GradMainMenu * $ff) shl 24), TexMainMenu, bmNormal, tfLinear
  );
  if GradLoadProfile > 0 then
  g2.PicRect(
    DRLoadProfile.x, DRLoadProfile.y, DRLoadProfile.w, DRLoadProfile.h,
    $ffffff or (Round(GradLoadProfile * $ff) shl 24), TexLoadProfile, bmNormal, tfLinear
  );
  if GradCreateProfile > 0 then
  g2.PicRect(
    DRCreateProfile.x, DRCreateProfile.y, DRCreateProfile.w, DRCreateProfile.h,
    $ffffff or (Round(GradCreateProfile * $ff) shl 24), TexCreateProfile, bmNormal, tfLinear
  );
  n0 := Pos;
  n1 := n0 + 4;
  if n1 > App.ProfileCount - 1 then n1 := App.ProfileCount - 1;
  j := 0;
  for i := n0 to n1 do
  begin
    if i = Selection then
    g2.PicRect(
      DRHighlight[j].x, DRHighlight[j].y, DRHighlight[j].w, DRHighlight[j].h,
      $ffffffff, TexHighlight, bmNormal, tfLinear
    );
    App.PrintInRect(App.Font0, DRNames[j], App.Profiles[i]^.Name, $ff000000);
    Inc(j);
  end;
  for i := 0 to 4 do
  begin
    App.PrintInRect(App.Font0, DRNums[i], IntToStr(Pos + i + 1), $ffffffff, True);
  end;
  //for i := 0 to High(DRHighlight) do
  //if i = Selection then
  //begin
  //  g2.PicRect(
  //    DRHighlight[i].x, DRHighlight[i].y, DRHighlight[i].w, DRHighlight[i].h,
  //    $ffffffff, TexHighlight, bmNormal, tfLinear
  //  );
  //end;
  //for i := 0 to App.ProfileCount - 1 do
  //begin
  //  App.PrintInRect(App.Font0, DRNames[i], App.Profiles[i]^.Name, $ff000000);
  //end;
  if not ScrollerDragging then
  sy := ScrollerRect.y + Pos / PosMax * (ScrollerRect.h - ScrollerSize.y)
  else
  begin
    mc := g2.MousePos;
    sy := g2.MousePos.y - ScrollerDrag;
    if sy < ScrollerRect.y then sy := ScrollerRect.y
    else if sy > ScrollerRect.y + ScrollerRect.h - ScrollerSize.y then
    sy := ScrollerRect.y + ScrollerRect.h - ScrollerSize.y;
  end;
  g2.PicRect(
    ScrollerRect.x, sy, ScrollerSize.x, ScrollerSize.y, $ffffffff, TexScroller, bmNormal, tfLinear
  );
  //for i := 0 to High(CRNames) do
  //begin
  //  g2.PrimRectHollow(
  //    CRNames[i].x, CRNames[i].y, CRNames[i].w, CRNames[i].h,
  //    $ffff0000
  //  );
  //end;
  //g2.PrimRectHollow(
  //  CRMainMenu.x, CRMainMenu.y, CRMainMenu.w, CRMainMenu.h,
  //  $ffff0000
  //);
  //g2.PrimRectHollow(
  //  CRLoadProfile.x, CRLoadProfile.y, CRLoadProfile.w, CRLoadProfile.h,
  //  $ffff0000
  //);
  //g2.PrimRectHollow(
  //  CRCreateProfile.x, CRCreateProfile.y, CRCreateProfile.w, CRCreateProfile.h,
  //  $ffff0000
  //);
end;

procedure TMenuProfile.Update;
  var mc: TPoint;
  var i: Integer;
begin
  mc := g2.MousePos;
  if ScrollerDragging then
  begin
    Pos := Round((mc.y - ScrollerRect.y - ScrollerDrag) / (ScrollerRect.h - ScrollerSize.y) * PosMax);
    if Pos < 0 then Pos := 0 else if Pos > PosMax then Pos := PosMax;
  end
  else
  for i := 0 to High(Rects) do
  if PtInRect(Rects[i]^, mc) then
  begin
    Grads[i]^ := Grads[i]^ + 0.1;
    if Grads[i]^ > 1 then Grads[i]^ := 1;
  end
  else
  begin
    Grads[i]^ := Grads[i]^ - 0.1;
    if Grads[i]^ < 0 then Grads[i]^ := 0;
  end;
end;

procedure TMenuProfile.MouseDown(const x, y: Integer);
  var mc: TPoint;
  var sr: TG2Rect;
begin
  mc := g2.MousePos;
  sr.x := ScrollerRect.x;
  sr.y := ScrollerRect.y + Pos / PosMax * (ScrollerRect.h - ScrollerSize.y);
  sr.w := ScrollerSize.x; sr.h := ScrollerSize.y;
  if not ScrollerDragging
  and PtInRect(sr, mc) then
  begin
    ScrollerDragging := True;
    ScrollerDrag := mc.y - sr.y;
  end;
end;

procedure TMenuProfile.MouseUp(const x, y: Integer);
  var i: Integer;
  var mc: TPoint;
begin
  mc := Point(x, y);
  if ScrollerDragging then
  begin
    ScrollerDragging := False;
    Pos := Round((mc.y - ScrollerRect.y - ScrollerDrag) / (ScrollerRect.h - ScrollerSize.y) * PosMax);
    if Pos < 0 then Pos := 0 else if Pos > PosMax then Pos := PosMax;
  end;
  if PtInRect(CRMainMenu, mc)
  and PtInRect(CRMainMenu, g2.MouseDownPos[G2MB_Left]) then
  begin
    App.MenuMain.SubMenu := nil;
    App.MenuMain.Show;
  end
  else if PtInRect(CRCreateProfile, mc)
  and PtInRect(CRCreateProfile, g2.MouseDownPos[G2MB_Left]) then
  begin
    if App.ProfileCount < 20 then
    begin
      SubMenu := App.MenuCreateProfile;
      App.MenuCreateProfile.Show;
    end;
  end
  else
  begin
    for i := 0 to High(CRNames) do
    if PtInRect(CRNames[i], mc)
    and PtInRect(CRNames[i], g2.MouseDownPos[G2MB_Left])
    and (Pos + i < App.ProfileCount) then
    begin
      Selection := Pos + i;
    end;
  end;
end;

procedure TMenuProfile.Show;
begin
  GradMainMenu := 0;
  GradLoadProfile := 0;
  GradCreateProfile := 0;
  Selection := App.ProfileSelected;
  ScrollerDragging := False;
end;
//TMenuProfile END

//TMenuCreateProfile BEGIN
constructor TMenuCreateProfile.Create;
  var s: Single;
begin
  inherited Create;
  TexMenu := TG2Texture2D.Create;
  TexCreate := TG2Texture2D.Create;
  TexCancel := TG2Texture2D.Create;
  TexMenu.Load('game/MenuCreateProfileRU.png', tuUsage2D);
  TexCreate.Load('game/MenuCreateProfileCreateRU.png', tuUsage2D);
  TexCancel.Load('game/MenuCreateProfileCancelRU.png', tuUsage2D);
  s := App.MenuProfile.r.w / App.MenuProfile.TexMenu.Width;
  r.w := TexMenu.Width * s;
  r.h := TexMenu.Height * s;
  r.x := (g2.Params.Width - r.w) * 0.5;
  r.y := App.MenuProfile.r.y + 98 * s;
  DRCreate.w := TexCreate.Width * s;
  DRCreate.h := TexCreate.Height * s;
  DRCreate.x := r.x + 90 * s;
  DRCreate.y := r.y + 109 * s;
  DRCancel.w := TexCancel.Width * s;
  DRCancel.h := TexCancel.Height * s;
  DRCancel.x := r.x + 272 * s;
  DRCancel.y := r.y + 109 * s;
  DRName.x := r.x + 34 * s;
  DRName.y := r.y + 61 * s;
  DRName.w := 422 * s;
  DRName.h := 25 * s;
  CRCreate.x := r.x + 90 * s;
  CRCreate.y := r.y + 120 * s;
  CRCreate.w := TexCreate.Width * s;
  CRCreate.h := 28 * s;
  CRCancel.x := r.x + 272 * s;
  CRCancel.y := r.y + 120 * s;
  CRCancel.w := TexCancel.Width * s;
  CRCancel.h := 28 * s;
  Rects[0] := @CRCreate;
  Rects[1] := @CRCancel;
  GradCreate := 0;
  GradCancel := 0;
  Grads[0] := @GradCreate;
  Grads[1] := @GradCancel;
  CurName := '';
  Carret := 0;
end;

destructor TMenuCreateProfile.Destroy;
begin
  inherited Destroy;
end;

procedure TMenuCreateProfile.Render;
  var i: Integer;
  var s: Single;
  var CarretPos: Single;
begin
  g2.PicRect(r.x, r.y, r.w, r.h, $ffffffff, TexMenu, bmNormal, tfLinear);
  g2.PicRect(
    DRCreate.x, DRCreate.y, DRCreate.w, DRCreate.h,
    $ffffff or (Round(GradCreate * $ff) shl 24), TexCreate, bmNormal, tfLinear
  );
  g2.PicRect(
    DRCancel.x, DRCancel.y, DRCancel.w, DRCancel.h,
    $ffffff or (Round(GradCancel * $ff) shl 24), TexCancel, bmNormal, tfLinear
  );
  s := DRName.h / App.Font0.TextHeight(CurName);
  App.Font0.Print(DRName.x, DRName.y, s, s, $ff000000, CurName, bmNormal, tfLinear);
  CarretPos := DRName.x;
  for i := 0 to Carret - 1 do
  CarretPos := CarretPos + App.Font0.TextWidth(CurName[i + 1]) * s;
  g2.PrimLine(
    CarretPos, DRName.t + DRName.h * 0.1, CarretPos, DRName.b - DRName.h * 0.1,
    $000000 or (Round(Abs(Sin(G2PiTime(300))) * $ff) shl 24)
  );
  //for i := 0 to High(Rects) do
  //g2.PrimRectHollow(
  //  Rects[i]^.x, Rects[i]^.y, Rects[i]^.w, Rects[i]^.h,
  //  $ffff0000
  //);
end;

procedure TMenuCreateProfile.Update;
  var mc: TPoint;
  var i: Integer;
begin
  mc := g2.MousePos;
  for i := 0 to High(Rects) do
  if PtInRect(Rects[i]^, mc) then
  begin
    Grads[i]^ := Grads[i]^ + 0.1;
    if Grads[i]^ > 1 then Grads[i]^ := 1;
  end
  else
  begin
    Grads[i]^ := Grads[i]^ - 0.1;
    if Grads[i]^ < 0 then Grads[i]^ := 0;
  end;
end;

procedure TMenuCreateProfile.Print(const Char: AnsiChar);
  var s0, s1: AnsiString;
begin
  if Length(CurName) < 18 then
  begin
    if Carret > 0 then
    s0 := G2StrCut(CurName, 1, Carret)
    else
    s0 := '';
    if Carret < Length(CurName) then
    s1 := G2StrCut(CurName, Carret + 1, Length(CurName))
    else
    s1 := '';
    CurName := s0 + Char + s1;
    Inc(Carret);
  end;
end;

procedure TMenuCreateProfile.MouseUp(const x, y: Integer);
  var mc: TPoint;
begin
  mc := Point(x, y);
  if PtInRect(CRCancel, mc) then
  begin
    App.MenuProfile.SubMenu := nil;
    App.MenuProfile.Show;
  end
  else if PtInRect(CRCreate, mc) then
  begin
    if CommitCreate then
    begin
      App.MenuProfile.SubMenu := nil;
      App.MenuProfile.Show;
    end;
  end;
end;

procedure TMenuCreateProfile.KeyDown(const Key: Integer);
  var i: Integer;
  var s0, s1: AnsiString;
begin
  case Key of
    G2K_Return:
    begin
      if CommitCreate then
      begin
        App.MenuProfile.SubMenu := nil;
        App.MenuProfile.Show;
      end;
    end;
    G2K_Back:
    begin
      if Carret > 0 then
      begin
        Dec(Carret);
        s0 := G2StrCut(CurName, 1, Carret);
        s1 := G2StrCut(CurName, Carret + 2, Length(CurName));
        CurName := s0 + s1;
      end;
    end;
    G2K_Delete:
    begin
      if Carret < Length(CurName) then
      begin
        s0 := G2StrCut(CurName, 1, Carret);
        s1 := G2StrCut(CurName, Carret + 2, Length(CurName));
        CurName := s0 + s1;
      end;
    end;
    G2K_Left:
    begin
      if Carret > 0 then
      Dec(Carret)
    end;
    G2K_Right:
    begin
      if Carret < Length(CurName) then
      Inc(Carret);
    end;
  end;
end;

procedure TMenuCreateProfile.Show;
  var i: Integer;
begin
  for i := 0 to High(Grads) do
  Grads[i]^ := 0;
  CurName := '';
  Carret := 0;
end;

function TMenuCreateProfile.CommitCreate: Boolean;
begin
  if (Length(CurName) > 0)
  and (App.ProfileCount < 35)
  and (App.ProfileFind(CurName) = -1) then
  begin
    App.ProfileAdd(CurName);
    Result := True;
  end
  else
  Result := False;
end;
//TMenuCreateProfile END

//TGameObject BEGIN
constructor TGameObject.Create;
begin
  inherited Create;
  App.Level.GameObjects.Add(Self);
  Dead := False;
  Alt := 1;
  Order := 0.5;
end;

destructor TGameObject.Destroy;
begin
  App.Level.GameObjects.Remove(Self);
  inherited Destroy;
end;

procedure TGameObject.Die;
begin
  if not Dead then
  begin
    Dead := True;
    App.Level.GameObjectsDead.Add(Self);
    OnDie;
  end;
end;

procedure TGameObject.OnDie;
begin

end;
//TGameObject END

//TClouds BEGIN
procedure TClouds.Render;
  var bm: TG2BlendMode;
  var t0x, t0y, t1x, t1y, ScaleRcp: Single;
begin
  case BlendMode of
    0: bm := bmNormal;
    1: bm := bmAdd;
  end;
  ScaleRcp := 1 / Scale;
  t0x := Pos.x * ScaleRcp;
  t0y := (Pos.y - App.Level.VCamPos.y - App.Level.VScreenSize.y) * ScaleRcp;
  t1x := (Pos.x + App.Level.VScreenSize.x) * ScaleRcp;
  t1y := (Pos.y - App.Level.VCamPos.y) * ScaleRcp;
  g2.PicRect(
    0, 0, g2.Params.Width, g2.Params.Height,
    t0x, t0y, t1x, t1y,
    Color, Img^.Texture,
    bm, tfLinear
  );
end;

procedure TClouds.Update;
begin
  Pos.x := Pos.x - SpeedH;
  Pos.y := Pos.y - SpeedV;
end;
//TClouds END

//TShot BEGIN
constructor TShot.Create;
begin
  inherited Create;
  Faction := 0;
  Damage := 100;
end;

procedure TShot.OnHit;
begin

end;
//TShot END

//TShotColored BEGIN
constructor TShotColored.Create;
begin
  inherited Create;
  _CurColor := 0;
  ColorInterval := 500;
  Brightness := $ff;
end;

procedure TShotColored.Update;
begin
  if Length(_Colors) > 1 then
  begin
    _CurColor := _CurColor + 1000 / (g2.Params.TargetUPS * ColorInterval);
    if _CurColor >= Length(_Colors) then _CurColor := _CurColor - Trunc(_CurColor);
  end;
end;

procedure TShotColored.AddColor(const Color: TG2Color);
begin
  SetLength(_Colors, Length(_Colors) + 1);
  _Colors[High(_Colors)] := Color;
end;

function TShotColored.GetColor: TG2Color;
  var cn0, cn1: Integer;
begin
  if Length(_Colors) > 1 then
  begin
    cn0 := Trunc(_CurColor);
    cn1 := (cn0 + 1) mod Length(_Colors);
    Result := G2LerpColor(_Colors[cn0], _Colors[cn1], Frac(_CurColor));
  end
  else if Length(_Colors) > 0 then
  begin
    Result := _Colors[0];
  end
  else
  begin
    Result := $ffffffff;
  end;
end;
//TShotColored EMD

//TShotParticle BEGIN
constructor TShotParticle.Create;
begin
  inherited Create;
  Alt := 7;
  Order := 0.9995;
  App.Level.Shots.Add(Self);
end;

destructor TShotParticle.Destroy;
begin
  App.Level.Shots.Remove(Self);
  inherited Destroy;
end;

procedure TShotParticle.Render;
  var s, v, v0, v1, v2, v3, di, dp, pp0, pp1: TG2Vec2;
  var b: Byte;
  var cn0, cn1: Integer;
begin
  s.x := App.Level.SizeVirtualToScreen(Size.x, 1 + 7 * 0.1);
  s.y := App.Level.SizeVirtualToScreen(Size.y, 1 + 7 * 0.1);
  v := App.Level.PosVirtualToScreen(Pos, 1 + 7 * 0.1);
  di := Dir; di.x := -di.x;
  dp := di.Perp;
  v0 := v - di * (s.y * 0.5) + dp * (s.x * 0.5);
  v1 := v0 - dp * s.x;
  v2 := v0 + di * s.y;
  v3 := v1 + di * s.y;
  g2.PicQuad(
    v0, v1, v2, v3,
    G2Vec2(0, Img^.Texture.SizeTV), G2Vec2(0, 0),
    G2Vec2(Img^.Texture.SizeTU, Img^.Texture.SizeTV), G2Vec2(Img^.Texture.SizeTU, 0),
    GetColor, Img^.Texture,
    bmNormal, tfLinear
  );
  g2.PicQuad(
    v0, v1, v2, v3,
    G2Vec2(0, Img^.Texture.SizeTV), G2Vec2(0, 0),
    G2Vec2(Img^.Texture.SizeTU, Img^.Texture.SizeTV), G2Vec2(Img^.Texture.SizeTU, 0),
    $ffffff or (Brightness shl 24), Img^.Texture,
    bmAdd, tfLinear
  );
end;

procedure TShotParticle.Update;
begin
  inherited Update;
  Pos := Pos + Dir * Vel;
  Pos.y := Pos.y + App.Level.VScreenSpeed;
  if (Pos.x < -0.1)
  or (Pos.x > 1.1)
  or (Pos.y > App.Level.VCamPos.y + 0.475)
  or (Pos.y < App.Level.VCamPos.y - 0.475) then
  Die;
end;

procedure TShotParticle.ProcessHits;
  var f, i: Integer;
begin
  for f := 0 to High(App.Level.Factions) do
  if f <> Faction - 1 then
  for i := 0 to App.Level.Factions[f].Count - 1 do
  begin
    if CheckHit(TFactionObject(App.Level.Factions[f][i])) then
    begin
      TFactionObject(App.Level.Factions[f][i]).Hit(Damage);
      OnHit;
      Die;
      Exit;
    end;
  end;
end;

function TShotParticle.CheckHit(const FactionObject: TFactionObject): Boolean;
begin
  Result := (
    (Pos.x > FactionObject.xl)
    and (Pos.x < FactionObject.xh)
    and (Pos.y > FactionObject.yl)
    and (Pos.y < FactionObject.yh)
  );
end;
//TShotParticle END

//TShotOmni BEGIN
constructor TShotOmni.Create;
begin
  inherited Create;
  Img := App.ImgListShots.Find('Shot02.png');
  Angle := 0;
  VelAngular := 0;
  Size.SetValue(0.01, 0.005);
end;

procedure TShotOmni.Update;
begin
  inherited Update;
  if not Dead then
  begin
    Angle := Angle + VelAngular;
  end;
end;

procedure TShotOmni.Render;
  var ScrSize, ScrPos: TG2Vec2;
begin
  ScrSize.x := App.Level.SizeVirtualToScreen(Size.x, 1 + Alt * 0.1);
  ScrSize.y := App.Level.SizeVirtualToScreen(Size.y, 1 + Alt * 0.1);
  ScrPos := App.Level.PosVirtualToScreen(Pos, 1 + Alt * 0.1);
  g2.PicRect(
    ScrPos, ScrSize.x, ScrSize.y, GetColor, 0.5, 0.5,
    1, 1, Angle, False, False,
    Img^.Texture, Img^.Texture.Width, Img^.Texture.Height,
    0, bmNormal, tfLinear
  );
  g2.PicRect(
    ScrPos, ScrSize.x, ScrSize.y, $ffffff or (Brightness shl 24), 0.5, 0.5,
    1, 1, Angle, False, False,
    Img^.Texture, Img^.Texture.Width, Img^.Texture.Height,
    0, bmAdd, tfLinear
  );
end;
//TShotOmni END

//TShotLaser BEGIN
constructor TShotLaser.Create;
begin
  inherited Create;
  Alt := 7;
  Order := 0.9995;
  App.Level.Shots.Add(Self);
  Dist := 2;
  Size := 0.01;
  Img := App.ImgListShots.Find('Laser0.png');
  ImgFlare := App.ImgListShots.Find('Flare0.png');
  ImgDust := App.ImgListShots.Find('LaserDust.png');
  State := lsIdle;
end;

destructor TShotLaser.Destroy;
begin
  App.Level.Shots.Remove(Self);
  inherited Destroy;
end;

procedure TShotLaser.Render;
  var v, vend, v0, v1, v2, v3, dp: TG2Vec2;
  var ss, ss2: Single;
  var c: TG2Color;
begin
  c := GetColor;
  if State = lsFire then
  begin
    dp := Dir.Perp * Size;
    v0 := App.Level.PosVirtualToScreen(Pos - dp, 1 + Alt * 0.1);
    v1 := App.Level.PosVirtualToScreen(Pos + dp, 1 + Alt * 0.1);
    v2 := App.Level.PosVirtualToScreen(Pos + Dir * Dist - dp, 1 + Alt * 0.1);
    v3 := App.Level.PosVirtualToScreen(Pos + Dir * Dist + dp, 1 + Alt * 0.1);
    g2.PicQuad(
      v0, v1, v2, v3,
      G2Vec2(0, 0), G2Vec2(1, 0),
      G2Vec2(0, 0), G2Vec2(1, 0),
      c, Img^.Texture,
      bmNormal, tfLinear
    );
  end;
  if (State = lsPrime) or (State = lsFire) then
  begin
    ss := App.Level.SizeVirtualToScreen(Size * 3 * (1 + Sin(G2PiTime(200)) * 0.5), 1 + Alt * 0.1);
    v := App.Level.PosVirtualToScreen(Pos, 1 + Alt * 0.1);
    g2.PicRect(
      v.x - ss, v.y - ss, ss * 2, ss * 2, c,
      ImgFlare^.Texture, bmNormal, tfLinear
    );
    ss2 := App.Level.SizeVirtualToScreen(Size * 3, 1 + Alt * 0.1);
    vend := App.Level.PosVirtualToScreen(Pos + Dir * Dist, 1 + Alt * 0.1);
    g2.PicRect(
      vend.x - ss2, vend.y - ss2, ss2 * 2, ss2 * 2, c,
      ImgFlare^.Texture, bmNormal, tfLinear
    );
  end;
  if State = lsFire then
  begin
    g2.PicQuad(
      v0, v1, v2, v3,
      G2Vec2(0, 0), G2Vec2(1, 0),
      G2Vec2(0, 0), G2Vec2(1, 0),
      $ffffffff, Img^.Texture,
      bmAdd, tfLinear
    );
  end;
  if (State = lsPrime) or (State = lsFire) then
  begin
    g2.PicRect(
      v.x - ss, v.y - ss, ss * 2, ss * 2, $ffffffff,
      ImgFlare^.Texture, bmAdd, tfLinear
    );
    g2.PicRect(
      vend.x - ss2, vend.y - ss2, ss2 * 2, ss2 * 2, $ffffffff,
      ImgFlare^.Texture, bmAdd, tfLinear
    );
  end;
end;

procedure TShotLaser.Update;
begin
  inherited Update;
end;

procedure TShotLaser.ProcessHits;
  var f, i: Integer;
  var dc, d: Single;
  var HitPos: TG2Vec2;
  var r: TG2Rect;
  var HitObj: TFactionObject;
begin
  dc := 2;
  if State = lsFire then
  begin
    HitObj := nil;
    for f := 0 to High(App.Level.Factions) do
    if f <> Faction - 1 then
    for i := 0 to App.Level.Factions[f].Count - 1 do
    begin
      r.l := TFactionObject(App.Level.Factions[f][i]).xl;
      r.t := TFactionObject(App.Level.Factions[f][i]).yl;
      r.r := TFactionObject(App.Level.Factions[f][i]).xh;
      r.b := TFactionObject(App.Level.Factions[f][i]).yh;
      if G2Ray2VsRect(Pos, Dir, r, HitPos, d)
      and (d < dc) then
      begin
        dc := d;
        HitObj := TFactionObject(App.Level.Factions[f][i]);
      end;
    end;
    if HitObj <> nil then
    begin
      HitObj.Hit(Damage);
      OnHit;
    end;
  end;
  Dist := dc;
end;
//TShotLaser END

//TShotMissile BEGIN
constructor TShotMissile.Create;
begin
  inherited Create;
  Img := App.ImgListShots.Find('Missile.png');
  ImgFlare := App.ImgListShots.Find('Flare0.png');
  Alt := 7;
  Order := 0.9995;
  App.Level.Shots.Add(Self);
  Trace := TTraceMissile.Create;
  Trace.SpawnType := TEffectFlame;
  Trace.Pos := Pos;
  Trace.Vel.SetValue(0, 0);
  Trace.SpawnInterval := 5;
  Trace.Restitution := 0.97;
  Trace.Acc.SetValue(0, 0);
  Trace.Dumping := 1;
  Trace.Order := 0.9994;
  Trace.CurOrder := Trace.Order;
  Trace.OrderInc := 0;
  Trace.SetTimes(0, 0);
end;

destructor TShotMissile.Destroy;
begin
  Trace.Die;
  App.Level.Shots.Remove(Self);
  inherited Destroy;
end;

procedure TShotMissile.Render;
  var s, v, v0, v1, v2, v3, di, dp, pp0, pp1: TG2Vec2;
  var b: Byte;
  var cn0, cn1: Integer;
  var fs: Single;
begin
  s.x := App.Level.SizeVirtualToScreen(Size.x, 1 + 7 * 0.1);
  s.y := App.Level.SizeVirtualToScreen(Size.y, 1 + 7 * 0.1);
  fs := App.Level.SizeVirtualToScreen(Size.x * 1.5 * Sin(G2PiTime(40) * 0.5), 1 + 7 * 0.1);
  v := App.Level.PosVirtualToScreen(Pos, 1 + 7 * 0.1);
  di := Dir; di.x := -di.x;
  dp := di.Perp;
  v0 := v - di * (s.y * 0.5) + dp * (s.x * 0.5);
  v1 := v0 - dp * s.x;
  v2 := v0 + di * s.y;
  v3 := v1 + di * s.y;
  g2.PicRect(
    v + di * (s.y * 0.5) - G2Vec2(fs, fs), fs * 2, fs * 2,
    $ffff8800, ImgFlare^.Texture, bmNormal, tfLinear
  );
  g2.PicRect(
    v + di * (s.y * 0.5) - G2Vec2(fs, fs), fs * 2, fs * 2,
    $ffffffff, ImgFlare^.Texture, bmAdd, tfLinear
  );
  g2.PicQuad(
    v0, v1, v2, v3,
    G2Vec2(0, Img^.Texture.SizeTV), G2Vec2(0, 0),
    G2Vec2(Img^.Texture.SizeTU, Img^.Texture.SizeTV), G2Vec2(Img^.Texture.SizeTU, 0),
    $ffffffff, Img^.Texture,
    bmNormal, tfLinear
  );
end;

procedure TShotMissile.Update;
begin
  Pos := Pos + Dir * Vel;
  Pos.y := Pos.y + App.Level.VScreenSpeed;
  Trace.Pos := Pos;
  if (Pos.x < -0.1)
  or (Pos.x > 1.1)
  or (Pos.y > App.Level.VCamPos.y + 0.475)
  or (Pos.y < App.Level.VCamPos.y - 0.475) then
  Die;
end;

procedure TShotMissile.ProcessHits;
  var f, i: Integer;
begin
  for f := 0 to High(App.Level.Factions) do
  if f <> Faction - 1 then
  for i := 0 to App.Level.Factions[f].Count - 1 do
  begin
    if CheckHit(TFactionObject(App.Level.Factions[f][i])) then
    begin
      TFactionObject(App.Level.Factions[f][i]).Hit(Damage);
      OnHit;
      Die;
      Exit;
    end;
  end;
end;

function TShotMissile.CheckHit(const FactionObject: TFactionObject): Boolean;
begin
  Result := (
    (Pos.x > FactionObject.xl)
    and (Pos.x < FactionObject.xh)
    and (Pos.y > FactionObject.yl)
    and (Pos.y < FactionObject.yh)
  );
end;

procedure TShotMissile.OnHit;
begin
  App.Level.CreateExplosion(Pos);
end;
//TShotMissile END

//TShotMissileDizzy BEGIN
constructor TShotMissileDizzy.Create;
begin
  inherited Create;
  _IsDizzy := False;
end;

procedure TShotMissileDizzy.Update;
begin
  inherited Update;
  if not Dead then
  begin
    if not _IsDizzy then
    begin
      _IsDizzy := True;
      _PrevDir := Dir;
      _NextDir := (_PrevDir + _PrevDir.Perp * ((Random(201) - 100) * 0.005)).Norm;
      _TimeDizzy := App.CurTime;
      _TimeDizzyDuration := 50;
    end
    else
    begin
      if App.CurTime - _TimeDizzy > _TimeDizzyDuration then
      begin
        Dir := _NextDir;
        _PrevDir := _NextDir;
        _NextDir := (_PrevDir + _PrevDir.Perp * ((Random(201) - 100) * 0.005)).Norm;
        _TimeDizzy := App.CurTime;
        _TimeDizzyDuration := 50 + Random(300);
      end
      else
      begin
        Dir := G2LerpVec2(_PrevDir, _NextDir, (App.CurTime - _TimeDizzy) / _TimeDizzyDuration).Norm;
      end;
    end;
  end;
end;
//TShotMissileDizzy END

//TShotBullet BEGIN
constructor TShotBullet.Create;
begin
  inherited Create;
  Img := App.ImgListShots.Find('Shot01.png');
  //GlowImg := App.ImgListShots.Find('Shot01Glow.png');
end;

procedure TShotBullet.OnHit;
begin
  //App.Level.CreateSparks(Pos);
end;
//TShotBullet END

//TShotPlasma BEGIN
constructor TShotPlasma.Create;
begin
  inherited Create;
  AddColor($ff00ff00);
  AddColor($ff884400);
  ColorInterval := 100;
  Brightness := $88;
end;

procedure TShotPlasma.OnHit;
begin
  App.Level.CreatePlasmaBlast(Pos);
end;
//TShotPlasma END

//TLevelObject BEGIN
procedure TLevelObject.Render;
  var sx, sy: Single;
  var v: TG2Vec2;
begin
  sx := App.Level.SizeVirtualToScreen(Size.x, 1 + Alt * 0.1);
  sy := App.Level.SizeVirtualToScreen(Size.y, 1 + Alt * 0.1);
  v := App.Level.PosVirtualToScreen(Pos, 1 + Alt * 0.1);
  g2.PicRect(
    v, sx, sy, Color,
    0.5, 0.5, Scale, Scale, Ang,
    (Flip = ofHorizontal) or (Flip = ofBoth),
    (Flip = ofVertical) or (Flip = ofBoth),
    Img^.Texture, Img^.Texture.Width, Img^.Texture.Height,
    0, bmNormal, tfLinear
  );
end;

procedure TLevelObject.Update;
begin

end;
//TLevelObject END

//TEffect BEGIN
constructor TEffect.Create;
begin
  inherited Create;
  Alt := 7;
  Order := 0.9998;
  _TimeCreated := App.CurTime;
  _TimeDuration := 1000;
  _TimeDelay := 0;
  t := 0;
end;

destructor TEffect.Destroy;
begin
  inherited Destroy;
end;

procedure TEffect.Render;
begin

end;

procedure TEffect.Update;
begin
  if _TimeDuration > 0 then
  begin
    if App.CurTime > _TimeCreated + _TimeDelay then
    t := (App.CurTime - _TimeCreated - _TimeDelay) / _TimeDuration
    else
    t := 0;
    if t >= 1 then
    Die;
  end
  else
  t := 0;
end;

procedure TEffect.SetTimes(const Delay, Duration: IntU32);
begin
  _TimeDelay := Delay;
  _TimeDuration := Duration;
end;
//TEffect END

//TTrace BEGIN
constructor TTrace.Create;
begin
  inherited Create;
  _TimeDuration := 2000;
  Pos.SetValue(0, 0);
  Acc.SetValue(0, 0);
  Vel.SetValue(0, 0);
  Restitution := 1;
  Dumping := 1;
  SpawnType := nil;
  SpawnInterval := 100;
  SpawnTime := App.CurTime - SpawnInterval;
  CurOrder := Order;
  OrderInc := 1E-6;
end;

destructor TTrace.Destroy;
begin
  inherited Destroy;
end;

procedure TTrace.Update;
  var e: TEffect;
begin
  inherited Update;
  if not Dead then
  begin
    Acc := Acc * Dumping;
    Vel := (Vel + Acc) * Restitution;
    Pos := Pos + Vel;
    if App.CurTime - SpawnTime > SpawnInterval then
    begin
      SpawnTime := App.CurTime;
      e := SpawnType.Create;
      e.Pos := Pos;
      e.Order := CurOrder;
      OnCreateEffect(e);
      CurOrder := CurOrder + OrderInc;
    end;
  end;
end;

procedure TTrace.OnCreateEffect(const Effect: TEffect);
begin

end;
//TTrace END

//TTraceMissile BEGIN
procedure TTraceMissile.OnCreateEffect(const Effect: TEffect);
  var Flame: TEffectFlame;
begin
  Flame := TEffectFlame(Effect);
  Flame.ScaleStart := 1.5;
  Flame.ScaleEnd := 0.5;
  Flame.VelLinear := G2RandomCirclePoint * Random(101) * 0.00001;
  Flame.VelAngular := G2RandomPi * 0.001;
  Flame.Restitution := 0.92;
end;
//TTraceMissile END

//TEffectWave BEGIN
constructor TEffectWave.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('WaveOutline.png');
  _TimeDuration := 200;
end;

destructor TEffectWave.Destroy;
begin
  inherited Destroy;
end;

procedure TEffectWave.Render;
  var s, r0, r1: Single;
  var ScrPos: TG2Vec2;
  var b: Byte;
  var c, c0: TG2Color;
begin
  ScrPos := App.Level.PosVirtualToScreen(Pos, 1 + Alt * 0.1);
  s := 0.2 + t * 2;
  b := Round((1 - t) * 255);
  c := G2Color(b, b, b, $ff);
  b := Round((1 - t) * 80);
  c0 := G2Color(b, b, b, $ff);
  r0 := G2LerpFloat(0.01 * App.Level.Resolution, 0.2 * App.Level.Resolution, t);
  r1 := G2LerpFloat(0.05 * App.Level.Resolution, 0.21 * App.Level.Resolution, t);
  App.DrawRing(
    ScrPos, r0, r1,
    Img^.Texture,
    c0, bmAdd, 2
  );
end;
//TEffectWave END

//TEffectExplosion BEGIN
constructor TEffectExplosion.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('Explosion' + IntToStr(Random(4)) + '.png');
  _TimeDelay := Random(100);
  _TimeDuration := 500;
  Scale := 1;
end;

destructor TEffectExplosion.Destroy;
begin
  inherited Destroy;
end;

procedure TEffectExplosion.Render;
  var s, sc: Single;
  var ScrPos: TG2Vec2;
  var a: Byte;
begin
  ScrPos := App.Level.PosVirtualToScreen(Pos, 1 + Alt * 0.1);
  s := 0.1 * App.Level.Resolution;
  sc := Scale * t;
  a := Round(Abs(1 - Abs(Cos(t * Pi))) * $ff);
  g2.PicRect(ScrPos, s, s, G2Color($ff, $ff, $ff, a), 0.5, 0.5, sc, sc, 0, False, False, Img^.Texture, Img^.Texture.Width, Img^.Texture.Height, 0, bmNormal, tfLinear);
end;
//TEffectExplosion END

//TEffectFlare BEGIN
constructor TEffectFlare.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('Flare.png');
  ScaleStart := 1;
  ScaleEnd := 1;
  Size := 0.15;
  _TimeDelay := 0;
  _TimeDuration := 500;
  Order := Order + 0.0001;
end;

destructor TEffectFlare.Destroy;
begin
  inherited Destroy;
end;

procedure TEffectFlare.Render;
  var CurScale, CurSize: Single;
  var ScrPos: TG2Vec2;
  var b: Byte;
  var c: TG2Color;
begin
  b := Round((1 - t) * $ff);
  c := G2Color(b, b, b, $ff);
  CurScale := G2LerpFloat(ScaleStart, ScaleEnd, t);
  CurSize := Size * CurScale * App.Level.Resolution;
  ScrPos := App.Level.PosVirtualToScreen(Pos, 1 + Alt * 0.1) - CurSize * 0.5;
  g2.PicRect(ScrPos, CurSize, CurSize, c, Img^.Texture, bmAdd, tfLinear);
end;
//TEffectFlare END

//TEffectFlame BEGIN
constructor TEffectFlame.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('Flame.png');
  ScaleStart := 1;
  ScaleEnd := 1;
  Size := 0.03;
  _TimeDelay := 0;
  _TimeDuration := 500;
  fw := Img^.Texture.Width div 4;
  fh := Img^.Texture.Height div 4;
  Ang := G2Random2Pi;
  VelLinear.SetValue(0, 0);
  VelAngular := 0;
  Restitution := 1;
end;

destructor TEffectFlame.Destroy;
begin
  inherited Destroy;
end;

procedure TEffectFlame.Render;
  var CurScale, CurSize: Single;
  var ScrPos: TG2Vec2;
  var b: Byte;
begin
  b := Round((1 - t) * $ff);
  CurScale := G2LerpFloat(ScaleStart, ScaleEnd, t);
  CurSize := Size * CurScale * App.Level.Resolution;
  ScrPos := App.Level.PosVirtualToScreen(Pos, 1 + Alt * 0.1);
  g2.PicRect(ScrPos, CurSize, CurSize, G2Color($ff, $ff, $ff, b), 0.5, 0.5, 1, 1, Ang, False, False, Img^.Texture, fw, fh, Round(t * 15), bmNormal, tfLinear);
end;

procedure TEffectFlame.Update;
begin
  inherited Update;
  if not Dead then
  begin
    VelLinear := VelLinear * Restitution;
    Pos := Pos + VelLinear;
    Ang := Ang + VelAngular;
  end;
end;
//TEffectFlame END

//TFactionObject BEGIN
procedure TFactionObject.SetFaction(const Value: Integer);
begin
  if Value = _Faction then Exit;
  if _Faction > 0 then
  App.Level.Factions[_Faction - 1].Remove(Self);
  _Faction := Value;
  if _Faction > 0 then
  App.Level.Factions[_Faction - 1].Add(Self);
end;

constructor TFactionObject.Create;
begin
  inherited Create;
  _Faction := 0;
end;

destructor TFactionObject.Destroy;
begin
  Faction := 0;
  inherited Destroy;
end;
//TFactionObject END

//TEnemy BEGIN
procedure TEnemy.SetPath(const Value: PPath);
begin
  if _Path <> Value then
  begin
    _Path := Value;
    PathPos := _Path^.GetPos(0, 1, 0);
    AdjustPos;
  end;
end;

constructor TEnemy.Create;
begin
  inherited Create;
  Alt := 7;
  Order := 0.9996;
  Pt0 := 0;
  Pt1 := 1;
  g := 0;
  _Path := nil;
  Size.SetValue(0.08, 0.08);
  Speed := 0.01;
  Angle := 0;
  HealthMax := 100;
  Health := HealthMax;
  HitTime := App.CurTime - 1000;
  Faction := 2;
  ShotLag := 1000;
  ShotRounds := 1;
  ShotRoundLag := 100;
  ShotRoundCur := 0;
  LastShotTime := App.CurTime + Random(ShotLag);
  LookDir := G2Vec2(0, -1);
  ImgFrame := 0;
  ImgFlip := False;
  TracePlayer := False;
  TracePlayerSpeed := 0.03;
end;

destructor TEnemy.Destroy;
begin
  inherited Destroy;
end;

procedure TEnemy.Render;
  var ScrPos, ScrSize: TG2Vec2;
  var Frame: Integer;
  var Flip: Boolean;
  var s: Single;
  var b: Byte;
//  var v0, v1: TG2Vec2;
begin
  ScrPos := App.Level.PosVirtualToScreen(Pos, 1 + 7 * 0.1);
  ScrSize.x := App.Level.SizeVirtualToScreen(Size.x, 1);
  ScrSize.y := App.Level.SizeVirtualToScreen(Size.y, 1);
  g2.PicRect(ScrPos, ScrSize.x, ScrSize.y, $ffffffff, 0.5, 0.5, 1, 1, Angle, ImgFlip, False, Img^.Texture, 128, 128, ImgFrame, bmNormal, tfLinear);
  s := (App.CurTime - HitTime) / 100;
  if s < 1 then
  begin
    b := Round((1 - s) * $ff);
    g2.PicRect(ScrPos, ScrSize.x, ScrSize.y, G2Color(b, b, b, $ff), 0.5, 0.5, 1, 1, Angle, ImgFlip, False, Img^.Texture, 128, 128, ImgFrame, bmAdd, tfLinear);
  end;
  //v0 := App.Level.PosVirtualToScreen(G2Vec2(xl, yh), 1 + 7 * 0.1);
  //v1 := App.Level.PosVirtualToScreen(G2Vec2(xh, yl), 1 + 7 * 0.1);
  //g2.PrimRectHollow(v0.x, v0.y, v1.x - v0.x, v1.y - v0.y, $ffff0000);
end;

procedure TEnemy.Update;
  var sx, sy, d: Single;
  var i: Integer;
  var PlayerDir, MoveDir: TG2Vec2;
begin
  if Health <= 0 then Die;
  AdvancePos;
  if not Dead then
  begin
    if TracePlayer and (App.Player <> nil) then
    begin
      PlayerDir := (App.Player.Pos - Pos).Norm;
      d := PlayerDir.Dot(LookDir.Perp);
      if Abs(d) < Sin(TracePlayerSpeed * 2) then
      Angle := PlayerDir.AngleOY + Pi
      else
      begin
        if d > 0 then Angle := Angle - TracePlayerSpeed
        else Angle := Angle + TracePlayerSpeed;
      end;
      G2SinCos(-(Angle + HalfPi), LookDir.y, LookDir.x);
    end
    else
    begin
      Angle := 0;
      LookDir.SetValue(0, -1);
    end;
    MoveDir := (Pos - PrevPos).Norm;
    ImgFrame := Round(LookDir.Perp.Dot(MoveDir) * 3);
    ImgFlip := ImgFrame < 0;
    ImgFrame := Abs(ImgFrame);
    sx := Size.x * 0.4;
    sy := Size.y * 0.4;
    xl := Pos.x - sx; xh := Pos.x + sx;
    yl := Pos.y - sy; yh := Pos.y + sy;
    if ((ShotRoundCur > 0) and (App.CurTime - LastShotTime > ShotRoundLag))
    or (App.CurTime - LastShotTime > ShotLag) then
    begin
      OnShoot;
      LastShotTime := App.CurTime;
      ShotRoundCur := (ShotRoundCur + 1) mod ShotRounds;
    end;
  end;
end;

procedure TEnemy.OnDie;
  var e: TEffect;
  var Trace: TTrace;
  var i: Integer;
  const Powerups: array[0..6] of CPlayerPowerup = (
    TPlayerPowerupHeal, TPlayerPowerupAddHealth, TPlayerPowerupAddShield, TPlayerPowerupAddShield,
    TPlayerPowerupMissile, TPlayerPowerupLaser, TPlayerPowerupBlaster
  );
begin
  if Health <= 0 then
  begin
    App.Level.CreateExplosion(Pos);
    if Random(31) = 0 then
    App.Level.CreatePowerup(Powerups[Random(Length(Powerups))], Pos);
    App.AddScore(10);
  end;
end;

procedure TEnemy.OnShoot;
begin

end;

procedure TEnemy.Hit(const Amount: Integer);
begin
  Health := Health - Amount;
  HitTime := App.CurTime;
end;

procedure TEnemy.AdjustPos;
  var v0, v1: TG2Vec2;
  var r: Single;
begin
  v0 := App.Level.PosVirtualToScreen(G2Vec2(0, App.Level.VCamPos.y - App.Level.VScreenSize.y * 0.5));
  Pos.x := PathPos.x * App.Level.Resolution;
  Pos.y := v0.y - PathPos.y * App.Level.Resolution;
  Pos := App.Level.PosScreenToVirtual(Pos, 1 + 7 * 0.1);
end;

procedure TEnemy.AdvancePos;
  var gvel, dg, d: Single;
  var dpt0, dpt1: Integer;
  var v: TG2Vec2;
begin
  if not Dead then
  begin
    gvel := Speed / (_Path^.Waypoints[Pt1].Pos - _Path^.Waypoints[Pt0].Pos).Len;
    dg := g + gvel;
    dpt0 := Pt0;
    dpt1 := Pt1;
    while dg > 1 do
    begin
      dg := dg - 1;
      dpt0 := dpt1;
      dpt1 := dpt1 + 1;
    end;
    if dpt1 > High(Path^.Waypoints) then
    begin
      Die;
    end;
    v := Path^.GetPos(dpt0, dpt1, dg);
    d := Speed / (v - PathPos).Len;
    gvel := gvel * d;
    g := g + gvel;
    while g > 1 do
    begin
      g := g - 1;
      Pt0 := Pt1;
      Pt1 := Pt1 + 1;
    end;
    if Pt1 > High(Path^.Waypoints) then
    begin
      Die;
    end;
    PathPos := Path^.GetPos(Pt0, Pt1, g);
    PrevPos := Pos;
    AdjustPos;
  end;
end;
//TEnemy END

//TEnemyAlien01 BEGIN
constructor TEnemyAlien01.Create;
begin
  inherited Create;
  Img := App.ImgListEnemies.Find('Enemy0.png');
end;

procedure TEnemyAlien01.OnShoot;
  var Shot: TShot;
begin
  Shot := App.Level.CreateShotParticle(TShotPlasma, Pos, G2Vec2(0, -1), G2Vec2(0.015, 0.03), 0.005);
  Shot.Faction := Faction;
  Shot.Damage := 50;
end;
//TEnemyAlien01 END

//TEnemyAlien02 BEGIN
constructor TEnemyAlien02.Create;
begin
  inherited Create;
  Img := App.ImgListEnemies.Find('Enemy1.png');
  ShotLag := 2000;
end;

procedure TEnemyAlien02.OnShoot;
  var Shot: TShot;
begin
  Shot := App.Level.CreateShotMissile(TShotMissileDizzy, Pos + G2Vec2(0, -0.001), G2Vec2(0, -1), G2Vec2(0.02, 0.02), 0.008);
  Shot.Faction := Faction;
  Shot.Damage := 100;
end;
//TEnemyAlien02 END

//TEnemyAlien03 BEGIN
constructor TEnemyAlien03.Create;
begin
  inherited Create;
  Img := App.ImgListEnemies.Find('Enemy2.png');
  TracePlayer := True;
  ShotRounds := 3;
  ShotRoundLag := 80;
end;

procedure TEnemyAlien03.OnShoot;
  var Shot: TShot;
  var s, c: Single;
begin
  G2SinCos(-Angle - HalfPi, s, c);
  Shot := App.Level.CreateShotParticle(TShotOmni, Pos, G2Vec2(c, s), G2Vec2(0.03, 0.01), 0.005);
  Shot.Faction := Faction;
  Shot.Damage := 30;
  TShotOmni(Shot).VelAngular := 0.3;
  TShotOmni(Shot).AddColor($ffe48100);
  TShotOmni(Shot).AddColor($ffe43b00);
  TShotOmni(Shot).ColorInterval := 100;
  TShotOmni(Shot).Brightness := $cc;
end;
//TEnemyAlien03 END

//TEnemyAlien04 BEGIN
constructor TEnemyAlien04.Create;
begin
  inherited Create;
  Img := App.ImgListEnemies.Find('Enemy3.png');
  TracePlayer := True;
  TracePlayerSpeed := 0.015;
  Laser := TShotLaser.Create;
  Laser.State := lsIdle;
  LaserDurationIdle := 3000;
  LaserDurationPrime := 500;
  LaserDurationFire := 500;
  LaserTimeState := App.CurTime - Random(3000);
  Laser.Faction := Faction;
  Laser.Size := Laser.Size * 0.5;
  Laser.AddColor($ffff0000);
  Laser.AddColor($ff00ff00);
  Laser.AddColor($ff0000ff);
  Laser.ColorInterval := 50;
  Laser.Damage := 2;
end;

destructor TEnemyAlien04.Destroy;
begin
  Laser.Die;
  inherited Destroy;
end;

procedure TEnemyAlien04.Update;
  var s, c: Single;
begin
  inherited Update;
  G2SinCos(-Angle - HalfPi, s, c);
  Laser.Pos := Pos + G2Vec2(c, s) * 0.01;
  Laser.Dir := G2Vec2(c, s);
  case Laser.State of
    lsIdle:
    begin
      if App.CurTime - LaserTimeState > LaserDurationIdle then
      begin
        Laser.State := lsPrime;
        LaserTimeState := App.CurTime;
      end;
    end;
    lsPrime:
    begin
      if App.CurTime - LaserTimeState > LaserDurationPrime then
      begin
        Laser.State := lsFire;
        LaserTimeState := App.CurTime;
      end;
    end;
    lsFire:
    begin
      if App.CurTime - LaserTimeState > LaserDurationFire then
      begin
        Laser.State := lsIdle;
        LaserTimeState := App.CurTime;
      end;
    end;
  end;
end;
//TEnemyAlien04 END

//TBossHitSpot BEGIN
constructor TBossHitSpot.Create;
begin
  inherited Create;
  HitTime := App.CurTime - 1000;
end;

procedure TBossHitSpot.Render;
begin

end;

procedure TBossHitSpot.Update;
begin

end;

procedure TBossHitSpot.Hit(const Amount: Integer);
begin
  HitTime := App.CurTime;
  if Assigned(OnHitProc) then
  OnHitProc(Amount);
end;

procedure TBossHitSpot.UpdateBounds;
begin
  xl := Boss.Pos.x + R.l;
  yl := Boss.Pos.y + R.t;
  xh := Boss.Pos.x + R.r;
  yh := Boss.Pos.y + R.b;
end;

function TBossHitSpot.GetHitGradient: Single;
begin
  Result := 1 - (App.CurTime - HitTime) / 100;
end;
//TBossHitSpot END

//TBoss BEGIN
constructor TBoss.Create;
begin
  inherited Create;
  Faction := 2;
  Alt := 7;
  Order := 0.9994;
  Vel.SetValue(0, 0);
end;

destructor TBoss.Destroy;
begin
  while _HitSpots.Count > 0 do
  HitSpotRemove(TBossHitSpot(_HitSpots[0]));
  while _Collisions.Count > 0 do
  Dispose(PG2Rect(_Collisions.Pop));
  inherited Destroy;
end;

function TBoss.HitSpotAdd(const R: TG2Rect; const Proc: TBossHitProc): TBossHitSpot;
begin
  Result := TBossHitSpot.Create;
  Result.Faction := Faction;
  Result.Boss := Self;
  Result.R := R;
  Result.OnHitProc := Proc;
  Result.UpdateBounds;
  _HitSpots.Add(Result);
end;

procedure TBoss.HitSpotRemove(const HitSpot: TBossHitSpot);
begin
  _HitSpots.Remove(HitSpot);
  HitSpot.Die;
end;

procedure TBoss.CollisionAdd(const R: TG2Rect);
  var cr: PG2Rect;
begin
  New(cr);
  cr^ := R;
  _Collisions.Add(cr);
end;

procedure TBoss.Update;
  var i: Integer;
begin
  Pos := Pos + Vel;
  for i := 0 to _HitSpots.Count - 1 do
  TBossHitSpot(_HitSpots[i]).UpdateBounds;
  CheckCollisions;
end;

procedure TBoss.CheckCollisions;
  var i: Integer;
  var r0, r1: TG2Rect;
  var pr: PG2Rect;
  var Resp: TG2Vec2;
  var px: Single;
begin
  if App.Player <> nil then
  for i := 0 to _Collisions.Count - 1 do
  begin
    pr := PG2Rect(_Collisions[i]);
    r0.l := Pos.x + pr^.l;
    r0.t := Pos.y + pr^.t;
    r0.r := Pos.x + pr^.r;
    r0.b := Pos.y + pr^.b;
    r1.l := App.Player.xl;
    r1.t := App.Player.yl;
    r1.r := App.Player.xh;
    r1.b := App.Player.yh;
    if G2RectVsRect(r0, r1, Resp) then
    begin
      App.Player.Pos := App.Player.Pos + Resp;
      App.Player.Vel := App.Player.Vel + Resp.Norm * 0.002;
      App.Player.UpdateBounds;
      App.Player.Hit(100);
      px := (App.Player.Pos.x - 0.5) * (1 + App.Player.Alt * 0.1);
      if (px < -0.5)
      or (px > 0.5) then
      begin
        App.Player.Kill;
        Exit;
      end;
    end;
  end;
end;
//TBoss END

//TBoss0AI BEGIN
procedure TBoss0AI.SetStage(const NewStage: TBoss0AIStage);
begin
  Stage := NewStage;
  StageTime := App.CurTime;
end;
//TBoss0AI END

//TBoss0 BEGIN
constructor TBoss0.Create;
  var r: TG2Rect;
begin
  inherited Create;
  Size.SetValue(0.4, 0.2);
  Pos := App.Level.VCamPos;
  Pos.y := Pos.y + 2 / 3 + Size.y * 0.5;
  Img := App.ImgListEnemies.Find('Boss0.png');
  Ext0.Img := App.ImgListEnemies.Find('Boss0_ext0.png');
  Ext0.Pos.SetValue(0, 0.047);
  Ext0.Size.SetValue(0.2, 0.1);
  Gun0.Img := App.ImgListEnemies.Find('Boss0_turret0.png');
  Gun0.Pos.SetValue(0, 0.03);
  Gun0.Size.SetValue(0.05, 0.1);
  Gun0.Pivot.SetValue(0.5, 0.3);
  Gun0.Angle := 0;
  Gun0.ShootInterval := 1000;
  Gun0.ShootSide := 1;
  Gun0.MFImg := App.ImgListEffects.Find('MuzzleFlash.png');
  Gun0.MFDuration := 100;
  Gun0.MFTime := App.CurTime - Gun0.MFDuration;
  Gun0.MFSide := 1;
  Launcher0.Img := App.ImgListEnemies.Find('Boss0_launcher0.png');
  Launcher0.Pos.SetValue(-0.09, 0.02);
  Launcher0.Size.SetValue(0.05, 0.1);
  Launcher0.ShootInterval := 2000;
  SetLength(Launcher0.ShootPos, 3);
  Launcher0.ShootPos[0] := Launcher0.Pos + G2Vec2(-0.008, -0.035);
  Launcher0.ShootPos[1] := Launcher0.Pos + G2Vec2(0, -0.035);
  Launcher0.ShootPos[2] := Launcher0.Pos + G2Vec2(0.009, -0.035);
  Launcher1.Img := App.ImgListEnemies.Find('Boss0_launcher1.png');
  Launcher1.Pos.SetValue(0.09, 0.02);
  Launcher1.Size.SetValue(0.05, 0.1);
  Launcher1.ShootInterval := 2000;
  SetLength(Launcher1.ShootPos, 3);
  Launcher1.ShootPos[0] := Launcher1.Pos + G2Vec2(-0.008, -0.035);
  Launcher1.ShootPos[1] := Launcher1.Pos + G2Vec2(0, -0.035);
  Launcher1.ShootPos[2] := Launcher1.Pos + G2Vec2(0.009, -0.035);
  Laser0Pos.SetValue(-0.073, -0.046);
  Laser0 := App.Level.CreateShotLaser(TShotLaser, Pos + Laser0Pos, G2Vec2(0, -1), 0.008);
  Laser0.AddColor($ffff0000);
  Laser0.Faction := Faction;
  Laser0.Damage := 15;
  Laser1Pos.SetValue(0.073, -0.046);
  Laser1 := App.Level.CreateShotLaser(TShotLaser, Pos + Laser1Pos, G2Vec2(0, -1), 0.008);
  Laser1.AddColor($ffff0000);
  Laser1.Faction := Faction;
  Laser1.Damage := 15;
  r.l := Gun0.Pos.x - 0.02;
  r.t := Gun0.Pos.y - 0.02;
  r.r := Gun0.Pos.x + 0.02;
  r.b := Gun0.Pos.y + 0.02;
  HitSpotGun0 := HitSpotAdd(r, @HitGun0);
  r.l := Launcher0.Pos.x - 0.02;
  r.t := Launcher0.Pos.y - 0.025;
  r.r := Launcher0.Pos.x + 0.02;
  r.b := Launcher0.Pos.y + 0.025;
  HitSpotLauncher0 := HitSpotAdd(r, @HitLauncher0);
  r.l := Launcher1.Pos.x - 0.02;
  r.t := Launcher1.Pos.y - 0.025;
  r.r := Launcher1.Pos.x + 0.02;
  r.b := Launcher1.Pos.y + 0.025;
  HitSpotLauncher1 := HitSpotAdd(r, @HitLauncher1);
  HitSpotBody := nil;
  HealthMaxGun0 := 800;
  HealthMaxLauncher0 := 1000;
  HealthMaxLauncher1 := 1000;
  HealthMaxBody := 1400;
  HealthGun0 := HealthMaxGun0;
  HealthLauncher0 := HealthMaxLauncher0;
  HealthLauncher1 := HealthMaxLauncher1;
  HealthBody := HealthMaxBody;
  r.l := -0.04;
  r.r := 0.04;
  r.t := -0.08;
  r.b := 0.08;
  CollisionAdd(r);
  r.l := -0.12;
  r.r := 0.12;
  r.t := -0.04;
  r.b := 0.06;
  CollisionAdd(r);
  r.l := -0.15;
  r.r := -0.1;
  r.t := -0.08;
  r.b := 0.05;
  CollisionAdd(r);
  r.l := 0.1;
  r.r := 0.15;
  r.t := -0.08;
  r.b := 0.05;
  CollisionAdd(r);
  LaserState := lsIdle;
  LaserDurationIdle := 3000;
  LaserDurationPrime := 1000;
  LaserDurationFire := 3000;
  LaserStateTime := App.CurTime;
  AI.SetStage(aiIntro);
end;

destructor TBoss0.Destroy;
begin
  Laser0.Die;
  Laser1.Die;
  inherited Destroy;
end;

procedure TBoss0.Update;
  var v2: TG2Vec2;
begin
  inherited Update;
  Laser0.Pos := Pos + Laser0Pos;
  Laser1.Pos := Pos + Laser1Pos;
  if App.Player <> nil then
  begin
    v2 := (App.Player.Pos - (Pos + Gun0.Pos)).Norm;
    if v2.y > -0.2 then v2.y := -0.2;
    Gun0.Angle := -(v2).AngleOX - HalfPi;
  end;
  DoAI;
end;

procedure TBoss0.Render;
  var v0, v1, ScrSize, ScrPos, ss, sp: TG2Vec2;
  var t, s, c: Single;
  var i: Integer;
begin
  ScrSize.x := App.Level.SizeVirtualToScreen(Size.x, 1 + Alt * 0.1);
  ScrSize.y := App.Level.SizeVirtualToScreen(Size.y, 1 + Alt * 0.1);
  ScrPos := App.Level.PosVirtualToScreen(Pos, 1 + Alt * 0.1);
  g2.PicRect(ScrPos, ScrSize.x, ScrSize.y, $ffffffff, 0.5, 0.5, 1, 1, 0, False, False, Img^.Texture, Img^.Texture.Width, Img^.Texture.Height, 0, bmNormal, tfLinear);
  if HitSpotBody <> nil then
  begin
    t := HitSpotBody.GetHitGradient;
    if t > 0 then
    g2.PicRect(ScrPos, ScrSize.x, ScrSize.y, $ffffff or (Round(t * $ff) shl 24), 0.5, 0.5, 1, 1, 0, False, False, Img^.Texture, Img^.Texture.Width, Img^.Texture.Height, 0, bmAdd, tfLinear);
  end;

  if HitSpotGun0 <> nil then
  begin
    ScrSize.x := App.Level.SizeVirtualToScreen(Gun0.Size.x, 1 + Alt * 0.1);
    ScrSize.y := App.Level.SizeVirtualToScreen(Gun0.Size.y, 1 + Alt * 0.1);
    ScrPos := App.Level.PosVirtualToScreen(Pos + Gun0.Pos, 1 + Alt * 0.1);
    g2.PicRect(
      ScrPos, ScrSize.x, ScrSize.y, $ffffffff, Gun0.Pivot.x, Gun0.Pivot.y, 1, 1, Gun0.Angle,
      False, False, Gun0.Img^.Texture, Gun0.Img^.Texture.Width, Gun0.Img^.Texture.Height, 0, bmNormal, tfLinear
    );
    t := HitSpotGun0.GetHitGradient;
    if t > 0 then
    g2.PicRect(
      ScrPos, ScrSize.x, ScrSize.y, $ffffff or (Round(t * $ff) shl 24), Gun0.Pivot.x, Gun0.Pivot.y, 1, 1, Gun0.Angle,
      False, False, Gun0.Img^.Texture, Gun0.Img^.Texture.Width, Gun0.Img^.Texture.Height, 0, bmAdd, tfLinear
    );
  end;

  ScrSize.x := App.Level.SizeVirtualToScreen(Ext0.Size.x, 1 + Alt * 0.1);
  ScrSize.y := App.Level.SizeVirtualToScreen(Ext0.Size.y, 1 + Alt * 0.1);
  ScrPos := App.Level.PosVirtualToScreen(Pos + Ext0.Pos, 1 + Alt * 0.1);
  g2.PicRect(
    ScrPos, ScrSize.x, ScrSize.y, $ffffffff, 0.5, 0.5, 1, 1, 0,
    False, False, Ext0.Img^.Texture, Ext0.Img^.Texture.Width, Ext0.Img^.Texture.Height, 0, bmNormal, tfLinear
  );

  if HitSpotLauncher0 <> nil then
  begin
    ScrSize.x := App.Level.SizeVirtualToScreen(Launcher0.Size.x, 1 + Alt * 0.1);
    ScrSize.y := App.Level.SizeVirtualToScreen(Launcher0.Size.y, 1 + Alt * 0.1);
    ScrPos := App.Level.PosVirtualToScreen(Pos + Launcher0.Pos, 1 + Alt * 0.1);
    g2.PicRect(
      ScrPos, ScrSize.x, ScrSize.y, $ffffffff, 0.5, 0.5, 1, 1, 0,
      False, False, Launcher0.Img^.Texture, Launcher0.Img^.Texture.Width, Launcher0.Img^.Texture.Height, 0, bmNormal, tfLinear
    );
    t := HitSpotLauncher0.GetHitGradient;
    if t > 0 then
    g2.PicRect(
      ScrPos, ScrSize.x, ScrSize.y, $ffffff or (Round(t * $ff) shl 24), 0.5, 0.5, 1, 1, 0,
      False, False, Launcher0.Img^.Texture, Launcher0.Img^.Texture.Width, Launcher0.Img^.Texture.Height, 0, bmAdd, tfLinear
    );
  end;

  if HitSpotLauncher1 <> nil then
  begin
    ScrSize.x := App.Level.SizeVirtualToScreen(Launcher1.Size.x, 1 + Alt * 0.1);
    ScrSize.y := App.Level.SizeVirtualToScreen(Launcher1.Size.y, 1 + Alt * 0.1);
    ScrPos := App.Level.PosVirtualToScreen(Pos + Launcher1.Pos, 1 + Alt * 0.1);
    g2.PicRect(
      ScrPos, ScrSize.x, ScrSize.y, $ffffffff, 0.5, 0.5, 1, 1, 0,
      False, False, Launcher1.Img^.Texture, Launcher1.Img^.Texture.Width, Launcher1.Img^.Texture.Height, 0, bmNormal, tfLinear
    );
    t := HitSpotLauncher1.GetHitGradient;
    if t > 0 then
    g2.PicRect(
      ScrPos, ScrSize.x, ScrSize.y, $ffffff or (Round(t * $ff) shl 24), 0.5, 0.5, 1, 1, 0,
      False, False, Launcher1.Img^.Texture, Launcher1.Img^.Texture.Width, Launcher1.Img^.Texture.Height, 0, bmAdd, tfLinear
    );
  end;

  if (HitSpotGun0 <> nil) and (App.CurTime - Gun0.MFTime < Gun0.MFDuration) then
  begin
    t := (App.CurTime - Gun0.MFTime) / Gun0.MFDuration;
    G2SinCos(-Gun0.Angle - HalfPi, s, c);
    ss := G2Vec2(c, s); sp := ss.Perp * Gun0.MFSide;
    ScrSize.x := App.Level.SizeVirtualToScreen(0.1, 1 + Alt * 0.1);
    ScrSize.y := App.Level.SizeVirtualToScreen(0.05, 1 + Alt * 0.1);
    ScrPos := App.Level.PosVirtualToScreen(Pos + Gun0.Pos + ss * 0.065 + sp * 0.004, 1 + Alt * 0.1);
    g2.PicRect(
      ScrPos, ScrSize.x, ScrSize.y, $ffffffff, 0.1, 0.5, 1 - t, 1 - t, Gun0.Angle + HalfPi,
      False, False, Gun0.MFImg^.Texture, Gun0.MFImg^.Texture.Width, Gun0.MFImg^.Texture.Height, 0, bmNormal, tfLinear
    );
  end;

  //for i := 0 to _HitSpots.Count - 1 do
  //begin
  //  v0 := App.Level.PosVirtualToScreen(G2Vec2(TBossHitSpot(_HitSpots[i]).xl, TBossHitSpot(_HitSpots[i]).yl), 1 + Alt * 0.1);
  //  v1 := App.Level.PosVirtualToScreen(G2Vec2(TBossHitSpot(_HitSpots[i]).xh, TBossHitSpot(_HitSpots[i]).yh), 1 + Alt * 0.1);
  //  g2.PrimRectHollow(v0.x, v0.y, v1.x - v0.x, v1.y - v0.y, $ffff0000);
  //end;
  //
  //for i := 0 to _Collisions.Count - 1 do
  //begin
  //  v0 := App.Level.PosVirtualToScreen(Pos + PG2Rect(_Collisions[i])^.TopLeft, 1 + Alt * 0.1);
  //  v1 := App.Level.PosVirtualToScreen(Pos + PG2Rect(_Collisions[i])^.BottomRight, 1 + Alt * 0.1);
  //  g2.PrimRectHollow(v0.x, v0.y, v1.x - v0.x, v1.y - v0.y, $ff0000ff);
  //end;
end;

procedure TBoss0.DoAI;
  var Shot: TShot;
  var s, c: Single;
  var ss, sp: TG2Vec2;
begin
  case AI.Stage of
    aiIntro:
    begin
      if Pos.y > App.Level.VCamPos.y + (App.Level.VScreenSize.y - Size.y) * 0.5 then
      Vel.y := -0.006
      else
      begin
        AI.SetStage(aiFight);
        Gun0.ShootTime := App.CurTime;
        Launcher0.ShootTime := App.CurTime;
        Launcher1.ShootTime := App.CurTime;
        LaserState := lsIdle;
        LaserStateTime := App.CurTime;
      end;
    end;
    aiFight:
    begin
      if Pos.y > App.Level.VCamPos.y + App.Level.VScreenSize.y * 0.5 - Size.y + 0.01 then
      Vel.y := Vel.y - 0.00015
      else if Pos.y < App.Level.VCamPos.y + App.Level.VScreenSize.y * 0.5 - Size.y - 0.01 then
      Vel.y := Vel.y + 0.00015;
      if Abs(Vel.y) > 0.002 then Vel.y := Vel.y * 0.95;
      if Pos.x < Size.x * (1 + Alt * 0.1) * 0.5 then Vel.x := Vel.x + 0.00015
      else if Pos.x > 1 - Size.x * (1 + Alt * 0.1) * 0.5 then Vel.x := Vel.x - 0.00015
      else if Abs(Vel.x) < 0.0015 then Vel.x := Vel.x + 0.00015;
      if Abs(Vel.x) > 0.002 then Vel.x := Vel.x * 0.95;
      if (HitSpotGun0 <> nil) and (App.CurTime - Gun0.ShootTime > Gun0.ShootInterval) then
      begin
        G2SinCos(-Gun0.Angle - HalfPi, s, c);
        Gun0.ShootTime := App.CurTime;
        ss := G2Vec2(c, s); sp := ss.Perp * Gun0.ShootSide;
        Shot := App.Level.CreateShotParticle(TShotBullet, Pos + Gun0.Pos + ss * 0.07 + sp * 0.005, G2Vec2(c, s), G2Vec2(0.02, 0.06), 0.015);
        Shot.Faction := Faction;
        Shot.Damage := 100;
        TShotBullet(Shot).AddColor($ffff0000);
        TShotBullet(Shot).AddColor($ffff8800);
        TShotBullet(Shot).ColorInterval := 100;
        TShotBullet(Shot).Brightness := $cc;
        Gun0.MFTime := App.CurTime;
        Gun0.MFSide := Gun0.ShootSide;
        Gun0.ShootSide := -Gun0.ShootSide;
      end;
      if (HitSpotLauncher0 <> nil) and (App.CurTime - Launcher0.ShootTime > Launcher0.ShootInterval) then
      begin
        Launcher0.ShootTime := App.CurTime;
        Shot := App.Level.CreateShotMissile(TShotMissileDizzy, Pos + Launcher0.ShootPos[Launcher0.ShootID], G2Vec2(0, -1), G2Vec2(0.02, 0.02), 0.008);
        Shot.Faction := Faction;
        Shot.Damage := 200;
        Launcher0.ShootID := (Launcher0.ShootID + 1) mod Length(Launcher0.ShootPos);
        if Launcher0.ShootID = 0 then
        Launcher0.ShootInterval := 2000
        else
        Launcher0.ShootInterval := 100;
      end;
      if (HitSpotLauncher1 <> nil) and (App.CurTime - Launcher1.ShootTime > Launcher1.ShootInterval) then
      begin
        Launcher1.ShootTime := App.CurTime;
        Shot := App.Level.CreateShotMissile(TShotMissileDizzy, Pos + Launcher1.ShootPos[Launcher1.ShootID], G2Vec2(0, -1), G2Vec2(0.02, 0.02), 0.008);
        Shot.Faction := Faction;
        Shot.Damage := 200;
        Launcher1.ShootID := (Launcher1.ShootID + 1) mod Length(Launcher1.ShootPos);
        if Launcher1.ShootID = 0 then
        Launcher1.ShootInterval := 2000
        else
        Launcher1.ShootInterval := 100;
      end;
      case LaserState of
        lsIdle:
        begin
          if App.CurTime - LaserStateTime > LaserDurationIdle then
          begin
            LaserState := lsPrime;
            LaserStateTime := App.CurTime;
          end;
        end;
        lsPrime:
        begin
          if App.CurTime - LaserStateTime > LaserDurationPrime then
          begin
            LaserState := lsFire;
            LaserStateTime := App.CurTime;
          end;
        end;
        lsFire:
        begin
          if App.CurTime - LaserStateTime > LaserDurationFire then
          begin
            LaserState := lsIdle;
            LaserStateTime := App.CurTime;
          end;
        end;
      end;
      Laser0.State := LaserState;
      Laser1.State := LaserState;
    end;
  end;
end;

procedure TBoss0.CheckDamage;
  var r: TG2Rect;
begin
  if (HitSpotGun0 = nil)
  and (HitSpotLauncher0 = nil)
  and (HitSpotLauncher1 = nil) then
  begin
    r.l := -0.03;
    r.t := -0.08;
    r.r := 0.03;
    r.b := -0.03;
    HitSpotBody := HitSpotAdd(r, @HitBody);
    LaserDurationIdle := 1000;
  end;
end;

procedure TBoss0.HitGun0(const Amount: Integer);
  var i: Integer;
begin
  if HitSpotGun0 <> nil then
  begin
    HealthGun0 := HealthGun0 - Amount;
    if HealthGun0 <= 0 then
    begin
      for i := 0 to 2 do
      App.Level.CreateExplosion(
        G2Vec2(
          HitSpotGun0.xl + Random(Round((HitSpotGun0.xh - HitSpotGun0.xl) * 1001)) * 0.001,
          HitSpotGun0.yl + Random(Round((HitSpotGun0.yh - HitSpotGun0.yl) * 1001)) * 0.001
        )
      );
      HitSpotRemove(HitSpotGun0);
      HitSpotGun0 := nil;
      CheckDamage;
    end
    else if HealthGun0 / HealthMaxGun0 < 0.3 then
    begin
      if Random(HealthGun0 div 100) = 0 then
      App.Level.CreateExplosion(
        G2Vec2(
          HitSpotGun0.xl + Random(Round((HitSpotGun0.xh - HitSpotGun0.xl) * 1001)) * 0.001,
          HitSpotGun0.yl + Random(Round((HitSpotGun0.yh - HitSpotGun0.yl) * 1001)) * 0.001
        )
      );
    end;
  end;
end;

procedure TBoss0.HitLauncher0(const Amount: Integer);
  var i: Integer;
begin
  if HitSpotLauncher0 <> nil then
  begin
    HealthLauncher0 := HealthLauncher0 - Amount;
    if HealthLauncher0 <= 0 then
    begin
      for i := 0 to 2 do
      App.Level.CreateExplosion(
        G2Vec2(
          HitSpotLauncher0.xl + Random(Round((HitSpotLauncher0.xh - HitSpotLauncher0.xl) * 1001)) * 0.001,
          HitSpotLauncher0.yl + Random(Round((HitSpotLauncher0.yh - HitSpotLauncher0.yl) * 1001)) * 0.001
        )
      );
      HitSpotRemove(HitSpotLauncher0);
      HitSpotLauncher0 := nil;
      CheckDamage;
    end
    else if HealthLauncher0 / HealthMaxLauncher0 < 0.3 then
    begin
      if Random(HealthLauncher0 div 100) = 0 then
      App.Level.CreateExplosion(
        G2Vec2(
          HitSpotLauncher0.xl + Random(Round((HitSpotLauncher0.xh - HitSpotLauncher0.xl) * 1001)) * 0.001,
          HitSpotLauncher0.yl + Random(Round((HitSpotLauncher0.yh - HitSpotLauncher0.yl) * 1001)) * 0.001
        )
      );
    end;
  end;
end;

procedure TBoss0.HitLauncher1(const Amount: Integer);
  var i: Integer;
begin
  if HitSpotLauncher1 <> nil then
  begin
    HealthLauncher1 := HealthLauncher1 - Amount;
    if HealthLauncher1 <= 0 then
    begin
      for i := 0 to 2 do
      App.Level.CreateExplosion(
        G2Vec2(
          HitSpotLauncher1.xl + Random(Round((HitSpotLauncher1.xh - HitSpotLauncher1.xl) * 1001)) * 0.001,
          HitSpotLauncher1.yl + Random(Round((HitSpotLauncher1.yh - HitSpotLauncher1.yl) * 1001)) * 0.001
        )
      );
      HitSpotRemove(HitSpotLauncher1);
      HitSpotLauncher1 := nil;
      CheckDamage;
    end
    else if HealthLauncher1 / HealthMaxLauncher1 < 0.3 then
    begin
      if Random(HealthLauncher1 div 100) = 0 then
      App.Level.CreateExplosion(
        G2Vec2(
          HitSpotLauncher1.xl + Random(Round((HitSpotLauncher1.xh - HitSpotLauncher1.xl) * 1001)) * 0.001,
          HitSpotLauncher1.yl + Random(Round((HitSpotLauncher1.yh - HitSpotLauncher1.yl) * 1001)) * 0.001
        )
      );
    end;
  end;
end;

procedure TBoss0.HitBody(const Amount: Integer);
  var i: Integer;
begin
  if HitSpotBody <> nil then
  begin
    HealthBody := HealthBody - Amount;
    if HealthBody <= 0 then
    begin
      for i := 0 to 19 do
      App.Level.CreateExplosion(
        G2Vec2(
          Pos.x + Random(Round((Size.x) * 1001)) * 0.001 - Size.x * 0.5,
          Pos.y + Random(Round((Size.y) * 1001)) * 0.001 - Size.y * 0.5
        )
      );
      HitSpotRemove(HitSpotBody);
      HitSpotBody := nil;
      Die;
      App.AddScore(2000);
      App.Level.State := lsFinished;
      App.Level.LevelEndTime := App.CurTime;
    end
    else if HealthBody / HealthMaxBody < 0.3 then
    begin
      if Random(HealthBody div 100) = 0 then
      App.Level.CreateExplosion(
        G2Vec2(
          HitSpotBody.xl + Random(Round((HitSpotBody.xh - HitSpotBody.xl) * 1001)) * 0.001,
          HitSpotBody.yl + Random(Round((HitSpotBody.yh - HitSpotBody.yl) * 1001)) * 0.001
        )
      );
    end;
  end;
end;
//TBoss0 END

//TPlayerPowerup BEGIN
constructor TPlayerPowerup.Create;
begin
  inherited Create;
  ImgBubble := App.ImgListEffects.Find('Bubble.png');
  BubbleFrameWidth := ImgBubble^.Texture.Width div 4;
  BubbleFrameHeight := ImgBubble^.Texture.Height div 4;
  BubbleFrameCount := 16;
  Size := 0.04;
  Alt := 7;
  Order := 0.9994;
end;

destructor TPlayerPowerup.Destroy;
begin
  inherited Destroy;
end;

procedure TPlayerPowerup.Render;
  var ScrPos: TG2Vec2;
  var ScrSize: Single;
begin
  ScrPos := App.Level.PosVirtualToScreen(Pos, 1 + Alt * 0.1);
  ScrSize := App.Level.SizeVirtualToScreen(Size, 1 + Alt * 0.1);
  g2.PicRect(
    ScrPos, ScrSize, ScrSize, $ffffffff, 0.5, 0.5, 0.7, 0.7, 0, False, False,
    Img^.Texture, Img^.Texture.Width, Img^.Texture.Height,
    0, bmNormal, tfLinear
  );
  g2.PicRect(
    ScrPos, ScrSize, ScrSize, $ffffffff, 0.5, 0.5, 1, 1, 0, False, False,
    ImgBubble^.Texture, BubbleFrameWidth, BubbleFrameHeight,
    Round(G2TimeInterval() * BubbleFrameCount) mod BubbleFrameCount, bmNormal, tfLinear
  );
end;

procedure TPlayerPowerup.Update;
begin
  if (Pos.x < -0.1)
  or (Pos.x > 1.1)
  or (Pos.y > App.Level.VCamPos.y + 0.475)
  or (Pos.y < App.Level.VCamPos.y - 0.475) then
  Die;
  if (App.Player <> nil)
  and (Pos.x > App.Player.Pos.x - App.Player.Size)
  and (Pos.x < App.Player.Pos.x + App.Player.Size)
  and (Pos.y > App.Player.Pos.y - App.Player.Size)
  and (Pos.y < App.Player.Pos.y + App.Player.Size) then
  begin
    OnPickup;
    Die;
  end;
end;
//TPlayerPowerup END

//TPlayerPowerupHeal BEGIN
constructor TPlayerPowerupHeal.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('IconHeal.png');
end;

procedure TPlayerPowerupHeal.OnPickup;
begin
  App.Player.Health := Min(App.Player.Health + 400, App.Player.HealthMax);
end;
//TPlayerPowerupHeal END

//TPlayerPowerupAddHealth BEGIN
constructor TPlayerPowerupAddHealth.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('IconAddHealth.png');
end;

procedure TPlayerPowerupAddHealth.OnPickup;
begin
  App.Player.HealthMax := Min(App.Player.HealthMax + 100, 1600);
  App.Player.Health := Min(App.Player.Health + 100, App.Player.HealthMax);
end;
//TPlayerPowerupAddHealth END

//TPlayerPowerupShield BEGIN
constructor TPlayerPowerupShield.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('IconShield.png');
end;

procedure TPlayerPowerupShield.OnPickup;
begin
  App.Player.Shield := Min(App.Player.Shield + 200, App.Player.ShieldMax);
end;
//TPlayerPowerupShield END

//TPlayerPowerupAddShield BEGIN
constructor TPlayerPowerupAddShield.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('IconAddShield.png');
end;

procedure TPlayerPowerupAddShield.OnPickup;
begin
  App.Player.ShieldMax := Min(App.Player.ShieldMax + 100, 1000);
  App.Player.Shield := Min(App.Player.Shield + 100, App.Player.ShieldMax);
end;
//TPlayerPowerupAddShield END

//TPlayerPowerupMissile BEGIN
constructor TPlayerPowerupMissile.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('IconMissile.png');
end;

procedure TPlayerPowerupMissile.OnPickup;
begin
  App.Player.AddGun(TPlayerGunMissile);
end;
//TPlayerPowerupMissile END

//TPlayerPowerupLaser BEGIN
constructor TPlayerPowerupLaser.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('IconLaser.png');
end;

procedure TPlayerPowerupLaser.OnPickup;
begin
  App.Player.AddGun(TPlayerGunLaser);
end;
//TPlayerPowerupLaser END

//TPlayerPowerupBlaster BEGIN
constructor TPlayerPowerupBlaster.Create;
begin
  inherited Create;
  Img := App.ImgListEffects.Find('IconBlaster.png');
end;

procedure TPlayerPowerupBlaster.OnPickup;
begin
  App.Player.AddGun(TPlayerGunBlaster);
end;
//TPlayerPowerupBlaster END

//TPlayerGun BEGIN
procedure TPlayerGun.SetLevel(const Value: Integer);
begin
  if Value <> _Level then
  begin
    _Level := Value;
    OnChangeLevel;
  end;
end;

constructor TPlayerGun.Create;
begin
  inherited Create;
  _Level := -1;
  _Faction := 0;
end;

destructor TPlayerGun.Destroy;
begin
  inherited Destroy;
end;

procedure TPlayerGun.Mount(const PosRef: PG2Vec2; const GunFront, GunLeft, GunRight: TG2Vec2; const Faction: Integer);
begin
  _PosRef := PosRef;
  _GunFront := GunFront;
  _GunSide[0] := GunLeft;
  _GunSide[1] := GunRight;
  _Faction := Faction;
  OnMount;
  Level := 0;
end;

procedure TPlayerGun.OnMount;
begin

end;

procedure TPlayerGun.OnChangeLevel;
begin

end;
//TPlayerGun END

//TPlayerGunBlaster BEGIN
procedure TPlayerGunBlaster.OnMount;
begin
  Level := 0;
  ShotTime := App.CurTime;
  ShotSide := 1;
end;

procedure TPlayerGunBlaster.OnChangeLevel;
begin
  case Level of
    0:
    begin
      ShotLag := 100;
    end;
    1:
    begin
      ShotLag := 100;
    end;
    2:
    begin
      ShotLag := 100;
    end;
  end;
end;

procedure TPlayerGunBlaster.Fire(const Enable: Boolean);
  var Shot: TShot;
begin
  if not Enable then Exit;
  if (App.CurTime - ShotTime > ShotLag) then
  begin
    ShotTime := App.CurTime;
    case Level of
      0:
      begin
        Shot := App.Level.CreateShotParticle(TShotBullet, _PosRef^ + _GunSide[ShotSide], G2Vec2(0, 1), G2Vec2(0.015, 0.03), 0.015);
        Shot.Faction := _Faction;
        Shot.Damage := 30;
        TShotBullet(Shot).AddColor($ff005bc1);
        if ShotSide = 1 then ShotSide := 0 else ShotSide := 1;
        App.SndBlast^.Play;
      end;
      1:
      begin
        Shot := App.Level.CreateShotParticle(TShotBullet, _PosRef^ + _GunFront, G2Vec2(-0.1, 1).Norm, G2Vec2(0.015, 0.03), 0.015);
        Shot.Faction := _Faction;
        Shot.Damage := 15;
        TShotBullet(Shot).AddColor($ff005bc1);
        Shot := App.Level.CreateShotParticle(TShotBullet, _PosRef^ + _GunFront, G2Vec2(0.1, 1).Norm, G2Vec2(0.015, 0.03), 0.015);
        Shot.Faction := _Faction;
        Shot.Damage := 15;
        TShotBullet(Shot).AddColor($ff005bc1);
        Shot := App.Level.CreateShotParticle(TShotBullet, _PosRef^ + _GunFront, G2Vec2(0, 1), G2Vec2(0.015, 0.03), 0.015);
        Shot.Faction := _Faction;
        Shot.Damage := 15;
        TShotBullet(Shot).AddColor($ff005bc1);
        App.SndBlast^.Play;
      end;
      2:
      begin
        Shot := App.Level.CreateShotParticle(TShotBullet, _PosRef^ + _GunSide[0], G2Vec2(0, 1), G2Vec2(0.015, 0.03), 0.015);
        Shot.Faction := _Faction;
        Shot.Damage := 25;
        TShotBullet(Shot).AddColor($ff005bc1);
        Shot := App.Level.CreateShotParticle(TShotBullet, _PosRef^ + _GunSide[1], G2Vec2(0, 1), G2Vec2(0.015, 0.03), 0.015);
        Shot.Faction := _Faction;
        Shot.Damage := 25;
        TShotBullet(Shot).AddColor($ff005bc1);
        Shot := App.Level.CreateShotParticle(TShotBullet, _PosRef^ + _GunSide[0], G2Vec2(-0.5, 1).Norm, G2Vec2(0.015, 0.03), 0.015);
        Shot.Faction := _Faction;
        Shot.Damage := 20;
        TShotBullet(Shot).AddColor($ff005bc1);
        Shot := App.Level.CreateShotParticle(TShotBullet, _PosRef^ + _GunSide[1], G2Vec2(0.5, 1).Norm, G2Vec2(0.015, 0.03), 0.015);
        Shot.Faction := _Faction;
        Shot.Damage := 20;
        TShotBullet(Shot).AddColor($ff005bc1);
        Shot := App.Level.CreateShotParticle(TShotBullet, _PosRef^ + _GunSide[0], G2Vec2(-1, 1).Norm, G2Vec2(0.015, 0.03), 0.015);
        Shot.Faction := _Faction;
        Shot.Damage := 20;
        TShotBullet(Shot).AddColor($ff005bc1);
        Shot := App.Level.CreateShotParticle(TShotBullet, _PosRef^ + _GunSide[1], G2Vec2(1, 1).Norm, G2Vec2(0.015, 0.03), 0.015);
        Shot.Faction := _Faction;
        Shot.Damage := 20;
        TShotBullet(Shot).AddColor($ff005bc1);
        App.SndBlast^.Play;
      end;
    end;
  end;
end;
//TPlayerGunBlaster END

//TPlayerGunLaser BEGIN
constructor TPlayerGunLaser.Create;
begin
  inherited Create;
  Laser := TShotLaser.Create;
  Laser.AddColor($ff0000ff);
  Laser.AddColor($ff0088ff);
  Laser.ColorInterval := 50;
  Laser.Dir.SetValue(0, 1);
  Laser.Size := Laser.Size * 0.5;
  Laser.Damage := 4;
  LaserLeft := TShotLaser.Create;
  LaserLeft.AddColor($ff0000ff);
  LaserLeft.AddColor($ff0088ff);
  LaserLeft.ColorInterval := 50;
  LaserLeft.Dir.SetValue(0, 1);
  LaserLeft.Size := LaserLeft.Size * 0.5;
  LaserLeft.Damage := 4;
  LaserRight := TShotLaser.Create;
  LaserRight.AddColor($ff0000ff);
  LaserRight.AddColor($ff0088ff);
  LaserRight.ColorInterval := 50;
  LaserRight.Dir.SetValue(0, 1);
  LaserRight.Size := LaserRight.Size * 0.5;
  LaserRight.Damage := 4;
  LaserLeftScatter := TShotLaser.Create;
  LaserLeftScatter.AddColor($ffffc600);
  LaserLeftScatter.AddColor($ffff5a00);
  LaserLeftScatter.ColorInterval := 50;
  LaserLeftScatter.Dir.SetValue(0, 1);
  LaserLeftScatter.Size := LaserLeftScatter.Size * 0.5;
  LaserLeftScatter.Damage := 4;
  LaserRightScatter := TShotLaser.Create;
  LaserRightScatter.AddColor($ffffc600);
  LaserRightScatter.AddColor($ffff5a00);
  LaserRightScatter.ColorInterval := 50;
  LaserRightScatter.Dir.SetValue(0, 1);
  LaserRightScatter.Size := LaserRightScatter.Size * 0.5;
  LaserRightScatter.Damage := 4;
end;

destructor TPlayerGunLaser.Destroy;
begin
  Laser.Die;
  LaserLeft.Die;
  LaserRight.Die;
  LaserLeftScatter.Die;
  LaserRightScatter.Die;
  inherited Destroy;
end;

procedure TPlayerGunLaser.OnMount;
begin
  Level := 0;
  Laser.Faction := _Faction;
  LaserLeft.Faction := _Faction;
  LaserRight.Faction := _Faction;
  LaserLeftScatter.Faction := _Faction;
  LaserRightScatter.Faction := _Faction;
end;

procedure TPlayerGunLaser.OnChangeLevel;
begin
  case Level of
    0:
    begin

    end;
    1:
    begin

    end;
    2:
    begin

    end;
  end;
end;

procedure TPlayerGunLaser.Fire(const Enable: Boolean);
begin
  case Level of
    0:
    begin
      Laser.Pos := _PosRef^ + _GunFront;
      if Enable then
      Laser.State := lsFire
      else
      Laser.State := lsIdle;
      LaserLeft.State := lsIdle;
      LaserRight.State := lsIdle;
      LaserLeftScatter.State := lsIdle;
      LaserRightScatter.State := lsIdle;
    end;
    1:
    begin
      LaserLeft.Pos := _PosRef^ + _GunSide[0];
      LaserRight.Pos := _PosRef^ + _GunSide[1];
      if Enable then
      begin
        LaserLeft.State := lsFire;
        LaserRight.State := lsFire;
      end
      else
      begin
        LaserLeft.State := lsIdle;
        LaserRight.State := lsIdle;
      end;
      Laser.State := lsIdle;
      LaserLeftScatter.State := lsIdle;
      LaserRightScatter.State := lsIdle;
    end;
    2:
    begin
      Laser.Pos := _PosRef^ + _GunFront;
      LaserLeft.Pos := _PosRef^ + _GunSide[0];
      LaserRight.Pos := _PosRef^ + _GunSide[1];
      LaserLeftScatter.Pos := _PosRef^ + _GunSide[0] + G2Vec2(0, -0.01);
      LaserRightScatter.Pos := _PosRef^ + _GunSide[1] + G2Vec2(0, -0.01);
      if Enable then
      begin
        Laser.State := lsFire;
        LaserLeft.State := lsFire;
        LaserRight.State := lsFire;
        LaserLeftScatter.State := lsFire;
        LaserRightScatter.State := lsFire;
        LaserLeftScatter.Dir := G2LerpVec2(G2Vec2(-0.5, 1).Norm, G2Vec2(-1, 0.5).Norm, Sin(G2PiTime(500)) * 0.5 + 0.5).Norm;
        LaserRightScatter.Dir := G2LerpVec2(G2Vec2(0.5, 1).Norm, G2Vec2(1, 0.5).Norm, Sin(G2PiTime(500)) * 0.5 + 0.5).Norm;
      end
      else
      begin
        Laser.State := lsIdle;
        LaserLeft.State := lsIdle;
        LaserRight.State := lsIdle;
        LaserLeftScatter.State := lsIdle;
        LaserRightScatter.State := lsIdle;
      end;
    end;
  end;
end;
//TPlayerGunLaser END

//TPlayerGunMissile BEGIN
procedure TPlayerGunMissile.OnMount;
begin
  Level := 0;
  ShotTime := App.CurTime;
  ShotSide := 1;
end;

procedure TPlayerGunMissile.OnChangeLevel;
begin
  case Level of
    0:
    begin
      ShotLag := 500;
    end;
    1:
    begin
      ShotLag := 250;
    end;
    2:
    begin
      ShotLag := 500;
    end;
  end;
end;

procedure TPlayerGunMissile.Fire(const Enable: Boolean);
  var Shot: TShot;
begin
  if not Enable then Exit;
  if (App.CurTime - ShotTime > ShotLag) then
  begin
    ShotTime := App.CurTime;
    case Level of
      0:
      begin
        Shot := App.Level.CreateShotMissile(TShotMissile, _PosRef^ + _GunFront, G2Vec2(0, 1), G2Vec2(0.02, 0.02), 0.008);
        Shot.Faction := _Faction;
        Shot.Damage := 100;
        App.SndMissile^.Play;
      end;
      1:
      begin
        Shot := App.Level.CreateShotMissile(TShotMissile, _PosRef^ + _GunSide[ShotSide], G2Vec2(0, 1), G2Vec2(0.02, 0.02), 0.008);
        Shot.Faction := _Faction;
        Shot.Damage := 100;
        if ShotSide = 1 then ShotSide := 0 else ShotSide := 1;
        App.SndMissile^.Play;
      end;
      2:
      begin
        Shot := App.Level.CreateShotMissile(TShotMissileDizzy, _PosRef^ + _GunSide[0], G2Vec2(0, 1), G2Vec2(0.02, 0.02), 0.01);
        Shot.Faction := _Faction;
        Shot.Damage := 100;
        Shot := App.Level.CreateShotMissile(TShotMissileDizzy, _PosRef^ + _GunSide[0], G2Vec2(0, 1), G2Vec2(0.02, 0.02), 0.01);
        Shot.Faction := _Faction;
        Shot.Damage := 100;
        Shot := App.Level.CreateShotMissile(TShotMissileDizzy, _PosRef^ + _GunSide[1], G2Vec2(0, 1), G2Vec2(0.02, 0.02), 0.01);
        Shot.Faction := _Faction;
        Shot.Damage := 100;
        Shot := App.Level.CreateShotMissile(TShotMissileDizzy, _PosRef^ + _GunSide[1], G2Vec2(0, 1), G2Vec2(0.02, 0.02), 0.01);
        Shot.Faction := _Faction;
        Shot.Damage := 100;
        App.SndMissile^.Play;
      end;
    end;
  end;
end;
//TPlayerGunMissile END

//TPlayer BEGIN
constructor TPlayer.Create;
begin
  inherited Create;
  ImgEngine := App.ImgListShots.Find('Flare0.png');
  ImgShield := App.ImgListEffects.Find('Shield.png');
  Alt := 7;
  Order := 0.9997;
  Size := 0.065;
  Pos.SetValue(0.5, Size * 0.5);
  Speed := 0.002;
  SpeedMax := 0.01;
  InputMethod := TInputMethodKeyboard.Create;
  Faction := 1;
  TimeHit := App.CurTime - 1000;
  TimeShieldHit := App.CurTime - 1000;
  HealthMax := 800;
  Health := HealthMax;
  ShieldMax := 400;
  Shield := ShieldMax;
  Guns.Clear;
  AddGun(TPlayerGunBlaster);
end;

destructor TPlayer.Destroy;
begin
  InputMethod.Free;
  while Guns.Count > 0 do
  TPlayerGun(Guns.Pop).Free;
  Guns.Clear;
  inherited Destroy;
end;

procedure TPlayer.Render;
  var ScrPos, EnginePos: TG2Vec2;
  var ScrSize, EngineSize, HitFlash: Single;
  var Frame: Integer;
  var Flip: Boolean;
//  var v0, v1: TG2Vec2;
begin
  ScrSize := App.Level.SizeVirtualToScreen(Size, 1 + Alt * 0.1);
  ScrPos := App.Level.PosVirtualToScreen(Pos, 1 + Alt * 0.1);
  Flip := Vel.x < 0;
  Frame := Round(Abs(Vel.x / SpeedMax) * 3);
  EngineSize := ScrSize * (1 + Sin(G2PiTime(10)) * 0.1);
  EnginePos := App.Level.PosVirtualToScreen(Pos + G2Vec2(0, -0.02), 1 + Alt * 0.1) + G2Vec2(-EngineSize * 0.5, -EngineSize * 0.5);
  g2.PicRect(EnginePos, EngineSize, EngineSize, $ff0044ff, ImgEngine^.Texture, bmNormal, tfLinear);
  g2.PicRect(EnginePos, EngineSize, EngineSize, $ffffffff, ImgEngine^.Texture, bmAdd, tfLinear);
  g2.PicRect(ScrPos, ScrSize, ScrSize, $ffffffff, 0.5, 0.5, 1, 1, 0, Flip, False, Img, 128, 128, Frame, bmNormal, tfLinear);
  HitFlash := 1 - (App.CurTime - TimeHit) / 100;
  if HitFlash > 0 then
  g2.PicRect(ScrPos, ScrSize, ScrSize, $ffffff or (Round($ff * HitFlash) shl 24), 0.5, 0.5, 1, 1, 0, Flip, False, Img, 128, 128, Frame, bmAdd, tfLinear);
  HitFlash := 1 - (App.CurTime - TimeShieldHit) / 100;
  if HitFlash > 0 then
  g2.PicRect(
    ScrPos, ScrSize, ScrSize, $ffffff or (Round($ff * HitFlash) shl 24),
    0.5, 0.5, 1.1, 1.1, 0, False, False,
    ImgShield^.Texture, ImgShield^.Texture.Width, ImgShield^.Texture.Height,
    Frame, bmNormal, tfLinear
  );
  //v0 := App.Level.PosVirtualToScreen(G2Vec2(xl, yh), 1 + 7 * 0.1);
  //v1 := App.Level.PosVirtualToScreen(G2Vec2(xh, yl), 1 + 7 * 0.1);
  //g2.PrimRectHollow(v0.x, v0.y, v1.x - v0.x, v1.y - v0.y, $ffff0000);
end;

procedure TPlayer.Update;
  var hs: Single;
  var br: TG2Rect;
  var i: Integer;
begin
  if not Dead then
  begin
    if Health <= 0 then
    begin
      Kill;
    end
    else
    begin
      Pos := Pos + Vel;
      if (App.Level.State = lsPlaying)
      or (App.Level.State = lsFinished) then
      Pos.y := Pos.y + App.Level.VScreenSpeed;
      if (App.Level.State = lsFinished)
      and (App.CurTime - App.Level.LevelEndTime > 1000) then
      Vel := Vel + G2Vec2(0, 0.004);
      Vel := Vel * 0.8;
      hs := Size * 0.4;
      hs := Size * 0.5;
      br.l := App.Level.VCamPos.x - App.Level.VScreenSize.x * 0.5 / (1 + Alt * 0.1);
      br.r := App.Level.VCamPos.x + App.Level.VScreenSize.x * 0.5 / (1 + Alt * 0.1);
      br.t := App.Level.VCamPos.y + App.Level.VScreenSize.y * 0.5 / (1 + Alt * 0.1);
      br.b := App.Level.VCamPos.y - App.Level.VScreenSize.y * 0.5 / (1 + Alt * 0.1);
      if App.Level.State <> lsFinished then
      begin
        if Pos.x - hs < br.l then Pos.x := br.l + hs
        else if Pos.x + hs > br.r then Pos.x := br.r - hs;
        if Pos.y - hs < br.b then Pos.y := br.b + hs
        else if Pos.y + hs > br.t then Pos.y := br.t - hs;
      end;
      for i := 0 to Guns.Count - 1 do
      TPlayerGun(Guns[i]).Fire(g2.KeyDown[G2K_Space]);
      UpdateBounds;
      if (Health mod 100 > 0)
      and (App.CurTime - TimeHit > 1000)
      and (App.CurTime - TimeShieldHit > 1000) then
      begin
        Health := Health + 1;
      end;
      if (Shield < ShieldMax)
      and (App.CurTime - TimeHit > 1000)
      and (App.CurTime - TimeShieldHit > 1000) then
      begin
        Shield := Shield + 1;
      end;
    end;
  end;
end;

procedure TPlayer.Hit(const Amount: Integer);
  var Dam, n: Integer;
begin
  if App.Level.State <> lsFinished then
  begin
    Dam := Amount;
    if Shield >= Dam then
    begin
      Shield := Shield - Dam;
      TimeShieldHit := App.CurTime;
    end
    else
    begin
      Dam := Dam - Shield;
      Shield := 0;
      Health := Health - Dam;
      TimeHit := App.CurTime;
    end;
  end;
end;

procedure TPlayer.Kill;
begin
  Die;
  App.Player := nil;
  App.Level.CreateExplosion(Pos);
end;

procedure TPlayer.Accelerate(const v: TG2Vec2);
  var d: Single;
begin
  Vel := Vel + v.Norm * Speed;
  d := Vel.Len;
  if d > SpeedMax then
  Vel := Vel.Norm * SpeedMax;
end;

procedure TPlayer.UpdateBounds;
  var hs: Single;
begin
  hs := Size * 0.4;
  xl := Pos.x - hs; xh := Pos.x + hs;
  yl := Pos.y - hs; yh := Pos.y + hs;
end;

procedure TPlayer.AddGun(const GunClass: CPlayerGun);
  var i: Integer;
  var Gun: TPlayerGun;
begin
  for i := 0 to Guns.Count - 1 do
  if TPlayerGun(Guns[i]) is GunClass then
  begin
    if TPlayerGun(Guns[i]).Level < 2 then
    TPlayerGun(Guns[i]).Level := TPlayerGun(Guns[i]).Level + 1;
    Exit;
  end;
  Gun := GunClass.Create;
  Guns.Add(Gun);
  Gun.Mount(@Pos, G2Vec2(0, 0.025), G2Vec2(-0.017, 0.005), G2Vec2(0.017, 0.005), Faction);
end;
//TPlayer END

//TGameOver BEGIN
procedure TGameOver.Update;
begin
  if G2Time - App.StateTime > 4500 then
  begin
    if NextLevel > -1 then
    begin
      App.Level.LoadLevel(NextLevel);
      App.GameState := gsGame;
    end
    else
    begin
      App.GameState := gsMenu;
    end;
  end;
end;

procedure TGameOver.Render;
  var asp, g: Single;
  var Str: AnsiString;
begin
  asp := g2.Params.Width / g2.Params.Height;
  g2.PicRect(
    0, 0, g2.Params.Width, g2.Params.Height, 0, 0,
    asp, 1, $ffffffff, App.MenuMain.TexBack, bmNormal, tfLinear
  );
  if NextLevel > -1 then
  begin
    Str := 'Level ' + IntToStr(App.LevelCur) + ' Complete';
    App.Font0.Print(
      (g2.Params.Width - App.Font0.TextWidth(Str) * App.gs) * 0.5,
      g2.Params.Height * 0.5 - 20 * App.gs - App.Font0.TextHeight(Str),
      App.gs, App.gs, $ffffffff, Str, bmNormal, tfLinear
    );
    Str := 'Your Current Score: ' + IntToStr(App.Score);
    App.Font0.Print(
      (g2.Params.Width - App.Font0.TextWidth(Str) * App.gs) * 0.5,
      g2.Params.Height * 0.5 + 20 * App.gs,
      App.gs, App.gs, $ffffffff, Str, bmNormal, tfLinear
    );
  end
  else
  begin
    Str := 'Game Over';
    App.Font0.Print(
      (g2.Params.Width - App.Font0.TextWidth(Str) * App.gs) * 0.5,
      g2.Params.Height * 0.5 - 20 * App.gs - App.Font0.TextHeight(Str),
      App.gs, App.gs, $ffffffff, Str, bmNormal, tfLinear
    );
    Str := 'Your Final Score: ' + IntToStr(App.Score);
    App.Font0.Print(
      (g2.Params.Width - App.Font0.TextWidth(Str) * App.gs) * 0.5,
      g2.Params.Height * 0.5 + 20 * App.gs,
      App.gs, App.gs, $ffffffff, Str, bmNormal, tfLinear
    );
  end;
  if G2Time - App.StateTime < 500 then
  begin
    g := (G2Time - App.StateTime) / 500;
    if g > 1 then g := 1; g := 1 - g;
    g2.PrimRect(0, 0, g2.Params.Width, g2.Params.Height, $000000 or (Round($ff * g) shl 24));
  end
  else if G2Time - App.StateTime > 4000 then
  begin
    g := (G2Time - App.StateTime - 4000) / 500;
    if g > 1 then g := 1;
    g2.PrimRect(0, 0, g2.Params.Width, g2.Params.Height, $000000 or (Round($ff * g) shl 24));
  end;
end;
//TGaveOver END

//TG2App BEGIN
procedure TG2App.SetGameState(const Value: TGameState);
begin
  _GameState := Value;
  StateTime := G2Time;
end;

procedure TG2App.Initialize;
  var ws, hs: Single;
  var i: Integer;
begin
  Sounds.Clear;
  for i := 0 to High(Profiles) do
  Profiles[i] := nil;
  ProfileCount := 0;
  ProfileSelected := -1;
  CurTime := G2Time();
  //GameState := gsDebug;
  g2.Window.Caption := 'Kosmos v1.0';
  GameState := gsLoading;
  ws := g2.Params.Width / 1024;
  hs := g2.Params.Height / 768;
  if ws > hs then begin gs := hs; gs2 := ws; end else begin gs := ws; gs2 := hs; end;
  Font1 := TG2Font.Create;
  Font1.Make(24);
  Font0 := TG2Font.Create;
  Font0.Load('gfx/Font0.g2f');

  TexBorder := TG2Texture2D.Create;
  TexBorder.Load('game\UIBorder.png', tuUsage2D);
  TexGradient0 := TG2Texture2D.Create;
  TexGradient0.Load('game\Gradient0.png', tuUsage2D);
  TexProgressBar := TG2Texture2D.Create;
  TexProgressBar.Load('game\ProgressBar.png', tuUsage2D);
  TexLogoIGDC := TG2Texture2D.Create;
  TexLogoIGDC.Load('game\LogoIGDC.png', tuUsage2D);

  SndMusic := CreateSound('game\Music.wav');
  SndBlast := CreateSound('game\Blast.wav');
  SndMissile := CreateSound('game\Missile.wav');
  SndExplosion := CreateSound('game\Explosion.wav');

  ImgListClouds.Initilaize('Clouds');
  ImgListGround.Initilaize('Ground');
  ImgListObjects.Initilaize('Objects');
  ImgListEnemies.Initilaize('Enemies');
  ImgListShots.Initilaize('Shots');
  ImgListEffects.Initilaize('Effects');
  ImgListShots.Load;
  ImgListEffects.Load;
  ImgListClouds.Load;
  ImgListGround.Load;
  ImgListEnemies.Load;

  InputMethods.Clear;

  g2.Gfx.ClearColor := $ff000000;

  Menu := nil;
  MenuMain := TMenuMain.Create;
  //MenuProfile := TMenuProfile.Create;
  //MenuCreateProfile := TMenuCreateProfile.Create;

  Level.Initialize;
  Level.Adjust;

  LevelFirst := 0;
  LevelLast := 1;
  LevelCur := 0;

  Menu := MenuMain;
  Menu.Show;
  GameState := gsIntro;

  SndMusic^.Loop;

  if GameState = gsDebug then Exit;
end;

procedure TG2App.Finalize;
begin
  MenuProfile.Free;
  MenuMain.Free;
  Level.Finalize;
  while InputMethods.Count > 0 do
  TInputMethod(InputMethods[0]).Free;
  ImgListObjects.Finalzie;
  ImgListClouds.Finalzie;
  ImgListGround.Finalzie;
  ImgListEnemies.Finalzie;
  ImgListShots.Finalzie;
  ImgListEffects.Finalzie;
  Font1.Free;
  Font0.Free;
  FreeSounds;
end;

procedure TG2App.Update;
  var i: Integer;
begin
  CurTime := G2Time();
  case GameState of
    gsMenu:
    begin
      Menu.OnUpdate;
    end;
    gsGame:
    begin
      if (Player <> nil)
      and (Level.State <> lsFinished) then
      for i := 0 to InputMethods.Count - 1 do
      TInputMethod(InputMethods[i]).Update;
      Level.Update;
    end;
    gsGameOver:
    begin
      GameOver.Update;
    end;
    gsIntro:
    begin
      if G2Time - StateTime >= 3000 then
      GameState := gsMenu;
    end;
  end;
end;

procedure TG2App.Render;
  var g: Single;
begin
  case GameState of
    gsIntro:
    begin
      RenderIntro;
    end;
    gsMenu:
    begin
      Menu.OnRender;
      if G2Time - StateTime < 500 then
      begin
        g := (G2Time - StateTime) / 500;
        if g > 1 then g := 1; g := 1 - g;
        g2.PrimRect(0, 0, g2.Params.Width, g2.Params.Height, $000000 or (Round($ff * g) shl 24));
      end;
    end;
    gsGame:
    begin
      Level.Render;
    end;
    gsGameOver:
    begin
      GameOver.Render;
    end;
  end;
  //Font1.Print(0, 0, 'Res: ' + IntToStr(g2.Params.Width) + 'x' + IntToStr(g2.Params.Height) + '; FPS: ' + IntToStr(g2.FPS));
end;

procedure TG2App.Print(const Char: AnsiChar);
begin
  if GameState = gsMenu then Menu.OnPrint(Char);
end;

procedure TG2App.KeyDown(const Key: Integer);
begin
  if GameState = gsMenu then Menu.OnKeyDown(Key);
  if Key = G2K_Escape then g2.Stop;
end;

procedure TG2App.KeyUp(const Key: Integer);
begin

end;

procedure TG2App.MouseDown(const Button, x, y: Integer);
begin
  if (GameState = gsMenu) and (Button = G2MB_Left) then Menu.OnMouseDown(x, y);
end;

procedure TG2App.MouseUp(const Button, x, y: Integer);
begin
  if (GameState = gsMenu) and (Button = G2MB_Left) then Menu.OnMouseUp(x, y);
end;

procedure TG2App.DrawRing(
  const Pos: TG2Vec2; const Radius0, Radius1: Single; const Tex: TG2Texture2D; const Color: TG2Color;
  const BlendMode: TG2BlendMode = bmNormal; const TexSpan: Single = 1; const Segments: Integer = 32
);
  var i, n: Integer;
  var Ang, tc, tc0, tc1: Single;
  var Pos0, Pos1, Pos2, Pos3: TG2Vec2;
begin
  g2.Gfx.Poly2D.PolyBegin(ptTriangles, Tex, BlendMode, tfLinear);
  Ang := TwoPi / Segments;
  tc := TexSpan / Segments;
  Pos0 := Pos + G2Vec2(Radius0, 0);
  Pos1 := Pos + G2Vec2(Radius1, 0);
  tc0 := 0;
  for i := 1 to Segments do
  begin
    G2SinCos(Ang * i, Pos2.y, Pos2.x);
    Pos3 := Pos + Pos2 * Radius1;
    Pos2 := Pos + Pos2 * Radius0;
    tc1 := tc0 + tc;
    g2.Gfx.Poly2D.PolyAdd(Pos0.x, Pos0.y, tc0, 1, Color);
    g2.Gfx.Poly2D.PolyAdd(Pos1.x, Pos1.y, tc0, 0, Color);
    g2.Gfx.Poly2D.PolyAdd(Pos2.x, Pos2.y, tc1, 1, Color);
    g2.Gfx.Poly2D.PolyAdd(Pos2.x, Pos2.y, tc1, 1, Color);
    g2.Gfx.Poly2D.PolyAdd(Pos1.x, Pos1.y, tc0, 0, Color);
    g2.Gfx.Poly2D.PolyAdd(Pos3.x, Pos3.y, tc1, 0, Color);
    Pos0 := Pos2;
    Pos1 := Pos3;
    tc0 := tc1;
  end;
  g2.Gfx.Poly2D.PolyEnd;
end;

procedure TG2App.DrawBorder(const r: TG2Rect; const Border: Single; const Color: TG2Color);
  var x0, x1, x2, x3, y0, y1, y2, y3, bw, bh: Single;
begin
  x0 := r.l; x1 := r.l + Border; x2 := r.r - Border; x3 := r.r;
  y0 := r.t; y1 := r.t + Border; y2 := r.b - Border; y3 := r.b;
  bw := x2 - x1; bh := y2 - y1;
  g2.PicRect(x0, y0, Border, Border, 0, 0, 0.5, 0.5, Color, TexBorder, bmNormal, tfLinear);
  g2.PicRect(x1, y0, bw, Border, 0.5, 0, 0.5, 0.5, Color, TexBorder, bmNormal, tfLinear);
  g2.PicRect(x2, y0, Border, Border, 0.5, 0, 1, 0.5, Color, TexBorder, bmNormal, tfLinear);
  g2.PicRect(x0, y1, Border, bh, 0, 0.5, 0.5, 0.5, Color, TexBorder, bmNormal, tfLinear);
  g2.PicRect(x0, y2, Border, Border, 0, 0.5, 0.5, 1, Color, TexBorder, bmNormal, tfLinear);
  g2.PicRect(x2, y1, Border, bh, 0.5, 0.5, 1, 0.5, Color, TexBorder, bmNormal, tfLinear);
  g2.PicRect(x1, y2, bw, Border, 0.5, 0.5, 0.5, 1, Color, TexBorder, bmNormal, tfLinear);
  g2.PicRect(x2, y2, Border, Border, 0.5, 0.5, 1, 1, Color, TexBorder, bmNormal, tfLinear);
end;

procedure TG2App.PrintInRect(
  const Font: TG2Font; const PrintRect: TG2Rect;
  const Text: AnsiString; const Color: TG2Color;
  const Center: Boolean = False
);
  var w, h, s, sx, sy, x, y: Single;
begin
  w := Font.TextWidth(Text);
  h := Font.TextHeight('A');
  sx := PrintRect.w / w;
  sy := PrintRect.h / h;
  if sx < sy then s := sx else s := sy;
  if Center then
  begin
    x := PrintRect.x + (PrintRect.w - w * s) * 0.5;
    y := PrintRect.y + (PrintRect.h - h * s) * 0.5;
  end
  else
  begin
    x := PrintRect.x;
    y := PrintRect.y;
  end;
  Font.Print(x, y, s, s, Color, Text, bmNormal, tfLinear);
end;

function TG2App.ProfileFind(const ProfName: AnsiString): Integer;
  var i: Integer;
begin
  for i := 0 to ProfileCount - 1 do
  if Profiles[i]^.Name = ProfName then
  begin
    Result := i;
    Exit;
  end;
  Result := -1;
end;

procedure TG2App.ProfileAdd(const ProfName: AnsiString);
begin
  New(Profiles[ProfileCount]);
  Profiles[ProfileCount]^.Name := ProfName;
  ProfileSelected := ProfileCount;
  Inc(ProfileCount);
end;

procedure TG2App.ProfileDelete(const Index: Integer);
  var i: Integer;
begin
  for i := Index to ProfileCount - 2 do
  begin
    Profiles[i]^.Name := Profiles[i + 1]^.Name;
  end;
  Dec(ProfileCount);
  Dispose(Profiles[ProfileCount]);
end;

procedure TG2App.RenderBox(const r: TG2Rect; const bs: Single; const Color: TG2Color; const Tex: TG2Texture2D; const BlendMode: TG2BlendMode = bmNormal);
begin
  RenderBox(r.x, r.y, r.w, r.h, bs, Color, Tex, BlendMode);
end;

procedure TG2App.RenderBox(const x, y, w, h, bs: Single; const Color: TG2Color; const Tex: TG2Texture2D; const BlendMode: TG2BlendMode = bmNormal);
  var x0, x1, x2, y0, y1, y2, bw, bh: Single;
begin
  bw := w - bs * 2;
  bh := h - bs * 2;
  x0 := x; x1 := x + bs; x2 := x1 + bw;
  y0 := y; y1 := y + bs; y2 := y1 + bh;
  g2.PicRect(x0, y0, bs, bs, 0, 0, 0.5, 0.5, Color, Tex, BlendMode, tfLinear);
  g2.PicRect(x1, y0, bw, bs, 0.5, 0, 0.5, 0.5, Color, Tex, BlendMode, tfLinear);
  g2.PicRect(x2, y0, bs, bs, 0.5, 0, 1, 0.5, Color, Tex, BlendMode, tfLinear);
  g2.PicRect(x0, y1, bs, bh, 0, 0.5, 0.5, 0.5, Color, Tex, BlendMode, tfLinear);
  g2.PicRect(x2, y1, bs, bh, 0.5, 0.5, 1, 0.5, Color, Tex, BlendMode, tfLinear);
  g2.PicRect(x0, y2, bs, bs, 0, 0.5, 0.5, 1, Color, Tex, BlendMode, tfLinear);
  g2.PicRect(x1, y2, bw, bs, 0.5, 0.5, 0.5, 1, Color, Tex, BlendMode, tfLinear);
  g2.PicRect(x2, y2, bs, bs, 0.5, 0.5, 1, 1, Color, Tex, BlendMode, tfLinear);
  g2.PicRect(x1, y1, bw, bh, 0.5, 0.5, 0.5, 0.5, Color, Tex, BlendMode, tfLinear);
end;

procedure TG2App.RenderButton(const r: TG2Rect; const bs: Single; const Caption: AnsiString; const Tex0, Tex1, Tex2: TG2Texture2D; const TextScaleX: Single = 1; const TextScaleY: Single = 1);
begin
  RenderButton(r.x, r.y, r.w, r.h, bs, Caption, Tex0, Tex1, Tex2, TextScaleX, TextScaleY);
end;

procedure TG2App.RenderButton(const x, y, w, h, bs: Single; const Caption: AnsiString; const Tex0, Tex1, Tex2: TG2Texture2D; const TextScaleX: Single = 1; const TextScaleY: Single = 1);
  var R: TRect;
  var TextRect: TG2Rect;
  var Tex: TG2Texture2D;
  var TextPress: Single;
begin
  R.Left := Round(x);
  R.Top := Round(y);
  R.Right := Round(x + w);
  R.Bottom := Round(y + h);
  TextPress := 0;
  if PtInRect(R, g2.MousePos) then
  begin
    if g2.MouseDown[G2MB_Left]
    and PtInRect(R, g2.MouseDownPos[G2MB_Left]) then
    begin
      Tex := Tex2;
      TextPress := App.gs;
    end
    else
    Tex := Tex1;
  end
  else
  Tex := Tex0;
  RenderBox(x, y, w, h, bs, $ffffffff, Tex);
  if Length(Caption) > 0 then
  begin
    TextRect.l := x + 2 * App.gs;
    TextRect.t := y + 2 * App.gs + TextPress;
    TextRect.r := x + w - 2 * App.gs;
    TextRect.b := y + h - 2 * App.gs + TextPress;
    TextRect.x := TextRect.x + (TextRect.w - TextRect.w * TextScaleX) * 0.5;
    TextRect.y := TextRect.y + (TextRect.h - TextRect.h * TextScaleY) * 0.5;
    TextRect.w := TextRect.w * TextScaleX;
    TextRect.h := TextRect.h * TextScaleY;
    TextFitRect(App.Font1, TextRect, $ff000000, Caption);
  end;
end;

procedure TG2App.TextFitRect(const TextFont: TG2Font; const R: TG2Rect; const Color: TG2Color; const Text: AnsiString; const Center: Boolean = True);
  var tw, th, sw, sh, t, x: Single;
begin
  tw := TextFont.TextWidth(Text); th := TextFont.TextHeight(Text);
  sw := R.w / tw; sh := R.h / th; if sw < sh then t := sw else t := sh;
  tw := tw * t; th := th * t;
  if Center then x := R.x + (R.w - tw) * 0.5 else x := R.x;
  TextFont.Print(
    x, R.y + (R.h - th) * 0.5,
    t, t, Color, Text,
    bmNormal, tfLinear
  );
end;

procedure TG2App.RenderIntro;
  var g: Single;
begin
  g := (G2Time - StateTime) / 2000;
  if g > 1 then g := 1;
  g2.PrimRect(0, 0, g2.Params.Width, g2.Params.Height, $ff000000);
  g2.PicRect(
    g2.Params.Width * 0.5, g2.Params.Height * 0.5, g2.Params.Height, g2.Params.Height,
    $ffffffff, 0.5, 0.5, g, g, 0, False, False,
    TexLogoIGDC, TexLogoIGDC.Width, TexLogoIGDC.Height, 0, bmNormal, tfLinear
  );
  if G2Time - StateTime > 2500 then
  begin
    g := (G2Time - StateTime - 2500) / 500;
    if g > 1 then g := 1;
    g2.PrimRect(0, 0, g2.Params.Width, g2.Params.Height, $000000 or (Round($ff * g) shl 24));
  end;
end;

procedure TG2App.AddScore(const Amount: Integer);
begin
  if CurTime - LastKillTime < 2000 then
  begin
    KillStreak := Min(KillStreak + 1, 22);
    Score := Score + Round(Amount * Power(1.1, KillStreak));
  end
  else
  begin
    KillStreak := 0;
    Score := Score + Amount;
  end;
  LastKillTime := App.CurTime;
end;

function TG2App.CreateSound(const FileName: WideString): PSound;
begin
  New(Result);
  Result^.Load(FileName);
  Sounds.Add(Result);
end;

function TG2App.CreateSound(const Buffer: Pointer; const Size: Int64): PSound;
begin
  New(Result);
  Result^.Load(Buffer, Size);
  Sounds.Add(Result);
end;

procedure TG2App.FreeSounds;
  var i: Integer;
begin
  for i := 0 to Sounds.Count - 1 do
  begin
    PSound(Sounds[i])^.Free;
    Dispose(PSound(Sounds[i]));
  end;
  Sounds.Clear;
end;
//TG2App END

end.
