{*************************************************************************}
{                                                                         }
{ DQE2_Types.pas - DQE2 SDK                                               }
{                                                                         }
{ This file is a part of the project:                                     }
{   Dizzy Quest Editor - the quest editor for Dizzy-like games            }
{                                                                         }
{ © Parfenov Alexey Andreevich, 2007                                      }
{                                                                         }
{ e-mail: AlkatrazStudio@yandex.ru                                        }
{                                                                         }
{ This library is free software; you can redistribute it and/or           }
{ modify it under the terms of the GNU General Public License             }
{ as published by the Free Software Foundation; either version 2.1 of     }
{ the License, or (at your option) any later version.                     }
{                                                                         }
{ This library is distributed in the hope that it will be useful,         }
{ but WITHOUT ANY WARRANTY; without even the implied warranty of          }
{ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU        }
{ General Public License for more details.                                }
{                                                                         }
{ You may read GNU General Public License at:                             }
{   http://www.gnu.org/copyleft/gpl.html                                  }
{                                                                         }
{*************************************************************************}
unit DQE2_types;

// Use the following directive to speed up search in DQE2_List, but remember:
// 1) object names will be case sensitive
// 2) names that are longer than 23 symbols will not be comparable
// 3) files for sprites must have their extensions in lower case
{.$DEFINE NAMEHASH}

// Use the following directive to reload sprite graphics each time you call
// Sprite.Load method, even the graphics was loaded before
// Warning: this may cause huge memory leakage, so use it at your own risk!
{.$DEFINE RELOADGRAPHICS}

interface

uses
  // Standart
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, StdActns, ToolWin,ImgList, ExtCtrls,
  jpeg, inifiles, types, math, mmsystem,
  // JEDI
  jvGif,JvTimer,
  // Asphyre
  AsphyreDevices, AsphyreDef, AsphyreCanvas, AsphyreImages, AsphyreFontsAlt,
  AsphyreTextures, AsphyreBmpLoad, AsphyrePNG, Asphyre2D,
  // paxScript
  BASE_PARSER, PaxScripter, PaxPascal, BASE_SCRIPTER, BASE_CODE,
  BASE_CLASS, BASE_SYMBOL, BASE_SYS,
  // Alexey
  gameplayer, alkatraz, stream_ex, Bradis,
  // IMPs
  IMP_System, IMP_Math, IMP_SysUtils, IMP_Classes, IMP_GamePlayer,
  IMP_AsphyreDef
  ;

type DQE2_Dummy=class(TCollectionItem)
  private
    procedure SetName(const Value: String);virtual;
  protected
    FName:String;
    FTagData:Pointer;
    FTag:integer;
    FTagFloat:Single;
    {$IFDEF NAMEHASH}FNameHashKey:Cardinal;{$ENDIF}
    constructor CreateWithoutName(Collection:TCollection);virtual;
    procedure AssignTo(Dest: TPersistent); override;
  public
    {$IFDEF NAMEHASH}class function ComputeHashKey(str:string):Cardinal;{$ENDIF}
    constructor Create(Collection:TCollection);override;
    property TagData:Pointer read FTagData write FTagData;
    procedure Assign(Src: TPersistent);
  published
    property Name:String read FName write SetName;
    property Tag:integer read FTag write FTag;
    property TagFloat:Single read FTagFloat write FTagFloat;
end;

type DQE2_DummyClass=class of DQE2_Dummy;

type DQE2_Sprite=class(DQE2_Dummy)
  private
    procedure SetDefaultName(const Value: string);
    procedure SetDefaultAlpha(const Value: byte);
    procedure SetDefaultAnimSpeed(const Value: ShortInt);
    procedure SetDefaultCollider(const Value: Boolean);
    procedure SetDefaultTransparent(const Value: boolean);
    procedure UnLoad;
    procedure SetName(const Value:string);override;
  protected
    FImages:TAsphyreImages;
    FSingleFrame:boolean;
    FDefaultName:string;
    FDefaultTransparent:boolean;
    FDefaultCollider:boolean;
    FDefaultAlpha:byte;
    FDefaultAnimSpeed:ShortInt;
    FThumbnail:TBitmap;
    FLoaded:Boolean;
    FTransparentColor:TColor;
    FTimesUsed:integer;
    FINI:TIniFile;
    FShortName:string;
    FHeight,FWidth:Word;
    FMask:array of array of array of boolean;
    FOriginalAlpha:cardinal;
    procedure UpdateFromINI(_nam:string;_tr,_col:boolean;_spd:ShortInt;_alp:byte);
    procedure UpdateTransparency(T:boolean);
    procedure AssignTo(Dest: TPersistent); override;
  public
    property DefaultName:string read FDefaultName write SetDefaultName;
    property DefaultTransparent:boolean read FDefaultTransparent write SetDefaultTransparent;
    property DefaultCollider:Boolean read FDefaultCollider write SetDefaultCollider;
    property DefaultAlpha:byte read FDefaultAlpha write SetDefaultAlpha;
    property DefaultAnimSpeed:ShortInt read FDefaultAnimSpeed write SetDefaultAnimSpeed;
    property SingleFrame:boolean read FSingleFrame;
    property Images:TAsphyreImages read FImages;
    property Thumbnail:TBitmap read FThumbnail;
    property Loaded:Boolean read FLoaded;
    property TransparentColor:TColor read FTransparentColor;
    property TimesUsed:integer read FTimesUsed;
    property INI:TIniFile read FINI;
    property ShortName:string read FShortName;
    property Height:Word read FHeight;
    property Width:Word read FWidth;
    constructor Create(Collection:TCollection);override;
    destructor Destroy;override;
    function Prepare(FileName:string):boolean;
    procedure Load;
end;

type DQE2_ObjectState=record
  X,Y:SmallInt;
  Alpha:Byte;
  Angle:Word;
  FlipVert,FlipHoriz:Boolean;
  CenterX,CenterY:SmallInt;
  AnimStart,AnimEnd:Word;
  AnimSpeed:Smallint;
  DrawFX:Cardinal;
  Color:TColor;
  Visible:Boolean;
  ReservedInt1:Integer;
  ReservedInt2:Integer;
  ReservedPtr1:Pointer;
end;

type DQE2_Graphical=class(DQE2_Dummy)
  private
    procedure SetColor(const Value: TColor);
  protected
    FX,FY:Smallint;
    FSprite:DQE2_Sprite;
    FWidth,FHeight:word;
    FAlpha:Byte;
    FDrawFX:Cardinal;
    FColor:TColor;
    FAnimFrame,FAnimCount,FAnimStart,FAnimEnd:byte;
    FAnimSpeed,FAnimPos:Shortint;
    FAngle:SmallInt;
    FFlipVert,FFlipHoriz:boolean;
    FCenterX,FCenterY:SmallInt;
    CurColor:TColor4;
    FState:DQE2_ObjectState;
    FChanged:boolean;
    FLocalMatrix:TPoint4;
    FVisible:boolean;
    FSaveName:boolean;
    procedure DoAnimate;virtual;
    procedure SetX(const Value: Smallint);virtual;
    procedure SetY(const Value: Smallint);virtual;
    procedure SetAlpha(const Value: byte);virtual;
    procedure SetAnimSpeed(const Value: Shortint);virtual;
    procedure SetDrawFX(const Value: Cardinal);virtual;
    procedure SetAnimEnd(const Value: Byte);virtual;
    procedure SetAnimStart(const Value: Byte);virtual;
    procedure SetSprite(const Value: DQE2_Sprite);virtual;
    procedure SetFlipHoriz(const Value: boolean);virtual;
    procedure SetFlipVert(const Value: boolean);virtual;
    procedure SetCenterX(const Value: SmallInt);virtual;
    procedure SetCenterY(const Value: SmallInt);virtual;
    procedure SetAngle(const Value: SmallInt);virtual;
    procedure SetState(const Value: DQE2_ObjectState);virtual;
    procedure OnSpriteChange(Value:DQE2_Sprite);virtual;
	  procedure ChangePos;
    procedure ChangeColor;
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create(Collection:TCollection);override;
    destructor Destroy;override;
    procedure SaveState;virtual;
    procedure LoadState;virtual;
    property Sprite:DQE2_Sprite read FSprite write SetSprite;
    property State:DQE2_ObjectState read FState write SetState;
    procedure Step;virtual;
    function PixelCollision(_x,_y:integer):boolean;
  published
    property X:SmallInt read FX write SetX;
    property Y:SmallInt read FY write SetY;
    property Width:word read FWidth;
    property Height:word read FHeight;
    property Alpha:byte read FAlpha write SetAlpha;
    property Angle:SmallInt read FAngle write SetAngle;
    property CenterX:Smallint read FCenterX write SetCenterX;
    property CenterY:Smallint read FCenterY write SetCenterY;
    property AnimStart:byte read FAnimStart write SetAnimStart;
    property AnimEnd:byte read FAnimEnd write SetAnimEnd;
    property AnimSpeed:ShortInt read FAnimSpeed write SetAnimSpeed;
    property AnimCount:byte read FAnimCount;
    property AnimFrame:byte read FAnimFrame;
    property FlipHoriz:boolean read FFlipHoriz write SetFlipHoriz;
    property FlipVert:boolean read FFlipVert write SetFlipvert;
    property Color:TColor read FColor write SetColor;
    property DrawFX:Cardinal read FDrawFX write SetDrawFX;
    property Visible:boolean read FVisible write FVisible;
    property SaveName:boolean read FSaveName write FSaveName;
end;

type DQE2_List=class(TCollection)
  protected
    FFriendlyClassName:string;
  public
    constructor Create(ItemClass: TCollectionItemClass);
    function Add:DQE2_Dummy;
    function FindByName(Name: string): DQE2_Dummy;
    property Items[Index:Integer]:TCollectionItem read GetItem write SetItem; default;
  published
    property FriendlyClassName:string read FFriendlyClassName;
    property Count;
end;

type DQE2_Zone=class(DQE2_Dummy)
  private
    procedure SetColor(const Value: TColor);
    procedure SetBackSpeed(const Value: SmallInt);
  protected
    FColor:TColor;
    FStartScripts:TList;
    FObjects:TList;
    FLeft,FTop,FRight,FBottom:word;
    CurColor:Cardinal;
    FThumbnail:TBitmap;
    FEnvironment:TGPStore;
    FDisableEnvironment:boolean;
    FTagRect:TRect;
    FHeight,FWidth:word;
    FPreRenderZone:DQE2_Zone;
    FBackSpeed:Smallint;
    FBackOffsetX,FBackOffsetY:Smallint;
    FCurBackSpeed:single;
    FTempL,FTempR,FTempT,FTempB,FTempW,FTempH:Integer;
    procedure UpdateTempPos;
    procedure AssignTo(Dest: TPersistent); override;
  public
    procedure SetNewPos(L,T,R,B:Word);
    constructor Create(Collection: TCollection;L,T,R,B:Word);
    destructor Destroy;override;
    property Color:TColor read FColor write SetColor;
    property Left:word read FLeft;
    property Top:word read FTop;
    property Bottom:word read FBottom;
    property Right:word read FRight;
    property Height:word read FHeight;
    property Width:word read FWidth;
    property StartScripts:TList read FStartScripts;
    property Objects:TList read FObjects;
    property Thumbnail:TBitmap read FThumbnail;
    property DisableEnvironment:boolean read FDisableEnvironment write FDisableEnvironment;
    property Environment:TGPStore read FEnvironment write FEnvironment;
    property TagRect:TRect read FTagRect write FTagRect;
    property PreRenderZone:DQE2_Zone read FPreRenderZone write FPreRenderZone;
    property BackSpeed:Smallint read FBackSpeed write SetBackSpeed;
    property BackOffsetX:Smallint read FBackOffsetX write FBackOffsetX;
    property BackOffsetY:Smallint read FBackOffsetY write FBackOffsetY;
end;

type DQE2_Item=class(DQE2_Graphical)
  private
    procedure SetVisible(const Value: boolean);
  protected
    FZone:DQE2_Zone;
    FDescription:string;
    FInBagIndex:ShortInt;
    FRealName:string;
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create(Collection: TCollection);override;
    destructor Destroy;override;
    procedure Step;override;
    procedure PutInBag;                              
    procedure Drop;
    procedure BringToFront;
    procedure SendToBack;
  published
    property Zone:DQE2_Zone read FZone write FZone;
    property Description:string read FDescription write FDescription;
    property InBagIndex:ShortInt read FinBagIndex;
    property RealName:string read FRealName write FRealName;
    property Visible:boolean read FVisible write SetVisible;
end;

type DQE2_Error=record
  IsError,IsSyntaxError:boolean;
  Line,Row,Pos:Integer;
  Text:string;
  MsgID:integer;
end;

type DQE2_Scriptable=class;

DQE2_ScriptMsg=procedure(Sender:DQE2_Scriptable;var Ignore:boolean)of object;

DQE2_ScriptStatus=procedure(Sender:DQE2_Scriptable)of object;

DQE2_ScriptBreakpoint=procedure(Sender:DQE2_Scriptable;var DoBreak:boolean)of object;

DQE2_Scriptable=class(DQE2_Dummy)
  private
    procedure SetText(const Value: string);
    procedure OnShowError(Sender:TPaxScripter);
    function GetBreakpoint(Index: Integer): Integer;
    function GetCurrentLine: integer;
    function GetInDebug: boolean;
  protected
    FRunning,FSuspended,FAutoStop:boolean;
    FProg:TPaxScripter;
    FChanged:boolean;
    FText:string;
    FLastError:DQE2_Error;
    FOnError:DQE2_ScriptMsg;
    FDefaultIgnore:boolean;
    FOnStart,FOnStop:DQE2_ScriptStatus;
    FBreakpoints:array of Integer;
    FBreakpointsCount:integer;
    FBreakFuncID:integer;
    FExceptionResult:integer;
    FUseScriptText:boolean;
    function DoMsg(msgID:integer;Def:boolean):boolean;
    function Exec(FuncID:Integer;Params:array of const;
                  out FuncResult:integer;
                  ShouldBeIdle:boolean;ExceptionErr:integer):boolean;
    function OnBreakCustomAction(FuncID:integer;const Params:array of const):boolean;virtual;
    procedure AssignTo(Dest: TPersistent); override;
    procedure MembersCallback(const Name:string; ID:Integer; Kind:TPAXMemberKind; ml:TPAXModifierList; Data:Pointer);
    procedure ParamsCallback(const Name:string; ID:Integer; Kind:TPAXMemberKind; ml:TPAXModifierList; Data:Pointer);
  public
    property Changed:boolean read FChanged;
    property LastError:DQE2_Error read FLastError;
    property OnError:DQE2_ScriptMsg read FOnError write FOnError;
    property DefaultIgnore:boolean read FDefaultIgnore write FDefaultIgnore;
    function Compile(AdditionalCode:string=''):boolean; virtual;
    function Run:boolean;virtual;
    procedure Stop;virtual;
    function Resume(NextLine:boolean=false):shortint;virtual;
    constructor Create(Collection: TCollection);override;
    destructor Destroy;override;
    property OnStart:DQE2_ScriptStatus read FOnStart write FOnStart;
    property OnStop:DQE2_ScriptStatus read FOnStop write FOnStop;
    property Breakpoints[Index:Integer]:Integer read GetBreakpoint;
    procedure ClearBreakpoints;
    procedure AddBreakpoint(Line:Integer);
    procedure RemoveBreakpoint(Line:Integer);
    property BreakpointsCount:integer read FBreakpointsCount;
    property InDebug:boolean read GetInDebug;
    procedure GetLocals(VarsList,ValuesList:TStrings);
    function Eval(Expression:string;out output:Variant):boolean;
  published
    property Text:string read FText write SetText;
    property AutoStop:boolean read FAutoStop write FAutoStop;
    property Running:boolean read FRunning;
    property CurrentLine:integer read GetCurrentLine;
    function GetCurrentStdFunctionName:string;virtual;
    property UseScriptText:boolean read FUseScriptText write FUseScriptText;
end;

type DQE2_Script=class;

DQE2_OnScriptInit=function(Sender:DQE2_Script):boolean of object;
DQE2_OnScriptStep=function(Sender:DQE2_Script):boolean of object;
DQE2_OnScriptUse=function(Sender:DQE2_Script;Item:DQE2_Item):integer of object;
DQE2_OnScriptPreRender=procedure(Sender:DQE2_Script) of object;
DQE2_OnScriptPostRender=procedure(Sender:DQE2_Script) of object;

DQE2_Script=class(DQE2_Scriptable)
  protected
    FIndexInList:integer;
    FPassCount:integer;
    FFuncRun,FFuncStep,FFuncUse,FFuncPrerender,FFuncPostrender:Integer;
    FBreakItem:DQE2_Item;
    FOnCustomInit:DQE2_OnScriptInit;
    FOnCustomStep:DQE2_OnScriptStep;
    FOnCustomUse:DQE2_OnScriptUse;
    FOnCustomPreRender:DQE2_OnScriptPreRender;
    FOnCustomPostRender:DQE2_OnScriptPostRender;
    procedure _Run(res:boolean);
    procedure _Step(res:boolean);
    procedure _OnUse(Item:DQE2_Item;res:integer);
    function OnBreakCustomAction(FuncID:integer;const Params:array of const):boolean;override;
    procedure AssignTo(Dest: TPersistent); override;
  public
    property PassCount:integer read FPassCount;
    function Compile(AdditionalCode:string=''):boolean; override;
    function Run(AutoStop:boolean=true):boolean;
    function Step:boolean;
    procedure Stop; override;
    function OnUse(Item:DQE2_Item):boolean;
    function OnPostRender:boolean;
    function OnPreRender:boolean;
    function Resume(NextLine:boolean=false):shortint;override;
    constructor Create(Collection: TCollection);override;
    destructor Destroy;override;
    property Suspended:boolean read FSuspended write FSuspended;
    function GetCurrentStdFunctionName:string;override;
    property OnCustomInit:DQE2_OnScriptInit read FOnCustomInit write FOnCustomInit;
    property OnCustomStep:DQE2_OnScriptStep read FOnCustomStep write FOnCustomStep;
    property OnCustomUse:DQE2_OnScriptUse read FOnCustomUse write FOnCustomUse;
    property OnCustomPostRender:DQE2_OnScriptPostRender read FOnCustomPostRender write FOnCustomPostRender;
end;

type DQE2_Entity=class;

DQE2_EntityType=class;

DQE2_Object=class(DQE2_Graphical)
  protected
    FZone:DQE2_Zone;
    FInZoneIndex:integer;
    FCollider:Boolean;
    FDamage:Integer;
    FMarked:boolean;
    procedure OnSpriteChange(Value:DQE2_Sprite);override;
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create(Collection: TCollection;Zone:DQE2_Zone);
    destructor Destroy;override;
    procedure SaveState;override;
    procedure LoadState;override;
    procedure Step; override;
    procedure SendToBack;
    procedure BringToFront;
    property InZoneIndex:integer read FInZoneIndex;
    function UpgradeToEntity(TheType:DQE2_EntityType):DQE2_Entity;
  published
    property Collider:boolean read FCollider write FCollider;
    property Damage:Integer read FDamage write FDamage;
    property Marked:boolean read FMarked write FMarked;
    property Zone:DQE2_Zone read FZone;
end;

DQE2_OnEntityInit=function(Sender:DQE2_Entity):boolean of object;
DQE2_OnEntityStep=function(Sender:DQE2_Entity):boolean of object;
DQE2_OnEntityDie=procedure(Sender:DQE2_Entity) of object;

DQE2_EntityType=class(DQE2_Scriptable)
  protected
    FOnCustomInit:DQE2_OnEntityInit;
    FOnCustomStep:DQE2_OnEntityStep;
    FOnCustomDie:DQE2_OnEntityDie;
    FFuncInit,FFuncStep,FFuncDie:Integer;
    FSender:DQE2_Entity;
    FCompiled:boolean;
    procedure _Init(Sender:DQE2_Entity;res:boolean);
    procedure _Step(Sender:DQE2_Entity;res:boolean);
    function OnBreakCustomAction(FuncID:integer;const Params:array of const):boolean;override;
    procedure Init(Entity:DQE2_Entity);
    procedure Step(Entity:DQE2_Entity);
    procedure Die(Entity:DQE2_Entity);
    procedure AssignTo(Dest: TPersistent); override;
  public
    function Compile(AdditionalCode:string=''):boolean;override;
    property OnCustomInit:DQE2_OnEntityInit read FOnCustomInit write FOnCustomInit;
    property OnCustomStep:DQE2_OnEntityStep read FOnCustomStep write FOnCustomStep;
    property OnCustomDie:DQE2_OnEntityDie read FOnCustomDie write FOnCustomDie;
    function Resume(NextLine:boolean=false):shortint;override;
    function CreateState:Variant;
    property Compiled:boolean read FCompiled;
    constructor Create(Collection: TCollection);override;
    destructor Destroy;override;
end;

DQE2_Entity=class(DQE2_Object)
  private
    procedure SetEntityType(const Value: DQE2_EntityType);
    function GetScriptState: TPAXScriptObject;
  protected
    FEntityType:DQE2_EntityType;
    FPassCount:integer;
    FRunning:boolean;
    FScriptState:Variant;
    procedure AssignTo(Dest: TPersistent); override;
  public
    property PassCount:integer read FPassCount;
    property Running:boolean read FRunning;
    procedure Run;
    procedure Step;override;
    procedure Die;
    procedure Stop;
    property ScriptState:TPAXScriptObject read GetScriptState;
    function DowngradeToObject:DQE2_Object;
    constructor Create(Collection: TCollection;Zone:DQE2_Zone;TheType:DQE2_EntityType);
    destructor Destroy;override;
    procedure SaveState;override;
    procedure LoadState;override;
  published
    property EntityType:DQE2_EntityType read FEntityType write SetEntityType;
end;

DQE2_Effect=class(DQE2_Graphical)
  protected
    FnLoops:integer;
    FPingPong:boolean;
    FDamage:ShortInt;
	  function DoAnimate:boolean;
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create(Collection: TCollection);override;             
    destructor Destroy;override;
  published
    property nLoops:integer read FnLoops write FnLoops;
    property PingPong:boolean read FPingPong;
    property Damage:ShortInt read FDamage write FDamage;
end;

type DQE2_BagCacheRecord=record
  _CurSel,_IX1,_IY1,_IX2,_IY2:Integer;
end;

type DQE2_BagCache=array of DQE2_BagCacheRecord;

type DQE2_Bag=class(DQE2_Graphical)
  private
    procedure SetCapacity(const Value: byte);
    procedure ChangeSelection;
  protected
    FCapacity:byte;
    FItems:TList;
    FCurSel:integer;
    FRunning:boolean;
    FKeyDown:boolean;
    FCurText:TStringList;
    FCurItem:dqe2_item;
    FCol:byte;
    BagCache:DQE2_BagCache;
    FFirstItem:integer;
    _dispitems:integer;
    procedure UpdateBagCache;
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create;overload;
    destructor Destroy;override;
    procedure Run(Selected:integer);
    function Step:integer;
    procedure Stop;
    property Items:TList read FItems;
  published
    property Running:boolean read FRunning;
    property Capacity:byte read FCapacity write SetCapacity;
end;

type DQE2_OnSimpleEvent=procedure of object;

type DQE2_Player=class(DQE2_Graphical)
  private
    procedure SetEnergy(const Value: Integer);
    procedure SetMaxEnergy(const Value: Word);
    procedure SetMaxLifes(const Value: byte);
  protected
    FlySpeed:ShortInt;
    OnFly:boolean;
    Dead:boolean;
    FlyTime:SmallInt;
    FAnimNum:byte;
    Rolling:boolean;
    FMaxLifes:byte;
    FMaxEnergy:Word;
    FEnergy:Integer;
    FSlowEnergy:Integer;
    FLifes:byte;
    FFootHold:DQE2_Object;
    FBag:DQE2_Bag;
    FLastX,FLastY:Smallint;
    FLastZone:DQE2_Zone;
    FSaved:boolean;
    FDeadTimer:word;
    FControl:boolean;
    FOnEnergyChange:DQE2_OnSimpleEvent;
    FOnSlowEnergyChange:DQE2_OnSimpleEvent;
    FOnLifesChange:DQE2_OnSimpleEvent;
    FOnCustomStep:DQE2_OnSimpleEvent;
	  function CheckCollision:boolean;
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create;
    destructor Destroy;override;
    procedure Step;override;
    property Bag:DQE2_Bag read FBag;
    property FootHold:DQE2_Object read FFootHold;
    function UseItem(Item:DQE2_Item):boolean;
    procedure MoveToZone(NewZone:DQE2_Zone;NewX,NewY:Smallint);
    function CollideWith(obj:DQE2_Graphical):boolean;
    property OnEnergyChange:DQE2_OnSimpleEvent read FOnEnergyChange write FOnEnergyChange;
    property OnSlowEnergyChange:DQE2_OnSimpleEvent read FOnSlowEnergyChange write FOnSlowEnergyChange;
    property OnLifesChange:DQE2_OnSimpleEvent read FOnLifesChange write FOnLifesChange;
    property OnCustomStep:DQE2_OnSimpleEvent read FOnCustomStep write FOnCustomStep;
  published
    property Energy:Integer read FEnergy write SetEnergy;
    property SlowEnergy:Integer read FSlowEnergy;
    property Lifes:byte read FLifes write FLifes;
    property MaxLifes:byte read FMaxLifes write SetMaxLifes;
    property MaxEnergy:Word read FMaxEnergy write SetMaxEnergy;
    property Control:boolean read FControl write FControl;
	  property Visible:boolean read FVisible write FVisible;
end;

type DQE2_Dialog=class;

DQE2_OnDialogPrepare=procedure(Sender:DQE2_Dialog;Question:integer)of object;
DQE2_OnDialogAnswer=function(Sender:DQE2_Dialog;Question,Answer:integer):integer of object;

DQE2_Dialog=class(DQE2_Scriptable)
  private
    procedure WriteLong(Index: integer; const Value: string);
    procedure WriteShort(Index: integer; const Value: string);
    function GetLong(Index: integer): string;
    function GetShort(Index: integer): string;
    function Answer(Q,A:integer):boolean;
  protected
    FQuestions,FAnswers,FAnswerTitles:TStringList;
    FCurIndex:integer;
    FCurAns,FCurQst,FCurSel:Integer;
    FCurAnswers:TStringList;
    FPlayerSpeech:boolean;
    FCurPS:string;
    FPersonName:string;
    CurPerson:string;
    CurText:TStringList;
    FAnswersCount:integer;
    FFuncAnswer,FFuncPrepare:integer;
    _LastA,_LastQ:integer;
    FOnCustomPrepare:DQE2_OnDialogPrepare;
    FOnCustomAnswer:DQE2_OnDialogAnswer;
    FFirstAnswer:integer;
    _dispanswers:integer;
    function OnBreakCustomAction(FuncID:integer;const Params:array of const):boolean;override;
    procedure _OnAnswer(Q,A,res:integer);
    procedure _Prepare;
    procedure AssignTo(Dest: TPersistent); override;
  public
    function Compile(AdditionalCode:string=''):boolean;override;
    function Run:boolean; override;
    procedure Step;
    constructor Create(Collection: TCollection);override;
    destructor Destroy;override;
    property Questions:TStringList read FQuestions;
    property AnswersCount:integer read FAnswersCount;
    property AnswersShort[Index:integer]:string read GetShort write WriteShort;
    property AnswersLong[Index:integer]:string read GetLong write WriteLong;
    procedure Stop; override;
    function AddAnswer(InShort,InLong:string):integer;
    function DeleteAnswer(Index:integer):integer;
    function Resume(NextLine:boolean=false):shortint;override;
    property OnCustomPrepare:DQE2_OnDialogPrepare read FOnCustomPrepare write FOnCustomPrepare;
    property OnCustomAnswer:DQE2_OnDialogAnswer read FOnCustomAnswer write FOnCustomAnswer;
  published
    property PersonName:string read FPersonName write FPersonName;
    property CurrentQuestion:Integer read FCurQst;
    property CurrentAnswer:Integer read FCurAns;
end;

type DQE2_Font=class(DQE2_Dummy)
  protected
    FFont:TAsphyreFontAlt;
    procedure AssignTo(Dest: TPersistent); override;
  public
    property Font:TAsphyreFontAlt read FFont;
    constructor Create(Collection: TCollection);override;
    destructor Destroy;override;
    function TextOut(X,Y:Integer;Text:string;Color:Cardinal;W:Integer=0;H:Integer=0):boolean;
end;

type DQE2_WorldState=(wsPlay,wsPause,wsStop);

type DQE2_OnZoneChange=procedure(NewZone:DQE2_Zone)of object;

type DQE2_OnStateChange=procedure(NewState:DQE2_WorldState) of object;

type DQE2_OnVarChange=procedure(VarName:string;VarValue:Variant)of object;

type DQE2_VideoOptions=record
  BitDepth:boolean;
  ZBuffer:boolean;
  Hardware:boolean;
  VSync:boolean;
  AntiAliasing:boolean;
  Dithering:boolean;
end;

type DQE2_ControlOptions=record
  KeyUp,KeyDown,KeyLeft,KeyRight,KeyFire,KeyCancel:byte;
  JoyFire,JoyCancel:Cardinal;
  JoyJump:Cardinal;
  JoyXasY:boolean;
  JoyBtnForJump:boolean;
end;

type DQE2_SkinOptions=record
  Answer,Bag,Question,Cancel:string;
  Head,Top,Bottom,Left,Right:string;
  EnergyRect,LifesRect,ZoneRect:TRect;
  LifeSprite,EnergySprite:string;
end;

type DQE2_World=class(TPersistent)
  private
    function GetCanSave: boolean;
    procedure SetControlOptions(const Value: DQE2_ControlOptions);
    procedure SetVideoOptions(const Value: DQE2_VideoOptions);
    function GetVarNames(Index: integer): string;
    function GetVarValues(Index: integer): string;
    procedure SetCurrentZone(const Value: DQE2_Zone);
    procedure SetUseCache(const Value: boolean);
    function GetGameSpeed: byte;
    procedure SetGameSpeed(const Value: byte);
    function GetKeyPressed(Index: Integer): boolean;
    procedure CreateVideoInterface;
    procedure SetJoystickEnabled(const Value: boolean);
  protected
    Win:HWND;
    FTimer:TJvTimer;
    FDevice:TAsphyreDevice;
    FCanvas:TAsphyreCanvas;
    FObjects:DQE2_List;
    FSprites:DQE2_List;
    FZones:DQE2_List;
    FDialogs:DQE2_List;
    FScripts:DQE2_List;
    FItems:DQE2_List;
    FFonts:DQE2_List;
    FEffects:DQE2_List;
    FEntityTypes:DQE2_List;
    FCurrentZone:DQE2_Zone;
    FImageList:TStringList;
    FPreRender,FPostRender:DQE2_OnSimpleEvent;
    FOnStep:DQE2_OnSimpleEvent;
    FOnZoneChange:DQE2_OnZoneChange;
    FOnStateChange:DQE2_OnStateChange;
    FOnGameOver:DQE2_OnSimpleEvent;
    FPlayer:DQE2_Player;
    FMusicPlayer:TGamePlayer;
    FOffsetX,FOffsetY:Integer;
    FState:DQE2_WorldState;
    FScriptLang:TPaxLanguage;
    FCurrentScript:DQE2_Script;
    FCurrentDialog:DQE2_Dialog;
    FCurrentEntity:DQE2_Entity;
    FRunningScripts,FRunningMusic:TList;
    W_Answer,W_Question,W_Cancel:DQE2_Graphical;
    WS_Answer,WS_Question,WS_Bag,WS_Cancel:DQE2_Sprite;
    FontStore:TAsphyreFontsAlt;
    F_PersonName,F_Question,F_Answer,F_ItemName,F_ItemText:DQE2_Font;
    FKeyDown,FKeyUp,FKeyLeft,FKeyRight,FKeyFire,FKeyCancel,FKeyJump,FKeysReady:boolean;
    FKeyboardState:TKeyboardState;
    FSharedDir:string;
    FVarNames,FVarValues:TStringList;
    FOnVarNew,FOnVarChange,FOnVarDelete:DQE2_OnVarChange;
    FVarCount:integer;
    FFadeSpeed,FFadeValue,FDeadTimer:Integer;
    FNextZone:DQE2_Zone;
    px,py:integer;
    FINI:TIniFile;
    FVideoOptions:DQE2_VideoOptions;
    FControlOptions:DQE2_ControlOptions;
    FSkinOptions:DQE2_SkinOptions;
    FJoyInfo:TJoyInfoEx;
    FSkinName:String;
    FSoundWalk,FSoundJump,FSoundWound,FSoundDie:TGPMusic;
    FLoadThumbnails:Boolean;
    FOnBreakpoint:DQE2_ScriptBreakpoint;
    FPlayerSprite:DQE2_Sprite;
    FUseCache:boolean;
    FScreenWidth,FScreenHeight:word;
    FFullScreen:boolean;
    FOnVideoUpdate:DQE2_OnSimpleEvent;
    FJoystickFound:boolean;
    _joyxmin,_joyxmax,_joyymin,_joyymax:Cardinal;
    FUseJoy:boolean;
    FJoystickEnabled:boolean;
    _width_div2,_height_div2,_maxzoneheight,_maxzonewidth:word;
    procedure OnRender(Sender:TObject);
    function GetUsedSprites(SPL:TStringList):integer;
    procedure Render;
    procedure _OnStep(Sender:TObject);
    procedure LoadSpritesFromCache(LoadThumbnails:boolean);
    procedure LoadSpritesFromDir(LoadThumbnails:boolean);
    procedure UpdateDisplay;
  public
    procedure MakeCache;
    procedure DeleteCache;
    procedure SaveThumbnails;
    property CurrentScript:DQE2_Script read FCurrentScript;
    property CurrentDialog:DQE2_Dialog read FCurrentDialog;
    property CurrentEntity:DQE2_Entity read FCurrentEntity;
    property RunningScripts:TList read FRunningScripts;
    property State:DQE2_WorldState read FState;
    property OffsetX:Integer read FOffsetX write FOffsetX;
    property OffsetY:Integer read FOffsetY write FOffsetY;
    property Device:TAsphyreDevice read FDevice;
    property Canvas:TAsphyreCanvas read FCanvas;
    property Objects:DQE2_List read FObjects;
    property Sprites:DQE2_List read FSprites;
    property Zones:DQE2_List read FZones;
    property Dialogs:DQE2_List read FDialogs;
    property Scripts:DQE2_List read FScripts;
    property Items:DQE2_List read FItems;
    property Fonts:DQE2_List read FFonts;
    property Effects:DQE2_List read FEffects;
    property EntityTypes:DQE2_List read FEntityTypes;
    property CurrentZone:DQE2_Zone read FCurrentZone write SetCurrentZone;
    function FindObjectInZone(ObjectName:String;ZoneName:string):DQE2_Object;overload;
    function FindObjectInZone(ObjectName:String;Zone:DQE2_Zone):DQE2_Object;overload;
    property ImageList:TStringList read FImageList;
    property Player:DQE2_Player read FPlayer;
    property MusicPlayer:TGamePlayer read FMusicPlayer;
    property ScreenWidth:word read FScreenWidth;
    property ScreenHeight:word read FScreenHeight;
    procedure SetResolution(Width,Height:word;Fullscreen:boolean=false);
    property OnVideoUpdate:DQE2_OnSimpleEvent read FOnVideoUpdate write FOnVideoUpdate;
    constructor Create(MainWindow:HWND;LoadThumnails:boolean);
    destructor Destroy;override;
    procedure LoadSkin(SkinName:string);
    procedure Step;
    procedure PutSprite(Obj:DQE2_Graphical);overload;
    procedure PutSprite(Image: TAsphyreImage;X,Y,W,H,CX,CY,Angle:Integer;Alpha,Color,DrawFX:Cardinal;FH,FV:boolean);overload;
    function PutEffectByName(Name:string;X,Y:SmallInt;Loops:byte;Pong:boolean):DQE2_Effect;
    function PutEffect(Sprite:DQE2_Sprite;X,Y:SmallInt;Loops:byte;Pong:boolean):DQE2_Effect;
    property OnStep:DQE2_OnSimpleEvent read FOnStep write FOnStep;
    property OnPreRender:DQE2_OnSimpleEvent read FPreRender write FPreRender;
    property OnPostRender:DQE2_OnSimpleEvent read FPostRender write FPostRender;
    property OnZoneChange:DQE2_OnZoneChange read FOnZoneChange write FOnZoneChange;
    property OnStateChange:DQE2_OnStateChange read FOnStateChange write FOnStateChange;
    property OnVarNew:DQE2_OnVarChange read FOnVarNew write FOnVarNew;
    property OnVarChange:DQE2_OnVarChange read FOnVarChange write FOnVarChange;
    property OnVarDelete:DQE2_OnVarChange read FOnVarDelete write FOnVarDelete;
    property OnBreakpoint:DQE2_ScriptBreakpoint read FOnBreakpoint write FOnBreakpoint;
    property KeyboardState:TKeyboardState read FKeyboardState;
    property JoyInfo:TJoyInfoEx read FJoyInfo;
    property JoystickFound:boolean read FJoystickFound;
    property JoystickEnabled:boolean read FJoystickEnabled write SetJoystickEnabled;
    property SharedDir:string read FSharedDir write FSharedDir;
    property KeyUp:boolean read FKeyUp write FKeyUp;
    property KeyDown:boolean read FKeyDown write FKeyDown;
    property KeyLeft:boolean read FKeyLeft write FKeyLeft;
    property KeyRight:boolean read FKeyRight write FKeyRight;
    property KeyFire:boolean read FKeyFire write FKeyFire;
    property KeyCancel:boolean read FKeyCancel write FKeyCancel;
    property KeyJump:boolean read FKeyJump write FKeyJump;
    property KeysReady:boolean read FKeysReady write FKeysReady;
    procedure Clear(SetDefaults:boolean);
    procedure LoadSprites(LoadThumbnails:boolean);
    procedure SaveToFile(FileName:string);
    procedure LoadFromFile(FileName:string;DoResume:boolean=false;ChangeShared:boolean=false);
    procedure Build(Dir:string;WorldName:string);
    procedure Play;
    procedure Pause;
    procedure Stop;
    procedure PackRunningScripts;
    procedure PackObjects(Zone:DQE2_Zone);
    procedure PackItems;
    procedure WriteVar(VarName:string;VarValue:Variant);
    function ReadVar(VarName:string):Variant;
    function VarExists(VarName:string):boolean;
    procedure DeleteVar(VarName:string);
    property SkinName:string read FSkinName;
    property VarNames[Index:integer]:string read GetVarNames;
    property VarValues[Index:integer]:string read GetVarValues;
    property VarCount:integer read FVarCount;
    property VideoOptions:DQE2_VideoOptions read FVideoOptions write SetVideoOptions;
    property ControlOptions:DQE2_ControlOptions read FControlOptions write SetControlOptions;
    property SkinOptions:DQE2_SkinOptions read FSkinOptions;
    property FontPersonName:DQE2_Font read F_PersonName write F_PersonName;
    property FontQuestion:DQE2_Font read F_Question write F_Question;
    property FontAnswer:DQE2_Font read F_Answer write F_Answer;
    property FontItemName:DQE2_Font read F_ItemName write F_ItemName;
    property FontItemText:DQE2_Font read F_ItemText write F_ItemText;
    property INI:TIniFile read FINI;
    property CanSave:boolean read GetCanSave;
    property SoundWalk:TGPMusic read FSoundWalk write FSoundWalk;
    property SoundJump:TGPMusic read FSoundJump write FSoundJump;
    property SoundWound:TGPMusic read FSoundWound write FSoundWound;
    property SoundDie:TGPMusic read FSoundDie write FSoundDie;
    procedure StopAllPlayerSounds;
    property UseCache:boolean read FUseCache write SetUseCache;
    property GameSpeed:byte read GetGameSpeed write SetGameSpeed;
    property KeyPressed[Index:Integer]:boolean read GetKeyPressed;
end;

var World:DQE2_World;
const dqe2version=4;
const dqe2versionStr='0.3';

const
    FuncRunName='Init';
    FuncStepName='Step';
    FuncUseName='OnUse';
    FuncPreRenderName='OnPreRender';
    FuncPostRenderName='OnPostRender';

    FuncPrepareName='Prepare';
    FuncAnswerName='OnAnswer';

    FuncDieName='OnDie';

    WorldCacheFileName='Objects\thumbs.cache';
    PlayerSpriteDefaultCategory='Dizzy';
    PlayerSpriteDefaultFilename='dizzy.gif';

    DefaultScreenWidth=640;
    DefaultScreenHeight=480;

    MaxBagItems=9;
    MaxDialogAnswers=5;
    DialogScrollEdge=MaxDialogAnswers div 2;

procedure AddAnswer(Num:Integer);

implementation

uses IMP_DQE2_Types;

var ZeroPattern:TTexCoord;

const DAnimSpeed=40;Dspeed=4;Dair=20;Dairspeed=4;
      DFrame:array[0..6]of array[0..1]of byte=
      (// animation sequences
      (0,2),  // (0) - Stand
      (2,10), // (1) - Right
      (10,18),// (2) - Left
      (18,26),// (3) - Jump up
      (26,34),// (4) - Jump right
      (34,42),// (5) - Jump left
      (42,45) // (6) - DIE!!!!  :)
      );
      PiDiv180=pi/180;
      DRect:trect=(left:12;top:0;right:35;bottom:42);
      DClimb=15;

type DQE2_ScriptCallbackData=record Vars,Values:TStrings;end;

// global functions
function ThisScript:DQE2_Script;
begin
  result:=World.FCurrentScript;
end;

function ThisDialog:DQE2_Dialog;
begin
  result:=World.FCurrentDialog;
end;

function ThisEntity:DQE2_Entity;
begin
  result:=World.FCurrentEntity;
end;

procedure WriteVar(VarName:string;VarValue:Variant);
begin
  world.WriteVar(VarName,VarValue);
end;

function ReadVar(VarName:string):Variant;
begin
  result:=World.ReadVar(VarName);
end;

function VarExists(VarName:string):boolean;
begin
  result:=world.VarExists(VarName);
end;

procedure DeleteVar(VarName:string);
begin
  world.DeleteVar(VarName);
end;

procedure AddAnswer(Num:Integer);
begin
  if world.CurrentDialog<>nil then
    if(Num>=0)and(Num<World.CurrentDialog.AnswersCount)then
      World.CurrentDialog.FCurAnswers.Add(world.CurrentDialog.FAnswerTitles[Num]);
end;

function FindObject(Name:string):DQE2_Object;
begin
  result:=DQE2_Object(World.Objects.FindByName(Name));
end;

function FindObjectInZoneName(ObjectName:string;ZoneName:string):DQE2_Object;
begin
  result:=world.FindObjectInZone(ObjectName,ZoneName);
end;

function FindObjectInZone(ObjectName:string;Zone:DQE2_Zone):DQE2_Object;
begin
  result:=world.FindObjectInZone(ObjectName,Zone);
end;

function FindScript(Name:string):DQE2_Script;
begin
  result:=DQE2_Script(World.Scripts.FindByName(Name));
end;

function FindZone(Name:string):DQE2_Zone;
begin
  result:=DQE2_Zone(World.Zones.FindByName(Name));
end;

function FindDialog(Name:string):DQE2_Dialog;
begin
  result:=DQE2_Dialog(World.Dialogs.FindByName(Name));
end;

function FindItem(Name:string):DQE2_Item;
begin
  result:=DQE2_Item(World.Items.FindByName(Name));
end;

function FindFont(Name:string):DQE2_Font;
begin
  result:=DQE2_Font(World.Fonts.FindByName(Name));
end;

function FindSprite(Name:string):DQE2_Sprite;
begin
  result:=DQE2_Sprite(World.Sprites.FindByName(Name));
end;

function FindEffect(Name:string):DQE2_Effect;
begin
  result:=DQE2_Effect(World.Effects.FindByName(Name));
end;

function FindEntity(Name:string):DQE2_Entity;
begin
  result:=DQE2_Entity(World.Objects.FindByName(Name));
  if not(result is DQE2_Entity) then
    result:=nil;
end;

function FindEntityType(Name:string):DQE2_EntityType;
begin
  result:=DQE2_EntityType(World.EntityTypes.FindByName(Name));
end;

// canvas functions
procedure SetPixel(x,y:Integer;Color:Cardinal);
begin
  world.Canvas.Pixels[x,y]:=Color;
end;

procedure Line(x0,y0,x1,y1:Single;Color0,Color1:Cardinal;DrawFx:Cardinal);
begin
  world.Canvas.Line(x0,y0,x1,y1,Color0,Color1,DrawFX);
end;

procedure FrameRect(Left,Top,Width,Height:Integer;Color:Cardinal;DrawFx:Cardinal);
begin
  world.Canvas.FrameRect(Left,Top,Width,Height,Color,DrawFX);
end;

procedure FillRect(Left,Top,Width,Height:Integer;Color:Cardinal;DrawFx:Cardinal);
begin
  world.Canvas.FillRect(Left,Top,Width,Height,Color,DrawFX);
end;

procedure Circle(Xpos,Ypos,Radius:Real;Color:Cardinal;DrawFx:Cardinal);
begin
  world.Canvas.Circle(Xpos,Ypos,Radius,Color,DrawFX);;
end;

procedure FillCircle(Xpos,Ypos,Radius:Integer;Color:Cardinal;DrawFx:Cardinal);
begin
  world.Canvas.FillCircle(Xpos,Ypos,Radius,cColor4(Color),DrawFX);
end;

procedure DrawSprite(Sprite:DQE2_Sprite;
                     X:Integer;Y:Integer;CX:Integer=0;CY:Integer=0;Angle:Integer=0;
                     Alpha:Cardinal=255;Color:Cardinal=$FFFFFF;DrawFX:Cardinal=fxBlend;
                     FH:boolean=false;FV:boolean=false;
                     AnimFrame:Word=0);
begin
  World.PutSprite(Sprite.Images.Item[AnimFrame],X,Y,Sprite.Width,Sprite.Height,
                  CX,CY,Angle,Alpha,Color,DrawFX,FH,FV);
end;

//
procedure ScriptReg;
begin
  RegisterRoutine('function ThisScript:DQE2_Script;',@ThisScript);
  RegisterRoutine('function ThisDialog:DQE2_Dialog;',@ThisDialog);
  RegisterRoutine('function ThisEntity:DQE2_Entity;',@ThisEntity);
  RegisterRoutine('procedure DeleteVar(VarName:string);',@DeleteVar);
  RegisterRoutine('function VarExists(VarName:string):boolean;',@VarExists);
  RegisterRoutine('function ReadVar(VarName:string):Variant;',@ReadVar);
  RegisterRoutine('procedure WriteVar(VarName:string;VarValue:Variant);',@WriteVar);
  RegisterRoutine('procedure AddAnswer(Num:Integer);',@AddAnswer);
  RegisterRoutine('function FindObject(Name:string):DQE2_Object;',@FindObject);
  RegisterRoutine('function FindObjectInZoneName(ObjectName:string;ZoneName:string):DQE2_Object;',@FindObjectInZoneName);
  RegisterRoutine('function FindObjectInZone(ObjectName:string;Zone:DQE2_Zone):DQE2_Object;',@FindObjectInZone);
  RegisterRoutine('function FindScript(Name:string):DQE2_Script;',@FindScript);
  RegisterRoutine('function FindItem(Name:string):DQE2_Item;',@FindItem);
  RegisterRoutine('function FindDialog(Name:string):DQE2_Dialog;',@FindDialog);
  RegisterRoutine('function FindZone(Name:string):DQE2_Zone;',@FindZone);
  RegisterRoutine('function FindFont(Name:string):DQE2_Font;',@FindFont);
  RegisterRoutine('function FindSprite(Name:string):DQE2_Sprite;',@FindSprite);
  RegisterRoutine('function FindEffect(Name:string):DQE2_Effect;',@FindEffect);
  RegisterClassType(TAsphyreCanvas);
  RegisterMethod(TAsphyreCanvas,'procedure SetPixel(x,y:Integer;Color:Cardinal);',@DQE2_types.SetPixel,true);
  RegisterMethod(TAsphyreCanvas,'procedure Line(x0,y0,x1,y1:Single;Color0,Color1:Cardinal;DrawFx:Cardinal);',@DQE2_types.Line,true);
  RegisterMethod(TAsphyreCanvas,'procedure FrameRect(Left,Top,Width,Height:Integer;Color:Cardinal;DrawFx:Cardinal);',@DQE2_types.FrameRect,true);
  RegisterMethod(TAsphyreCanvas,'procedure FillRect(Left,Top,Width,Height:Integer;Color:Cardinal;DrawFx:Cardinal);',@DQE2_types.FillRect,true);
  RegisterMethod(TAsphyreCanvas,'procedure Circle(Xpos,Ypos,Radius:Real;Color:Cardinal;DrawFx:Cardinal);',@DQE2_types.Circle,true);
  RegisterMethod(TAsphyreCanvas,'procedure FillCircle(Xpos,Ypos,Radius:Integer;Color:Cardinal;DrawFx:Cardinal);',@DQE2_types.FillCircle,true);
  RegisterMethod(TAsphyreCanvas,'procedure DrawSprite(Sprite:DQE2_Sprite;X:Integer;Y:Integer;CX:Integer=0;CY:Integer=0;Angle:Integer=0;Alpha:Cardinal=255;Color:Cardinal=$FFFFFF;DrawFX:Cardinal=fxBlend;FH:boolean=false;FV:boolean=false;AnimFrame:Word=0);',@DQE2_types.DrawSprite,true);
end;

procedure SetWorkDirectory;
begin
  world.SharedDir:=ExpandFileName(world.SharedDir);
  SetCurrentDir(world.SharedDir);
end;

procedure WordWrapText(Font:TAsphyreFontAlt;Txt:string;MaxWidth:integer;Strings:TStringList);
var
  s,t,w,cur:string;
  p,m:integer;
begin
  Strings.Clear;
  t:=StringReplace(txt,#13#10,#13,[rfReplaceAll]);
  m:=length(txt);p:=1;w:='';cur:='';
  while p<=m do
  begin
    s:=t[p];inc(p);
    if(s=' ')or(p>m)or(s=#13) then
    begin
      if(Font.TextWidth(cur+' '+w)<=MaxWidth)then
        begin cur:=cur+' '+w;end
      else
        begin Strings.Add(cur);cur:=w;end;
      if(cur<>'')and(p>m)then begin Strings.Add(cur+s);cur:='';end;
      if s=#13 then begin Strings.Add(cur);cur:='';end;
      w:='';
    end else w:=w+s;
  end;
end;

procedure FreeAndNil(var Obj);
begin
  try
    TObject(obj).Free;
  finally
    Pointer(obj):=nil;
  end;
end;

function GetPos(X,Y,W,H,CX,CY,A:Integer;FH,FV:boolean):TPoint4;
var
  CurFlip:TPoint4;
  SinVal,CosVal:Extended;
  dx,dy:Single;
  _cx,_cy:Integer;
begin
  if FH then
  begin
    CurFlip[0].y:=Y+H;
    CurFlip[1].y:=Y+H;
    CurFlip[2].y:=Y;
    CurFlip[3].y:=Y;
    _cy:=CY-H;
  end
  else
  begin
    CurFlip[0].y:=Y;
    CurFlip[1].y:=Y;
    CurFlip[2].y:=Y+H;
    CurFlip[3].y:=Y+H;
    _cy:=CY;
  end;
  if FV then
  begin
    CurFlip[0].x:=X+W;
    CurFlip[1].x:=X;
    CurFlip[2].x:=X;
    CurFlip[3].x:=X+W;
    _cx:=CX-W;
  end
  else
  begin
    CurFlip[0].x:=X;
    CurFlip[1].x:=X+W;
    CurFlip[2].x:=X+W;
    CurFlip[3].x:=X;
    _cx:=CX;
  end;
  if A=0 then
  begin
    result:=CurFlip;
  end
  else
  begin
    SinVal:=_sin[A];CosVal:=_cos[A];
    dx:=CurFlip[0].x+_CX;dy:=CurFlip[0].y+_CY;
    CurFlip[0].x:=CurFlip[0].x-dx;CurFlip[0].y:=CurFlip[0].y-dy;
    CurFlip[1].x:=CurFlip[1].x-dx;CurFlip[1].y:=CurFlip[1].y-dy;
    CurFlip[2].x:=CurFlip[2].x-dx;CurFlip[2].y:=CurFlip[2].y-dy;
    CurFlip[3].x:=CurFlip[3].x-dx;CurFlip[3].y:=CurFlip[3].y-dy;
    result[0].x:=(CurFlip[0].x*CosVal)-(CurFlip[0].y*SinVal)+dx;
    result[0].y:=(CurFlip[0].y*CosVal)+(CurFlip[0].x*SinVal)+dy;
    result[1].x:=(CurFlip[1].x*CosVal)-(CurFlip[1].y*SinVal)+dx;
    result[1].y:=(CurFlip[1].y*CosVal)+(CurFlip[1].x*SinVal)+dy;
    result[2].x:=(CurFlip[2].x*CosVal)-(CurFlip[2].y*SinVal)+dx;
    result[2].y:=(CurFlip[2].y*CosVal)+(CurFlip[2].x*SinVal)+dy;
    result[3].x:=(CurFlip[3].x*CosVal)-(CurFlip[3].y*SinVal)+dx;
    result[3].y:=(CurFlip[3].y*CosVal)+(CurFlip[3].x*SinVal)+dy;
  end;
end;

function GetColor(C,A:Cardinal):TColor4;
begin
  result:=cColor4(C or ((A and $FF) shl 24));
end;

{ DQE2_Object }

procedure DQE2_Object.AssignTo(Dest: TPersistent);
var
  x:DQE2_Object;
begin
  inherited;
  if not(Dest is DQE2_Object) then exit;
  x:=Dest as DQE2_Object;
  x.FCollider:=FCollider;
  x.FDamage:=FDamage;
  x.FMarked:=FMarked;
end;

procedure DQE2_Object.BringToFront;
var
  a,n:integer;
begin
  n:=FZone.Objects.Count-1;
  if FInZoneIndex<n then
  begin
    for a:=FInZoneIndex+1 to n do
    begin
      dqe2_object(FZone.FObjects[a]).FInZoneIndex:=a-1;
      FZone.FObjects.Exchange(a-1,a);
    end;
    FInZoneIndex:=n;
  end;
end;

constructor DQE2_Object.Create(Collection: TCollection;Zone:DQE2_Zone);
begin
  inherited Create(Collection);
  FDamage:=0;FCollider:=false;
  FMarked:=false;FZone:=Zone;
  if FZone<>nil then
  begin
    FZone.Objects.Add(Self);
    FInZoneIndex:=FZone.Objects.Count-1;
  end
  else
    FInZoneIndex:=-1;    
  SaveState;
end;

destructor DQE2_Object.Destroy;
begin
  if FInZoneIndex>=0 then FZone.Objects[FInZoneIndex]:=nil;
  inherited;
end;

procedure DQE2_Object.LoadState;
begin
  inherited;
  FCollider:=boolean(FState.ReservedInt1);
  FDamage:=FState.ReservedInt2;
end;

procedure DQE2_Object.OnSpriteChange(Value: DQE2_Sprite);
begin
  inherited;
  FCollider:=Value.DefaultCollider;
end;

procedure DQE2_Object.SaveState;
begin
  inherited;
  FState.ReservedInt1:=integer(FCollider);
  FState.ReservedInt2:=FDamage;
end;

procedure DQE2_Object.SendToBack;
var
  a:integer;
begin
  if FInZoneIndex>0 then
  begin
    for a:=FInZoneIndex-1 downto 0 do
    begin
      dqe2_object(FZone.FObjects[a]).FInZoneIndex:=a+1;
      FZone.FObjects.Exchange(a+1,a);
    end;
    FInZoneIndex:=0;
  end;
end;

procedure DQE2_Object.Step;
begin
  inherited;
  DoAnimate;
  world.PutSprite(Self);
end;

function DQE2_Object.UpgradeToEntity(TheType:DQE2_EntityType): DQE2_Entity;
begin
  Collection:=nil;
  result:=DQE2_Entity.Create(world.Objects,nil,TheType);
  FZone.Objects[FInZoneIndex]:=result;
  result.FZone:=FZone;result.FInZoneIndex:=FInZoneIndex;
  result.FName:=FName;
  AssignTo(result);
  FInZoneIndex:=-1;
  Free;
end;

{ DQE2_List }

function DQE2_List.Add: DQE2_Dummy;
begin
  result:=inherited Add as DQE2_Dummy;
end;

constructor DQE2_List.Create(ItemClass: TCollectionItemClass);
begin
  inherited;
end;

function DQE2_List.FindByName(Name: string): DQE2_Dummy;
var
  a:integer;
  {$IFDEF NAMEHASH}hk:Cardinal;{$ENDIF}
begin
  result:=nil;
  {$IFDEF NAMEHASH}hk:=DQE2_Dummy.ComputeHashKey(Name);{$ENDIF}
  for a:=0 to Count-1 do
    {$IFDEF NAMEHASH}
      if DQE2_Dummy(Items[a]).FNameHashKey=hk then
    {$ELSE}
      if AnsiStrIComp(PAnsiChar(DQE2_Dummy(Items[a]).Name),PAnsiChar(Name))=0 then
    {$ENDIF}
        begin result:=DQE2_Dummy(Items[a]);break;end;
end;

{ DQE2_Dummy }

{$IFDEF NAMEHASH}
class function DQE2_Dummy.ComputeHashKey(str: string): Cardinal;
var
   i, n : Integer;
begin
  n:=Length(str);
  Result:=n;
  for i:=1 to n do
    Result:=(Result shl 1)+Byte(str[i]);
end;
{$ENDIF}

procedure DQE2_Dummy.Assign(Src: TPersistent);
begin
  if src is DQE2_Dummy then DQE2_Dummy(Src).AssignTo(Self);
end;

procedure DQE2_Dummy.AssignTo(Dest: TPersistent);
var
  x:DQE2_Dummy;
begin
  //inherited;
  if not(Dest is DQE2_Dummy) then exit;
  x:=Dest as DQE2_Dummy;
  //x.FName:=FName;
  x.FTagData:=FTagData;
  x.FTag:=FTag;
  x.FTagFloat:=FTagFloat;
end;

constructor DQE2_Dummy.Create(Collection: TCollection);
begin
  inherited;
  Name:='';FTagData:=nil;FTag:=0;FTagFloat:=0;
end;

constructor DQE2_Dummy.CreateWithoutName(Collection: TCollection);
begin
  inherited Create(Collection);
  Fname:='';FTagData:=nil;FTag:=0;FTagFloat:=0;
end;

procedure DQE2_Dummy.SetName(const Value: String);
var
  a,n,p:integer;
  s,v:string;
  c:char;
  tmp:DQE2_Dummy;
const
  openb='([{';
  closeb=')]}';
begin
  if collection<>nil then
  begin
    a:=1;
    n:=length(value);
    if n=0 then
      v:=copy(ClassName,6,length(ClassName)-5)
    else
    begin
      v:=Value;
      tmp:=DQE2_List(Collection).FindByName(v);
      if(tmp<>nil)and(tmp<>Self)then
      begin
        if n>2 then
        begin
          p:=pos(v[n],closeb);
          if p>0 then
          begin
            s:='';c:=openb[p];dec(n);
            while n>0 do
            begin
              if(v[n]=c)then
              begin
                try
                  a:=StrToInt(s);
                  SetLength(v,n-1);
                except
                end;
                break;
              end;
              s:=v[n]+s;dec(n);
            end;
          end;
        end;
      end;
    end;
    s:=v;
    tmp:=DQE2_List(Collection).FindByName(v);
    while (tmp<>nil)and(tmp<>Self)do
    begin
      inc(a);s:=v+'('+inttostr(a)+')';
      tmp:=DQE2_List(Collection).FindByName(s);
    end;
    FName := s;
  end else FName:=Value;
  {$IFDEF NAMEHASH}FNameHashKey:=ComputeHashKey(FName);{$ENDIF}
end;

{ DQE2_World }

procedure DQE2_World.Build(Dir: string;WorldName:string);
var
  s,s1,s2:string;
  a,n:integer;
  SList:TStringList;
  sc:DQE2_Script;
  di:DQE2_Dialog;
  sp:DQE2_Sprite;
begin
  SetWorkDirectory;
  for a:=0 to FScripts.Count-1 do
  begin
    sc:=DQE2_Script(FScripts[a]);
    sc.FChanged:=true;
    if sc.UseScriptText then if not sc.Compile then exit;
  end;
  for a:=0 to FDialogs.Count-1 do
  begin
    di:=DQE2_Dialog(FDialogs[a]);
    di.FChanged:=true;
    if di.UseScriptText then if not di.Compile then exit;
  end;
  ForceDirectories(dir+'\Quest');
  SaveToFile(dir+'\Quest\'+WorldName+'.dqe');
  dir:=dir+'\Shared';
  ForceDirectories(dir);
  SList:=TStringList.Create;
  //n:=GetUsedSprites(SList);
  //n:=0;
  for a:=0 to FObjects.Count-1 do
  begin
    sp:=DQE2_Object(Fobjects[a]).Sprite;
    if sp<>nil then
      if SList.IndexOfObject(sp)=-1 then
        SList.AddObject(sp.Name,sp);
  end;
  for a:=0 to FItems.Count-1 do
  begin
    sp:=DQE2_Item(FItems[a]).Sprite;
    if sp<>nil then
      if SList.IndexOfObject(sp)=-1 then
        SList.AddObject(sp.Name,sp);
  end;
  sp:=FPlayer.Sprite;
  if sp<>nil then
   if SList.IndexOfObject(sp)=-1 then
      SList.AddObject(sp.Name,sp);
  n:=SList.Count;
  {TODO: bug with DQE2_Sprite.TimesUsed counter}
  if n>0 then
  begin
    CreateDir(dir+'\Objects');
    s1:='';
    for a:=0 to n-1 do
    begin
      s:=dir+'\'+SList.Strings[a];
      s2:=ExtractFileDir(s);
      if(s2<>s1)then
      begin
        CreateDir(s2);
        CopyFile(PAnsiChar(ExtractFileDir(SList.Strings[a])+'\objects.ini'),PAnsiChar(s2+'\objects.ini'),false);
        s1:=s2;
      end;
      CopyFile(PAnsiChar(SList.Strings[a]),PAnsiChar(s),false);
    end;
  end;
  if(FMusicPlayer.MusicCount>1)or(FMusicPlayer.PlayList.Count>0) then
  begin
    CreateDir(dir+'\Music');
    for a:=0 to FMusicPlayer.PlayList.Count-1 do
    begin
      s:=ExtractFileName(FMusicPlayer.PlayList.Files[a]);
      CopyFile(PAnsiChar('Music\'+s),PAnsiChar(dir+'\Music\'+s),false);
    end;
    for a:=1 to FMusicPlayer.MusicCount-1 do
    begin
      s:=ExtractFileName(FMusicPlayer.Musics[a].FileName);
      CopyFile(PAnsiChar('Music\'+s),PAnsiChar(dir+'\Music\'+s),false);
    end;
  end;
  s:=dir+'\'+ExtractFileDir(WS_Answer.Name);
  ForceDirectories(s);
  CopyFile(PAnsiChar(FSkinOptions.Answer)       ,PAnsiChar(dir+'\'+FSkinOptions.Answer),false);
  CopyFile(PAnsiChar(FSkinOptions.Question)     ,PAnsiChar(dir+'\'+FSkinOptions.Question),false);
  CopyFile(PAnsiChar(FSkinOptions.Bag)          ,PAnsiChar(dir+'\'+FSkinOptions.Bag),false);
  CopyFile(PAnsiChar(FSkinOptions.Cancel)       ,PAnsiChar(dir+'\'+FSkinOptions.Cancel),false);
  CopyFile(PAnsiChar(FSkinOptions.Head)         ,PAnsiChar(dir+'\'+FSkinOptions.Head),false);
  CopyFile(PAnsiChar(FSkinOptions.Top)          ,PAnsiChar(dir+'\'+FSkinOptions.Top),false);
  CopyFile(PAnsiChar(FSkinOptions.Bottom)       ,PAnsiChar(dir+'\'+FSkinOptions.Bottom),false);
  CopyFile(PAnsiChar(FSkinOptions.Left)         ,PAnsiChar(dir+'\'+FSkinOptions.Left),false);
  CopyFile(PAnsiChar(FSkinOptions.Right)        ,PAnsiChar(dir+'\'+FSkinOptions.Right),false);
  CopyFile(PAnsiChar(FSkinOptions.LifeSprite)   ,PAnsiChar(dir+'\'+FSkinOptions.LifeSprite),false);
  CopyFile(PAnsiChar(FSkinOptions.EnergySprite) ,PAnsiChar(dir+'\'+FSkinOptions.EnergySprite),false);
  s:='Skins\'+ExtractFileName(s);
  CopyFile(PAnsiChar(s+'\skin.ini'),PAnsiChar(dir+'\'+s+'\skin.ini'),false);
  FreeAndNil(SList);
end;

procedure DQE2_World.SetResolution(Width, Height: word;Fullscreen:boolean=false);
begin
  if(FScreenWidth<>Width)or(FScreenHeight<>Height)or(FFullscreen<>Fullscreen)then
  begin
    FScreenWidth:=Width;FScreenHeight:=Height;FFullScreen:=Fullscreen;
    UpdateDisplay;
  end;
end;

procedure DQE2_World.Clear(SetDefaults:boolean);
var
  a:integer;
begin
  Stop;
  FObjects.Clear;
  FScripts.Clear;
  FZones.Clear;
  FDialogs.Clear;
  FItems.Clear;
  FFonts.Clear;
  FEffects.Clear;
  FEntityTypes.Clear;
  FVarNames.Clear;
  FVarValues.Clear;
  FVarCount:=0;
  FPlayer.Bag.Items.Clear;
  FRunningScripts.Clear;
  FRunningMusic.Clear;
  FMusicPlayer.PlayList.Clear;
  for a:=FMusicPlayer.MusicCount-1 downto 1 do
    FMusicPlayer.Musics[a].Free;
  FMusicPlayer.Music.Close;
  FSoundWalk:=nil;FSoundJump:=nil;FSoundWound:=nil;FSoundDie:=nil;
  FCurrentScript:=nil;FCurrentDialog:=nil;
  if SetDefaults then
  begin
    SetResolution(DefaultScreenWidth,DefaultScreenHeight);
    FCurrentZone:=DQE2_Zone.Create(FZones,0,0,FScreenWidth,FScreenHeight);
    FCurrentZone.Name:='Zone';
    FMusicPlayer.PlayList.Mode:=pmLoopAll;
    FPlayer.FLastZone:=FCurrentZone;
    FPlayer.FLastX:=0;
    FPlayer.FLastY:=0;
    FPlayer.FSaved:=false;
    // Fonts
    F_PersonName:=DQE2_Font.Create(World.FFonts);F_PersonName.Name:='Interlocutor''s name';
    F_PersonName.FFont.FontName:='Courier New';
    F_PersonName.FFont.Antialias:=true;
    F_PersonName.FFont.Style:=[febold,feShadow];
    F_PersonName.FFont.ShadowColor:=cRGB1(0,0,255);
    F_PersonName.FFont.Size:=30;
    F_PersonName.FFont.Update;
    F_Question:=DQE2_Font.Create(World.FFonts);F_Question.Name:='Question';
    F_Question.FFont.FontName:='Arial';
    F_Question.FFont.Antialias:=true;
    F_Question.FFont.Style:=[febold,feShadow];
    F_Question.FFont.ShadowColor:=cRGB1(255,0,0);
    F_Question.FFont.Size:=20;
    F_Question.FFont.Update;
    F_Answer:=DQE2_Font.Create(World.FFonts);F_Answer.Name:='Answer';
    F_Answer.FFont.FontName:='Arial';
    F_Answer.FFont.Antialias:=true;
    F_Answer.FFont.Style:=[febold,feShadow];
    F_Answer.FFont.ShadowColor:=cRGB1(255,255,255);
    F_Answer.FFont.Size:=20;
    F_Answer.FFont.Update;
    F_ItemName:=DQE2_Font.Create(World.FFonts);F_ItemName.Name:='Item''s name';
    F_ItemName.FFont.FontName:='Tahoma';
    F_ItemName.FFont.Antialias:=true;
    F_ItemName.FFont.Style:=[febold,feShadow];
    F_ItemName.FFont.ShadowColor:=cRGB1(0,0,255);
    F_ItemName.FFont.Size:=30;
    F_ItemName.FFont.Update;
    F_ItemText:=DQE2_Font.Create(World.FFonts);F_ItemText.Name:='Item''s description';
    F_ItemText.FFont.FontName:='Arial';
    F_ItemText.FFont.Antialias:=true;
    F_ItemText.FFont.Style:=[febold,feShadow];
    F_ItemText.FFont.ShadowColor:=cRGB1(0,64,64);
    F_ItemText.FFont.Size:=20;
    F_ItemText.FFont.Update;
  end;
end;

function GP_Create(data:pointer):DWORD;stdcall;
var
  info:JOYCAPSA;
begin
  World.FMusicPlayer:=TGamePlayer.Create(AppPath+'..\Shared\Lib');
  if joyGetNumDevs()>0 then
  begin
    ZeroMemory(@info,sizeof(info));
    if joyGetDevCapsA(JOYSTICKID1,@info,sizeof(info))=JOYERR_NOERROR then
    begin
      World.FJoystickFound:=true;
      World._joyxmin:=info.wXmin;World._joyxmax:=info.wXmax;
      World._joyymin:=info.wYmin;World._joyymax:=info.wYmax;
    end
    else
      World.FJoystickFound:=false;
  end
  else
    World.FJoystickFound:=false;
  result:=0;
end;

constructor DQE2_World.Create(MainWindow:HWND;LoadThumnails:boolean);
var
  tmp,tmp2:Cardinal;
begin
  ShowWindow(Application.Handle,SW_HIDE);
  inherited Create;
  World:=Self;Randomize;
  Win:=MainWindow;
  //
  FLoadThumbnails:=LoadThumnails;
  FINI:=TIniFile.Create(AppPath+'..\Shared\data\options.ini');
  if ParamCount=0 then
    FSharedDir:=AppPath+'..\Shared'
  else
    FSharedDir:=ExtractFileDir(ParamStr(1))+'\..\Shared';
  SetWorkDirectory;FState:=wsStop;
  ZeroPattern:=tPattern(0);
  // Lists
  FObjects:=DQE2_List.Create(DQE2_Object);FObjects.FFriendlyClassName:='Object';
  FSprites:=DQE2_List.Create(DQE2_Sprite);FSprites.FFriendlyClassName:='Sprite';
  FZones:=DQE2_List.Create(DQE2_Zone);FZones.FFriendlyClassName:='Zone';
  FDialogs:=DQE2_List.Create(DQE2_Dialog);FDialogs.FFriendlyClassName:='Dialog';
  FScripts:=DQE2_List.Create(DQE2_Script);FScripts.FFriendlyClassName:='Script';
  FItems:=DQE2_List.Create(DQE2_Item);FItems.FFriendlyClassName:='Item';
  FFonts:=DQE2_List.Create(DQE2_Font);FFonts.FFriendlyClassName:='Font';
  FEffects:=DQE2_List.Create(DQE2_Effect);FEffects.FFriendlyClassName:='Effect';
  FEntityTypes:=DQE2_List.Create(DQE2_EntityType);FEntityTypes.FFriendlyClassName:='Entity type';
  FImageList:=TStringList.Create;
  FOffsetX:=0;FOffsetY:=0;
  FPreRender:=nil;FPostRender:=nil;
  FOnZoneChange:=nil;
  // interface
  W_Question:=DQE2_Graphical.Create(nil);
  W_Answer:=DQE2_Graphical.Create(nil);
  W_Cancel:=DQE2_Graphical.Create(nil);
  WS_Answer:=DQE2_Sprite.Create(nil);
  WS_Question:=DQE2_Sprite.Create(nil);
  WS_Bag:=DQE2_Sprite.Create(nil);
  WS_Cancel:=DQE2_Sprite.Create(nil);
  // DirectX
  FScreenWidth:=0;FScreenHeight:=0;FDevice:=nil;FCanvas:=nil;FFullScreen:=false;
  SetResolution(DefaultScreenWidth,DefaultScreenHeight);
  // start gp init in other thread for speedup on multicore systems
  tmp:=CreateThread(nil,0,@GP_Create,nil,0,tmp2);
  //
  FUseCache:=FINI.ReadBool('World.OtherOptions','UseCache',false);
  LoadSprites(LoadThumnails);
  FPlayer:=DQE2_Player.Create;
  LoadSkin('default');
  // controls
  FControlOptions.KeyLeft:=FINI.ReadInteger('World.ControlOptions','KeyLeft',37);
  FControlOptions.KeyRight:=FINI.ReadInteger('World.ControlOptions','KeyRight',39);
  FControlOptions.KeyUp:=FINI.ReadInteger('World.ControlOptions','KeyUp',38);
  FControlOptions.KeyDown:=FINI.ReadInteger('World.ControlOptions','KeyDown',40);
  FControlOptions.KeyFire:=FINI.ReadInteger('World.ControlOptions','KeyFire',13);
  FControlOptions.KeyCancel:=FINI.ReadInteger('World.ControlOptions','KeyCancel',27);
  FControlOptions.JoyFire:=FINI.ReadInteger('World.ControlOptions','JoyFire',8);
  FControlOptions.JoyCancel:=FINI.ReadInteger('World.ControlOptions','JoyCancel',1);
  FControlOptions.JoyJump:=FINI.ReadInteger('World.ControlOptions','JoyJump',4);
  FControlOptions.JoyXasY:=FINI.ReadBool('World.ControlOptions','JoyXasY',false);
  FControlOptions.JoyBtnForJump:=FINI.ReadBool('World.ControlOptions','JoyBtnForJump',true);
  // scripts
  FScriptLang:=TPaxPascal.Create(nil);
  FVarNames:=TStringList.Create;FVarNames.CaseSensitive:=false;
  FVarValues:=TStringList.Create;FVarValues.CaseSensitive:=false;
  FVarCount:=0;
  FRunningScripts:=TList.Create;
  FRunningMusic:=TList.Create;
  ScriptReg;
  // wait for musicplayer init
  WaitForSingleObject(tmp,INFINITE);
  CloseHandle(tmp);
  // others
  SetJoystickEnabled(FINI.ReadBool('World.ControlOptions','JoyEnabled',false));
  FOnBreakpoint:=nil;
  FontStore:=TAsphyreFontsAlt.Create(nil);
  Clear(true);
  FJoyInfo.dwSize:=sizeof(FJoyInfo);
  FJoyInfo.dwFlags:=JOY_RETURNX or JOY_RETURNY or JOY_RETURNBUTTONS;
  // finally, run timer
  FTimer:=TJvTimer.Create(nil);
  FTimer.Threaded:=false;
  SetGameSpeed(FINI.ReadInteger('World.OtherOptions','GameSpeed',245));
  FTimer.ThreadPriority:=tpHigher;
  FTimer.SyncEvent:=true;
  FTimer.OnTimer:=_OnStep;
  FTimer.Enabled:=true;
  //
  ShowWindow(Application.Handle,SW_SHOW);
end;

procedure DQE2_World.DeleteCache;
var
  s:string;
begin
  s:=FSharedDir+'\'+WorldCacheFileName;
  if FileExists(s) then DeleteFile(s);
end;

procedure DQE2_World.DeleteVar(VarName: string);
var
  n:integer;
begin
  n:=FVarNames.IndexOf(VarName);
  if n>=0 then
  begin
    if Assigned(FOnVarDelete) then
      FOnVarDelete(FVarNames.Strings[n],FVarValues.Strings[n]);
    FVarNames.Delete(n);
    FVarValues.Delete(n);
    dec(FVarCount);
  end;
end;

function DQE2_World.VarExists(VarName: string): boolean;
begin
  result:=(FVarNames.IndexOf(VarName)>=0);
end;

procedure DQE2_World.WriteVar(VarName: string; VarValue: Variant);
var
  n:integer;
begin
  n:=FVarNames.IndexOf(VarName);
  if n>=0 then
  begin
    FVarValues.Strings[n]:=VarValue;
    if Assigned(FOnVarChange)then
      FOnVarChange(FVarNames[n],FVarValues[n]);
  end
  else
  begin
    n:=World.FVarNames.Add(VarName);
    world.FVarValues.Add(VarValue);
    inc(FVarCount);
    if Assigned(FOnVarNew)then
      FOnVarNew(FVarNames[n],FVarValues[n]);
  end;
end;

procedure DQE2_World._OnStep(Sender: TObject);
begin
  if Assigned(Self.FOnStep)then Self.FOnStep();
  Self.Step();
end;

function DQE2_World.ReadVar(VarName: string): Variant;
var
  n:integer;
begin
  n:=FVarNames.IndexOf(VarName);
  if n>=0 then
    result:=FVarValues.Strings[n]
  else
    result:='';
end;

destructor DQE2_World.Destroy;
var
  a:integer;
begin
  FreeAndNil(FTimer);
  Clear(false);
  FreeAndNil(FObjects);
  FreeAndNil(FSprites);
  FreeAndNil(FZones);
  FreeAndNil(FDialogs);
  FreeAndNil(FScripts);
  FreeAndNil(FItems);
  FreeAndNil(FFonts);
  FreeAndNil(FEffects);
  FreeAndNil(FEntityTypes);
  FreeAndNil(FCanvas);
  FreeAndNil(FDevice);
  for a:=0 to FImageList.Count-1 do
    TStringList(FImageList.Objects[a]).Free;
  FreeAndNil(FImageList);
  FreeAndNil(FMusicPlayer);
  FreeAndNil(FPlayer);
  FreeAndNil(FRunningScripts);
  FreeAndNil(FRunningMusic);
  FreeAndNil(W_Answer);
  FreeAndNil(W_Question);
  FreeAndNil(W_Cancel);
  FreeAndNil(WS_Answer);
  FreeAndNil(WS_Question);
  FreeAndNil(WS_Bag);
  FreeAndNil(WS_Cancel);
  FreeAndNil(FontStore);
  FreeAndNil(FVarNames);
  FreeAndNil(FVarValues);
  FreeAndNil(FINI);
  FreeAndNil(FScriptLang);
  //
  World:=nil;
  inherited;
end;

function DQE2_World.FindObjectInZone(ObjectName: String;
  Zone: DQE2_Zone): DQE2_Object;
var
  a,n:integer;
  c:PAnsiChar;
  tmp:DQE2_Object;
begin
  n:=Zone.Objects.Count;result:=nil;
  if n>0 then
  begin
    c:=PAnsiChar(ObjectName);
    for a:=0 to n-1 do
    begin
      tmp:=DQE2_Object(Zone.Objects[a]);
      if AnsiStrIComp(PAnsiChar(tmp.Name),c)=0 then
        begin result:=tmp;break;end;
    end;
  end;
end;

function DQE2_World.FindObjectInZone(ObjectName, ZoneName: string): DQE2_Object;
var
  zn:DQE2_Zone;
begin
  zn:=DQE2_Zone(FZones.FindByName(ZoneName));
  if zn=nil then
    result:=nil
  else
    result:=FindObjectInZone(ObjectName,zn);
end;

function DQE2_World.GetCanSave: boolean;
begin
  result:=(FKeysReady)and(FFadeSpeed=0)and
  (((FPlayer.FFootHold<>nil)and(not FPlayer.OnFly))or(not FPlayer.Control))
  and(FPlayer.FAnimNum=0)and(not FPlayer.Rolling)and
  (not FPlayer.FBag.Running)and(CurrentDialog=nil)and(FState=wsPlay);
end;

function DQE2_World.GetGameSpeed: byte;
begin
  result:=256-FTimer.Interval;
end;

function DQE2_World.GetKeyPressed(Index: Integer): boolean;
begin
  result:=(FKeyboardState[Index] and 128)<>0;
end;

function DQE2_World.GetUsedSprites(SPL: TStringList): integer;
var
  sp:dqe2_sprite;
  a:integer;
begin
  SPL.CaseSensitive:=false;
  SPL.Clear;
  {for a:=0 to FSprites.Count-1 do
  begin
    sp:=DQE2_Sprite(FSprites[a]);
    if sp.TimesUsed>0 then
      SPL.Add(sp.Name)
  end;}
  for a:=0 to FObjects.Count-1 do
  begin
    sp:=DQE2_Object(Fobjects[a]).Sprite;
    if sp<>nil then
      if SPL.IndexOfObject(sp)=-1 then
        SPL.AddObject(sp.Name,sp);
  end;
  for a:=0 to FItems.Count-1 do
  begin
    sp:=DQE2_Item(FItems[a]).Sprite;
    if sp<>nil then
      if SPL.IndexOfObject(sp)=-1 then
        SPL.AddObject(sp.Name,sp);
  end;
  if FPlayer<>nil then
  begin
    sp:=FPlayer.Sprite;
    if sp<>nil then
      if SPL.IndexOfObject(sp)=-1 then
        SPL.AddObject(sp.Name,sp);
  end;
  result:=SPL.Count;
end;

function DQE2_World.GetVarNames(Index: integer): string;
begin
  result:=FVarNames[Index];
end;

function DQE2_World.GetVarValues(Index: integer): string;
begin
  result:=FVarValues[Index];
end;

procedure DQE2_World.LoadSkin(SkinName: string);
var
  i:TINIFile;
  dir:string;
begin
  SetWorkDirectory;dir:='Skins\'+SkinName+'\';
  i:=TIniFile.Create(dir+'skin.ini');
  FSkinOptions.Answer:=dir+(i.ReadString('Windows','Answer','Answer.gif'));
  FSkinOptions.Bag:=dir+(i.ReadString('Windows','Bag','Bag.gif'));
  FSkinOptions.Question:=dir+(i.ReadString('Windows','Question','Question.gif'));
  FSkinOptions.Cancel:=dir+(i.ReadString('Windows','Cancel','Cancel.gif'));
  FSkinOptions.Head:=dir+i.ReadString('Borders','Head','_head.gif');
  FSkinOptions.Top:=dir+i.ReadString('Borders','Top','_top.gif');
  FSkinOptions.Bottom:=dir+i.ReadString('Borders','Bottom','_bottom.gif');
  FSkinOptions.Left:=dir+i.ReadString('Borders','Left','_left.gif');
  FSkinOptions.Right:=dir+i.ReadString('Borders','Right','_right.gif');
  FSkinOptions.EnergySprite:=dir+i.ReadString('Energy','Sprite','Energy.gif');
  FSkinOptions.EnergyRect.Left:=i.ReadInteger('Energy','Left',0);
  FSkinOptions.EnergyRect.Right:=i.ReadInteger('Energy','Right',0);
  FSkinOptions.EnergyRect.Top:=i.ReadInteger('Energy','Top',0);
  FSkinOptions.EnergyRect.Bottom:=i.ReadInteger('Energy','Bottom',0);
  FSkinOptions.LifeSprite:=dir+i.ReadString('Lifes','Sprite','Life.gif');
  FSkinOptions.LifesRect.Left:=i.ReadInteger('Lifes','Left',0);
  FSkinOptions.LifesRect.Right:=i.ReadInteger('Lifes','Right',0);
  FSkinOptions.LifesRect.Top:=i.ReadInteger('Lifes','Top',0);
  FSkinOptions.LifesRect.Bottom:=i.ReadInteger('Lifes','Bottom',0);
  FSkinOptions.ZoneRect.Left:=i.ReadInteger('Zone','Left',0);
  FSkinOptions.ZoneRect.Top:=i.ReadInteger('Zone','Top',0);
  FSkinOptions.ZoneRect.Right:=i.ReadInteger('Zone','Right',0);
  FSkinOptions.ZoneRect.Bottom:=i.ReadInteger('Zone','Bottom',0);
  FreeAndNil(i);
  WS_Answer.Name:=FSkinOptions.Answer;
  WS_Question.Name:=FSkinOptions.Question;
  WS_Bag.Name:=FSkinOptions.Bag;
  WS_Cancel.Name:=FSkinOptions.Cancel;
  WS_Cancel.FDefaultTransparent:=true;
  WS_Question.DefaultTransparent:=true;
  WS_Answer.DefaultTransparent:=true;
  WS_Bag.DefaultTransparent:=true;
  WS_Answer.Load;WS_Question.Load;WS_Bag.Load;WS_Cancel.Load;
  W_Answer.Sprite:=WS_Answer;
  W_Question.Sprite:=WS_Question;
  W_Cancel.Sprite:=WS_Cancel;
  FPlayer.Bag.Sprite:=WS_Bag;
  FSkinName:=SkinName;
end;

procedure DQE2_World.LoadSprites(LoadThumbnails:boolean);
var
  a:integer;
begin
  SetWorkDirectory;
  for a:=0 to FImageList.Count-1 do
    TStringList(FImageList.Objects[a]).Free;
  FImageList.Clear;
  if FUseCache and LoadThumbnails then
    if FileExists(WorldCacheFileName) then
      LoadSpritesFromCache(LoadThumbnails)
    else
    begin
      LoadSpritesFromDir(LoadThumbnails);
      MakeCache;
    end
  else
    LoadSpritesFromDir(LoadThumbnails);
end;

procedure DQE2_World.LoadSpritesFromCache(LoadThumbnails: boolean);
var
  f:TMemoryStreamEx;
  ff:TFileStreamEx;
  a,n,x,y:integer;
  ss,s:string;
  ini:TIniFile;
  spr:DQE2_Sprite;
  u:TStringList;
  bmp:TBitmap;
  c:TCanvas;
  playercat:boolean;
begin
  ff:=TFileStreamEx.Create(WorldCacheFileName,fmOpenRead);
  f:=TMemoryStreamEx.Create;
  f.CopyFrom(ff,0);
  ff.Free;
  f.Position:=0;
  n:=f.ReadInteger;
  playercat:=false;
  if n>0 then
  begin
    for a:=0 to n-1 do
    begin
      s:=f.ReadString;
      if s='' then
      begin
        ss:=f.ReadString;
        ini:=TIniFile.Create('Objects\'+ss+'\objects.ini');
        u:=TStringList.Create;
        u.CaseSensitive:=false;
        FImageList.AddObject(ss,u);
        s:=f.ReadString;
        if playercat=false then
          playercat:=(ss=PlayerSpriteDefaultCategory);
      end;
      spr:=DQE2_Sprite.Create(World.Sprites);
      spr.FINI:=ini;
      spr.FDefaultName:=f.ReadString;
      spr.FDefaultTransparent:=f.ReadBoolean;
      spr.FDefaultCollider:=f.ReadBoolean;
      spr.FDefaultAlpha:=f.ReadByte;
      spr.FDefaultAnimSpeed:=f.ReadShortint;
      spr.FHeight:=f.ReadWord;
      spr.FWidth:=f.ReadWord;
      spr.FName:='Objects\'+ss+'\'+s;
      spr.FShortName:=s;
      u.AddObject(spr.ShortName,spr);
      if FPlayerSprite=nil then
        if playercat then
          if s=PlayerSpriteDefaultFilename then
            FPlayerSprite:=spr;
    end;
    if LoadThumbnails then
      for a:=0 to n-1 do
      begin
        bmp:=TBitmap.Create;
        DQE2_Sprite(World.Sprites[a]).FThumbnail:=bmp;
        bmp.SetSize(50,50);
        c:=bmp.Canvas;
        for x:=0 to 49 do
          for y:=0 to 49 do
            c.Pixels[x,y]:=f.ReadInteger;
      end;
  end;
  f.Free;
end;

procedure DQE2_World.LoadSpritesFromDir(LoadThumbnails: boolean);
var
  f1,f2:TSearchRec;
  s,ext:string;
  i:DQE2_Sprite;
  ini:TIniFile;
  _tr,_col:boolean;
  _nam:string;
  _alp:byte;
  _spd:ShortInt;
  u:TStringList;
  playercat:boolean;
begin
  if FindFirst('Objects\*',faDirectory,f1)=0 then
  begin
    playercat:=false;
    repeat
      if(f1.Name<>'.')and(f1.Name<>'..')then
        if FindFirst('Objects\'+f1.Name+'\*',faAnyFile,f2)=0 then
        begin
          ini:=TIniFile.Create('Objects\'+f1.Name+'\Objects.ini');
          _nam:=ini.ReadString('Default','Name','Îáúåêò');
          _tr:=ini.ReadBool('Default','Transparent',false);
          _col:=ini.ReadBool('Default','Collider',false);
          _alp:=ini.ReadInteger('Default','Alpha',255);
          _spd:=ini.ReadInteger('Default','AnimSpeed',30);
          u:=TStringList.Create;
          u.CaseSensitive:=false;
          FImageList.AddObject(f1.Name,u);
          if playercat=false then
            playercat:=(f1.Name=PlayerSpriteDefaultCategory);
          repeat
            if(f2.Name<>'.')and(f2.Name<>'..')and((f2.Attr and faDirectory)=0)then
            begin
              s:='Objects\'+f1.Name+'\'+f2.Name;ext:=LowerCase(ExtractFileExt(s));
              if(ext='.bmp')or(ext='.jpg')or(ext='.jpeg')or(ext='.gif')or(ext='.png')or(ext='.apng')or(ext='.tga')then
              begin
                i:=DQE2_Sprite.Create(FSprites);
                i.FINI:=ini;i.Name:=s;
                i.FShortName:=ExtractFileName(i.Name);
                i.UpdateFromINI(_nam,_tr,_col,_spd,_alp);
                if LoadThumbnails then i.Prepare(s);
                u.AddObject(i.ShortName,i);
                if FPlayerSprite=nil then
                  if playercat then
                    if f2.Name=PlayerSpriteDefaultFilename then
                      FPlayerSprite:=i;
              end;
            end;
          until FindNext(f2)<>0;
          FindClose(f2);
        end;
    until FindNext(f1)<>0;
    FindClose(f1);
  end;
end;

procedure DQE2_World.MakeCache;
var
  a,x,y:integer;
  f:TFileStreamEx;
  bmp:TCanvas;
  spr:DQE2_Sprite;
  s,ss,sss:string;
begin
  SetWorkDirectory;
  f:=TFileStreamEx.Create('Objects\thumbs.cache',fmCreate);
  f.WriteInteger(FSprites.Count);
  if FSprites.Count>0 then
  begin
    ss:='';
    for a:=0 to FSprites.Count-1 do
    begin
      spr:=DQE2_Sprite(FSprites[a]);
      sss:=copy(spr.FName,9,length(spr.FName)-8);
      s:=ExtractFileDir(sss);
      if s<>ss then
      begin
        ss:=s;f.WriteChar(#0);f.WriteString(s);
      end;
      f.WriteString(ExtractFileName(sss));
      f.WriteString(spr.DefaultName);
      f.WriteBoolean(spr.DefaultTransparent);
      f.WriteBoolean(spr.DefaultCollider);
      f.WriteByte(spr.DefaultAlpha);
      f.WriteShortInt(spr.DefaultAnimSpeed);
      f.WriteWord(spr.Height);
      f.WriteWord(spr.Width);
    end;
    for a:=0 to FSprites.Count-1 do
    begin
      bmp:=DQE2_Sprite(FSprites[a]).Thumbnail.Canvas;
      for x:=0 to 49 do
        for y:=0 to 49 do
          f.WriteInteger(bmp.Pixels[x,y]);
    end;
  end;
  f.Free
end;

procedure DQE2_World.PackItems;
var
  a:integer;
begin
  Fplayer.Bag.Items.Pack;Fplayer.Bag.Items.Capacity:=Fplayer.Bag.Items.Count;
  for a:=0 to Fplayer.Bag.Items.Count-1 do
    dqe2_item(Fplayer.Bag.Items[a]).FInBagIndex:=a;
end;

procedure DQE2_World.PackObjects(Zone:DQE2_Zone);
var
  a:integer;
begin
  Zone.Objects.Pack;Zone.Objects.Capacity:=zone.Objects.Count;
  for a:=0 to zone.Objects.Count-1 do
    dqe2_object(zone.objects[a]).FInZoneIndex:=a;
end;

procedure DQE2_World.PackRunningScripts;
var
  a:integer;
begin
  for a:=0 to FRunningScripts.Count-1 do
    if not dqe2_script(FRunningScripts[a]).FRunning then
      FRunningScripts[a]:=nil;
  FRunningScripts.Pack;FRunningScripts.Capacity:=FRunningScripts.Count;
  for a:=0 to FRunningScripts.Count-1 do
    dqe2_script(FRunningScripts[a]).FIndexInList:=a;
end;

procedure DQE2_World.Pause;
var
  a:integer;
begin
  if Assigned(FOnStateChange)then FOnStateChange(wsPause);
  FState:=wsPause;
  for a:=0 to FMusicPlayer.MusicCount-1 do
    FMusicPlayer.Musics[a].Pause;
end;

procedure DQE2_World.Play;
var
  a:integer;
begin
  if Assigned(FOnStateChange)then FOnStateChange(wsPlay);
  for a:=0 to FMusicPlayer.MusicCount-1 do
    FMusicPlayer.Musics[a].UnPause;
  if FState=wsStop then
  begin
    CurrentZone:=CurrentZone;
    for a:=0 to FEntityTypes.Count-1 do
      DQE2_EntityType(FEntityTypes[a]).FCompiled:=false;
  end;
  FState:=wsPlay;
end;

function CorrectMatrix(mx:TPoint4):TPoint4;
begin
  result[0].x:=mx[0].x-world.OffsetX;
  result[0].y:=mx[0].y-world.OffsetY;
  result[1].x:=mx[1].x-world.OffsetX;
  result[1].y:=mx[1].y-world.OffsetY;
  result[2].x:=mx[2].x-world.OffsetX;
  result[2].y:=mx[2].y-world.OffsetY;
  result[3].x:=mx[3].x-world.OffsetX;
  result[3].y:=mx[3].y-world.OffsetY;
end;

function DQE2_World.PutEffect(Sprite: DQE2_Sprite; X, Y: SmallInt; Loops: byte;
  Pong: boolean): DQE2_Effect;
begin
  result:=DQE2_Effect.Create(FEffects);
  result.Sprite:=Sprite;
  result.X:=X;result.Y:=Y;result.FnLoops:=Loops;result.FPingPong:=Pong;
  result.Step;
end;

function DQE2_World.PutEffectByName(Name: string; X, Y: SmallInt; Loops: byte;
  Pong: boolean):DQE2_Effect;
var
  n:integer;
  str:TStringList;
begin
  n:=FImageList.IndexOf('Effects');if n=-1 then begin result:=nil;exit;end;
  str:=TStringList(FImageList.Objects[n]);
  n:=str.IndexOf(Name);if n=-1 then begin result:=nil;exit;end;
  result:=PutEffect(DQE2_Sprite(str.Objects[n]),X,Y,Loops,Pong);
end;

procedure DQE2_World.PutSprite(Image: TAsphyreImage; X, Y, W, H, CX, CY,
  Angle: Integer; Alpha,Color, DrawFX: Cardinal;FH,FV:boolean);
begin
  FCanvas.TexMap(Image,CorrectMatrix(GetPos(X,Y,W,H,CX,CY,Angle,FH,FV)),
  GetColor(Color,Alpha),
  ZeroPattern,DrawFX);
end;

procedure DQE2_World.PutSprite(Obj: DQE2_Graphical);
begin
  if obj.FVisible then
  FCanvas.TexMap(
    obj.FSprite.Images.Item[obj.FAnimFrame],
    CorrectMatrix(obj.FLocalMatrix),
    obj.CurColor,
    ZeroPattern,
    obj.DrawFX)
end;

procedure DQE2_World.SaveThumbnails;
var
  a,x,y:integer;
  f:TFileStreamEx;
  bmp:TCanvas;
  spr:DQE2_Sprite;
  s,ss,sss:string;
begin
  if FSprites.Count>0 then
  begin
    f:=TFileStreamEx.Create(SharedDir+'\Objects\thumbs.cache',fmCreate);
    ss:='';
    for a:=0 to FSprites.Count-1 do
    begin
      spr:=DQE2_Sprite(FSprites[a]);
      sss:=copy(spr.FName,9,length(spr.FName)-8);
      s:=ExtractFileDir(sss);
      if s<>ss then
      begin
        ss:=s;f.WriteString(sss);
      end
      else
        f.WriteString(ExtractFileName(sss));
      bmp:=spr.FThumbnail.Canvas;
      for x:=0 to 49 do
        for y:=0 to 49 do
          f.WriteInteger(bmp.Pixels[x,y]);
    end;
    f.Free
  end;
end;

procedure DQE2_World.SetControlOptions(const Value: DQE2_ControlOptions);
begin
  FControlOptions := Value;
  FINI.WriteInteger('World.ControlOptions','KeyUp',Value.KeyUp);
  FINI.WriteInteger('World.ControlOptions','KeyDown',Value.KeyDown);
  FINI.WriteInteger('World.ControlOptions','KeyLeft',Value.KeyLeft);
  FINI.WriteInteger('World.ControlOptions','KeyRight',Value.KeyRight);
  FINI.WriteInteger('World.ControlOptions','KeyFire',Value.KeyFire);
  FINI.WriteInteger('World.ControlOptions','KeyCancel',Value.KeyCancel);
  FINI.WriteInteger('World.ControlOptions','JoyFire',Value.JoyFire);
  FINI.WriteInteger('World.ControlOptions','JoyCancel',Value.JoyCancel);
  FINI.WriteInteger('World.ControlOptions','JoyJump',Value.JoyJump);
  FINI.WriteBool('World.ControlOptions','UseXforY',Value.JoyXasY);
  FINI.WriteBool('World.ControlOptions','JoyBtnForJump',Value.JoyBtnForJump);
end;

procedure DQE2_World.SetCurrentZone(const Value: DQE2_Zone);
var
  a:integer;
  x,tmp:TGPStore;
begin
  if FCurrentZone<>nil then
  begin
    px:=FPlayer.X+FCurrentZone.Left;py:=FPlayer.Y+FCurrentZone.Top;
  end
  else
  begin
    px:=0;py:=0;
  end;
  for a:=FRunningScripts.Count-1 downto 0 do
  begin
    FCurrentScript:=DQE2_Script(FRunningScripts[a]);
    if FCurrentScript<>nil then
      try if FCurrentScript.FAutoStop then FCurrentScript.Stop;except end;
  end;
  for a:=0 to FCurrentZone.Objects.Count-1 do
    if DQE2_Object(FCurrentZone.Objects[a]) is DQE2_Entity then
      DQE2_Entity(FCurrentZone.Objects[a]).Stop;
  FRunningScripts.Clear;
  x:=Value.Environment;
  for a:=1 to FMusicPlayer.MusicCount-1 do
    FMusicPlayer.Musics[a].Stop;
  for a:=0 to FMusicPlayer.MusicCount-1 do
  begin
    if boolean(FMusicPlayer.Musics[a].Tag) then
      if Value.FDisableEnvironment then
      begin
        ZeroMemory(@tmp,sizeof(tmp));
        tmp.Volume:=FMusicPlayer.Musics[a].Volume;
        FMusicPlayer.Musics[a].LoadState(tmp);
      end
      else
        FMusicPlayer.Musics[a].LoadState(x);
  end;
  FNextZone:=Value;
  FFadeSpeed:=25;
  StopAllPlayerSounds;
end;

procedure DQE2_World.SetGameSpeed(const Value: byte);
var
  x:integer;
begin
  x:=256-Value;
  if x<>FTimer.Interval then
  begin
    FTimer.Interval:=x;
    FINI.WriteInteger('World.OtherOptions','GameSpeed',Value);
  end;
end;

procedure DQE2_World.SetJoystickEnabled(const Value: boolean);
begin
  FJoystickEnabled := Value;
  FINI.WriteBool('World.ControlOptions','JoyEnabled',Value);
  FUseJoy:=Value and FJoystickFound;
end;

procedure DQE2_World.SetUseCache(const Value: boolean);
begin
  if FUseCache<>Value then
  begin
    FUseCache := Value;
    FINI.WriteBool('World.OtherOptions','UseCache',Value);
  end;
end;

procedure DQE2_World.SetVideoOptions(const Value: DQE2_VideoOptions);
begin
  if CompareMem(@Value,@FVideoOptions,sizeof(DQE2_VideoOptions))=false then
  begin
    FVideoOptions := Value;
    FINI.WriteBool('World.VideoOptions','Device.BitDepth',Value.BitDepth);
    FINI.WriteBool('World.VideoOptions','Device.ZBuffer',Value.ZBuffer);
    FINI.WriteBool('World.VideoOptions','Device.Hardware',Value.Hardware);
    FINI.WriteBool('World.VideoOptions','Device.VSync',Value.VSync);
    FINI.WriteBool('World.VideoOptions','Canvas.Antialiasing',Value.AntiAliasing);
    FINI.WriteBool('World.VideoOptions','Canvas.Dithering',Value.Dithering);
    UpdateDisplay;
  end;
end;

procedure DQE2_World.SaveToFile(FileName: string);
var
  f:TFileStreamEx;
  a,b:integer;
  sz:Cardinal;
  s,s1,s2:string;
  SList:TStringList;
  obj:DQE2_Object;
  di:DQE2_Dialog;
  sc:DQE2_Script;
  zn:DQE2_Zone;
  it:DQE2_Item;
  ef:DQE2_Effect;
  et:DQE2_EntityType;
  ent:DQE2_Entity;
  st:TGPStore;
  fn:DQE2_Font;
  vv:variant;
  ok:boolean;
  ents:TList;

  procedure SaveVars(Sender:Pointer);
  var
    pax:TPaxScripter;
    b:integer;
    b3:boolean;
  begin
    pax:=DQE2_Scriptable(Sender).FProg;
    for b:=50 to pax.IDCount do
    begin
      s:=pax.GetFullName(b);
      b3:=pax.IsMethod(b);if b3 then break;
      if(pax.GetName(b)=s)then
      if(IsValidIdent(s,false))then
      if(pax.fScripter.ClassList.FindClassByName(s)=nil)then
      begin
        f.WriteString(s);
        s1:=AnsiUpperCase(pax.GetTypeName(b));
        ok:=false;
        if s1='DQE2_DIALOG' then begin ok:=true;f.WriteByte(10);end else
        if s1='DQE2_FONT'   then begin ok:=true;f.WriteByte(11);end else
        if s1='DQE2_ITEM'   then begin ok:=true;f.WriteByte(12);end else
        if s1='DQE2_OBJECT' then begin ok:=true;f.WriteByte(13);end else
        if s1='DQE2_SCRIPT' then begin ok:=true;f.WriteByte(14);end else
        if s1='DQE2_SPRITE' then begin ok:=true;f.WriteByte(15);end else
        if s1='DQE2_ZONE'   then begin ok:=true;f.WriteByte(16);end else
        if s1='DQE2_EFFECT' then begin ok:=true;f.WriteByte(17);end else
        if s1='DQE2_ENTITY' then begin ok:=true;f.WriteByte(18);end else
        if s1='DQE2_ENTITYTYPE' then begin ok:=true;f.WriteByte(19);end else
        if(s1='STRING')or(s1='VARIANT') then begin f.WriteByte(1);end else
          f.WriteByte(2);//Ints, Floats & Booleans
        if ok then
        begin
          try
            pax.Eval(s+'.name',FScriptLang.LanguageName,vv);
            f.WriteString(vv);
          except
            f.WriteString('');
          end;
        end
        else
        begin
          f.WriteString(pax.GetValueByID(b));
        end;
      end;
    end;
    f.WriteString('');
  end;

  procedure SaveEntityState(e:DQE2_Entity);
  var
    x:TPAXScriptObject;
    v:Variant;
    a:integer;
  begin
    x:=e.ScriptState;
    if x<>nil then
    begin
      for a:=0 to x.PropertyList.Count-1 do
      begin
        v:=x.PropertyList.Properties[a].fValue^;
        if IsObject(v) then
        begin
          if TObject(pointer(cardinal(v))) is DQE2_Dummy then
            f.WriteString(DQE2_Dummy(pointer(cardinal(v))).Name)
        end
        else
        begin
          f.WriteWord(VarType(v));
          f.WriteString(string(v));
        end;
      end;
    end;
  end;

begin
try
  //Pause;
  SetWorkDirectory;
  ForceDirectories(ExtractFileDir(FileName));
  f:=TFileStreamEx.Create(FileName,fmCreate);
  // id
  f.WriteString('DQE2');
  f.Writeword(dqe2version);
  f.WriteWord(FScreenWidth);f.WriteWord(FScreenHeight);
  // getting used sprites
  SList:=TStringList.Create;
  GetUsedSprites(SList);
  // saving sprites
  f.WriteInteger(SList.Count);s1:='';
  for a:=0 to SList.Count-1 do
  begin
    s:=copy(SList.Strings[a],9,length(SList.Strings[a])-8);
    s2:=ExtractFileDir(s);
    if s2<>s1 then
    begin
      s1:=s2;f.WriteString(ChangeFileExt(s,''));
    end
    else
      f.WriteString(ExtractFileNameOnly(s));
    s:=AnsiLowerCase(ExtractFileExt(s));
    if s='.bmp' then f.WriteByte(1) else
    if s='.gif' then f.WriteByte(2) else
    if s='.jpg' then f.WriteByte(3) else
    if s='.jpeg' then f.WriteByte(4) else
    if s='.png' then f.WriteByte(5) else
    if s='.apng' then f.WriteByte(6);
  end;
  // saving scripts
  f.WriteInteger(FScripts.Count);
  for a:=0 to FScripts.Count-1 do
  begin
    sc:=DQE2_Script(FScripts[a]);
    f.WriteString(sc.Name);
    f.WriteStringList(sc.FText);
    f.WriteBoolean(sc.FSuspended);
    f.WriteBoolean(sc.FAutoStop);
    f.WriteBoolean(sc.FUseScriptText);
  end;
  // saving entity types
  f.WriteInteger(FEntityTypes.Count);
  if FEntityTypes.Count>0 then
    for a:=0 to FEntityTypes.Count-1 do
    begin
      et:=DQE2_EntityType(FEntityTypes[a]);
      f.WriteString(et.Name);
      f.WriteStringList(et.Text);
      f.WriteBoolean(et.FUseScriptText);
    end;
  // saving objects and zones
  ents:=TList.Create;
  f.WriteInteger(FZones.Count);
  if FZones.Count>0 then
  begin
    for a:=0 to FZones.Count-1 do
    begin
      zn:=DQE2_Zone(FZones[a]);
      f.WriteWord(zn.Left);f.WriteWord(zn.Top);
      f.WriteWord(zn.Right);f.WriteWord(zn.Bottom);
      f.WriteString(zn.Name);
      f.WriteBoolean(zn.DisableEnvironment);
      f.WriteBuffer(zn.Environment,sizeof(zn.Environment));
      f.WriteInteger(zn.Color);
      f.WriteInteger(zn.StartScripts.Count);
      if zn.StartScripts.Count>0 then
        for b:=0 to zn.StartScripts.Count-1 do
          f.WriteInteger(DQE2_Script(zn.StartScripts[b]).Index);
      f.WriteInteger(zn.Objects.Count);
      if zn.Objects.Count>0 then
        for b:=0 to zn.Objects.Count-1 do
        begin
          if DQE2_Dummy(zn.Objects[b]).ClassNameIs('DQE2_Entity') then
          begin
            ent:=DQE2_Entity(zn.Objects[b]);
            f.WriteInteger(ent.EntityType.Index);
            ents.Add(pointer(ent.Index));
          end
          else
            f.WriteInteger(-1);
          obj:=DQE2_Object(zn.Objects[b]);
          f.WriteString(obj.FName);f.WriteBoolean(obj.FMarked);
          f.WriteInteger(SList.IndexOf(obj.Sprite.Name));
          f.WriteSmallint(obj.X);f.WriteSmallint(obj.Y);
          f.WriteWord(obj.Width);f.WriteWord(obj.Height);
          f.WriteByte(obj.Alpha);f.WriteSmallInt(obj.Angle);
          f.WriteSmallint(obj.CenterX);f.WriteSmallint(obj.CenterY);
          f.WriteCardinal(obj.Color);f.WriteCardinal(obj.DrawFX);
          f.WriteByte(obj.AnimEnd);f.WriteByte(obj.AnimStart);
          f.WriteShortint(obj.AnimSpeed);
          f.WriteInteger(obj.Damage);
          f.WriteBoolean(obj.Collider);
          f.WriteBoolean(obj.FlipVert);f.WriteBoolean(obj.FlipHoriz);
          f.WriteBoolean(obj.Visible);
        end;
    end;
    for a:=0 to FZones.Count-1 do
    begin
      zn:=DQE2_Zone(FZones[a]);
      if zn.FPreRenderZone<>nil then
        f.WriteInteger(zn.FPreRenderZone.Index)
      else
        f.WriteInteger(-1);
      f.WriteSmallint(zn.FBackSpeed);
      f.WriteSmallint(zn.FBackOffsetX);
      f.WriteSmallint(zn.FBackOffsetY);
    end;
  end;
  // saving music
  FMusicPlayer.Music.tag:=1;
  f.WriteInteger(FMusicPlayer.PlayList.Count);
  for a:=0 to FMusicPlayer.PlayList.Count-1 do
    f.WriteString(ExtractFileName(FMusicPlayer.PlayList.Files[a]));
  sz:=SizeOf(st);
  f.WriteCardinal(sz);
  FMusicPlayer.Music.SaveState(st);
  f.WriteBuffer(st,sz);
  f.WriteBoolean(boolean(FMusicPlayer.Music.Tag));
  f.WriteByte(byte(FMusicPlayer.PlayList.Mode));
  f.WriteInteger(FMusicPlayer.MusicCount);
  for a:=1 to FMusicPlayer.MusicCount-1 do
  begin
    f.WriteString(ExtractFileName(FMusicPlayer.Musics[a].FileName));
    FMusicPlayer.Musics[a].SaveState(st);f.WriteBuffer(st,sizeof(st));
    f.WriteBoolean(boolean(FMusicPlayer.Musics[a].Tag));
  end;
  // saving fonts
  f.WriteInteger(FFonts.Count);
  for a:=0 to FFonts.Count-1 do
  begin
    fn:=DQE2_Font(FFonts[a]);
    f.WriteString(fn.Name);
    f.WriteString(fn.FFont.FontName);
    f.WriteByte(fn.Font.Size);
    f.WriteByte(fn.FFont.CharSet);
    f.WriteBoolean(fn.FFont.Antialias);
    b:=0;
    if feBold in fn.FFont.Style then b:=b or 1;
    if feItalic in fn.FFont.Style then b:=b or 2;
    if feShadow in fn.FFont.Style then b:=b or 4;
    if feEmboss in fn.FFont.Style then b:=b or 8;
    f.WriteByte(b);
    f.WriteSingle(fn.FFont.ShadowIntensity);
    f.WriteCardinal(fn.Font.ShadowColor);
    f.WriteByte(fn.Font.ShadowDepth);
    f.WriteSingle(fn.FFont.EmbossStrength);
    f.WriteCardinal(fn.FFont.EmbossColor);
    f.WriteByte(fn.FFont.EmbossDepth);
  end;
  // saving dialogs
  f.WriteInteger(FDialogs.Count);
  for a:=0 to FDialogs.Count-1 do
  begin
    di:=DQE2_Dialog(FDialogs[a]);
    f.WriteString(di.FName);
    f.WriteStringList(di.FText);
    f.WriteString(di.FPersonName);
    f.WriteBoolean(di.FUseScriptText);
    f.WriteInteger(di.FQuestions.Count);
    for b:=0 to di.FQuestions.Count-1 do
      f.WriteStringList(di.FQuestions.Strings[b]);
    f.WriteInteger(di.FAnswers.Count);
    for b:=0 to di.FAnswersCount-1 do
    begin
      f.WriteString(di.FAnswerTitles.Strings[b]);
      f.WriteStringList(di.FAnswers.Strings[b]);
    end;
  end;
  // saving items
  f.WriteInteger(FItems.Count);
  for a:=0 to FItems.count-1 do
  begin
    it:=dqe2_item(FItems[a]);
    f.WriteString(it.Name);
    if it.Zone=nil then f.WriteInteger(-1) else f.WriteInteger(it.Zone.Index);
    f.WriteInteger(SList.IndexOf(it.Sprite.Name));
    f.WriteString(it.RealName);
    f.WriteStringList(it.Description);
    f.WriteSmallint(it.X);f.WriteSmallint(it.Y);
    f.WriteWord(it.Width);f.WriteWord(it.Height);
    f.WriteByte(it.Alpha);
    f.WriteByte(it.AnimEnd);
    f.WriteByte(it.AnimStart);
    f.WriteShortInt(it.AnimSpeed);
    f.WriteBoolean(it.FlipHoriz);
    f.WriteBoolean(it.FlipVert);
    f.WriteCardinal(it.Color);f.WriteCardinal(it.DrawFX);
    f.WriteBoolean(it.Visible);
  end;
  // saving variables
  f.WriteInteger(FVarCount);
  for a:=0 to FVarCount-1 do
  begin
    f.WriteString(FVarNames.Strings[a]);
    f.WriteString(FVarValues.Strings[a]);
  end;
  // saving effects
  f.WriteInteger(FEffects.Count);
  for a:=0 to FEffects.Count-1 do
  begin
    ef:=DQE2_Effect(FEffects[a]);
    f.WriteString(ef.Name);
    f.WriteInteger(SList.IndexOf(ef.Sprite.Name));
    f.WriteSmallint(ef.X);f.WriteSmallint(ef.Y);
    f.WriteWord(ef.Width);f.WriteWord(ef.Height);
    f.WriteByte(ef.Alpha);f.WriteSmallint(ef.Angle);
    f.WriteSmallint(ef.CenterX);f.WriteSmallint(ef.CenterY);
    f.WriteCardinal(ef.Color);f.WriteCardinal(ef.DrawFX);
    f.WriteByte(ef.AnimEnd);f.WriteByte(ef.AnimStart);
    f.WriteShortint(ef.AnimSpeed);
    f.WriteShortInt(ef.Damage);
    f.WriteBoolean(ef.FlipVert);f.WriteBoolean(ef.FlipHoriz);
    f.WriteInteger(ef.FnLoops);f.WriteBoolean(ef.FPingPong);
    f.WriteShortInt(ef.FAnimPos);
    f.WriteBoolean(ef.Visible);
  end;
  // saving settings
  f.WriteByte(FPlayer.Bag.FCapacity);
  f.WriteInteger(FPlayer.Bag.Items.Count);
  for a:=0 to FPlayer.Bag.Items.Count-1 do
    f.WriteInteger(DQE2_Item(FPlayer.Bag.Items[a]).Index);
  f.WriteString(FPlayer.Sprite.ShortName);
  f.WriteString(FPlayer.Name);
  f.WriteInteger(FCurrentZone.Index);
  f.WriteSmallint(FPlayer.X);
  f.WriteSmallint(FPlayer.Y);
  f.WriteByte(FPlayer.Lifes);
  f.WriteWord(FPlayer.Energy);
  f.WriteByte(FPlayer.MaxLifes);
  f.WriteWord(FPlayer.MaxEnergy);
  f.WriteString(FSkinName);
  f.WriteInteger(F_PersonName.Index);
  f.WriteInteger(F_Question.Index);
  f.WriteInteger(F_Answer.Index);
  f.WriteInteger(F_ItemName.Index);
  f.WriteInteger(F_ItemText.Index);
  f.WriteBoolean(FPlayer.FControl);
  f.WriteBoolean(FPlayer.FVisible);
  if FSoundWalk=nil then f.WriteInteger(0) else f.WriteInteger(FSoundWalk.Index);
  if FSoundJump=nil then f.WriteInteger(0) else f.WriteInteger(FSoundJump.Index);
  if FSoundWound=nil then f.WriteInteger(0) else f.WriteInteger(FSoundWound.Index);
  if FSoundDie=nil then f.WriteInteger(0) else f.WriteInteger(FSoundDie.Index);
  // saving scripts' internal variables & other runtime stuff
  if World.State<>wsStop then
  begin
    for a:=0 to FScripts.Count-1 do
    begin
      sc:=dqe2_script(FScripts[a]);
      f.WriteBoolean(sc.FRunning);
      f.WriteInteger(sc.FPassCount);
      SaveVars(sc);
    end;
    for a:=0 to FDialogs.Count-1 do
      SaveVars(FDialogs[a]);
    f.WriteByte(byte(FMusicPlayer.Music.MusicState));
    if FMusicPlayer.Music.MusicState<>msIdle then
    begin
      f.WriteShortInt(FMusicPlayer.PlayList.MusicIndex);
      f.WriteInteger(FMusicPlayer.Music.Position);
    end;
    for a:=1 to FMusicPlayer.MusicCount-1 do
    begin
      f.WriteByte(byte(FMusicPlayer.Musics[a].MusicState));
      if FMusicPlayer.Musics[a].MusicState<>msIdle then
      begin
        f.WriteWord(FMusicPlayer.Musics[a].nLoops);
        f.WriteInteger(FMusicPlayer.Musics[a].Position);
      end;
    end;
    f.WriteBoolean(FPlayer.FSaved);
    if FPlayer.FSaved then
    begin
      f.WriteInteger(FPlayer.FLastZone.Index);
      f.WriteSmallint(FPlayer.FLastX);
      f.WriteSmallint(FPlayer.FLastY);
    end;
    for a:=0 to ents.Count-1 do
    begin
      ent:=DQE2_Entity(FObjects[integer(ents[a])]);
      if ent.EntityType=nil then continue;
      if ent.EntityType.Compiled=false then continue;
      if ent.Running=false then continue;
      f.WriteInteger(ent.Index);
      SaveEntityState(ent);
    end;
    f.WriteInteger(-1);
  end;
  // cleaning up
  FreeAndNil(ents);
  FreeAndNil(SList);
  FreeAndNil(f);
except
  FreeAndNil(ents);
  FreeAndNil(SList);
  FreeAndNil(f);
end;
end;

function doReadWord(f:TFileStreamEx):integer;begin result:=f.ReadWord;end;
function doReadInteger(f:TFileStreamEx):integer;begin result:=f.ReadInteger;end;

procedure DQE2_World.LoadFromFile(FileName: string;DoResume:boolean=false;ChangeShared:boolean=false);
var
  f:TFileStreamEx;
  a,b,c,n,n1,n2,n3:integer;
  sz:Cardinal;
  s,s1,s2:string;
  SList:TStringList;
  obj:DQE2_Object;
  di:DQE2_Dialog;
  sc:DQE2_Script;
  zn:DQE2_Zone;
  it:DQE2_Item;
  et:DQE2_EntityType;
  ent:DQE2_Entity;
  ver,w,_l,_t,_r,_b:word;
  st:TGPStore;
  m:TGPMusic;
  fn:DQE2_Font;
  ef:DQE2_Effect;
  scr_n,di_n,mus_n:integer;
  state:TGPMusicState;

  procedure LoadVars(Sender:Pointer);
  var
    pax:TPaxScripter;
    scr:DQE2_Scriptable;
    ad:string;
  begin
    scr:=DQE2_Scriptable(Sender);
    if scr.UseScriptText then
    begin
      ad:='procedure __Run;'#13#10'begin'#13#10;
      pax:=scr.FProg;
      repeat
        s:=f.ReadString;if s='' then break;
        n1:=f.ReadByte;
        s1:=f.ReadString;if s1='' then continue;
        s1:=StringReplace(s1,'''','''''',[rfReplaceAll]);
        case n1 of
          10:s1:='FindDialog('''+s1+''')';
          11:s1:='FindFont('''+s1+''')';
          12:s1:='FindItem('''+s1+''')';
          13:s1:='FindObject('''+s1+''')';
          14:s1:='FindScript('''+s1+''')';
          15:s1:='FindSprite('''+s1+''')';
          16:s1:='FindZone('''+s1+''')';
          17:s1:='FindEffect('''+s1+''')';
          18:s1:='FindEntity('''+s1+''')';
          19:s1:='FindEntityType('''+s1+''')';
          1:s1:=''''+s1+'''';
          2:s1:=StringReplace(s1,',','.',[rfReplaceAll]);
        else s1:=s1;
        end;
        ad:=ad+s+':='+s1+';'#13#10;
      until false;
      ad:=ad+'end;';
      if scr.Compile(ad) then
        pax.CallFunction('__Run',[]);
    end;
  end;

  procedure LoadEntityState(e:DQE2_Entity);
  var
    x:TPAXScriptObject;
    a:integer;
    v:PVariant;
    tmp:variant;
    tmp_type:TVarType;
    w:DQE2_Dummy;
    s:string;

    procedure q(lst:DQE2_List);
    begin
      v^:=cardinal(pointer(lst.FindByName(s)));
    end;

  begin
    x:=e.ScriptState;
    for a:=0 to x.PropertyList.Count-1 do
    begin
      v:=x.PropertyList.Properties[a].fValue;
      if IsObject(v^) then
      begin
        if TObject(pointer(cardinal(v^))) is DQE2_Dummy then
        begin
          s:=f.ReadString;
          w:=DQE2_Dummy(pointer(cardinal(v^)));
          if w.ClassNameIs('DQE2_Dialog') then q(Dialogs) else
          if w.ClassNameIs('DQE2_Font') then q(Fonts) else
          if w.ClassNameIs('DQE2_Item') then q(Items) else
          if w.ClassNameIs('DQE2_Object') then q(Objects) else
          if w.ClassNameIs('DQE2_Script') then q(Scripts) else
          if w.ClassNameIs('DQE2_Sprite') then q(Sprites) else
          if w.ClassNameIs('DQE2_Zone') then q(Zones) else
          if w.ClassNameIs('DQE2_Effect') then q(Effects) else
          if w.ClassNameIs('DQE2_Entity') then q(Objects) else
          if w.ClassNameIs('DQE2_EntityType') then q(EntityTypes);
        end;
      end
      else
      begin
        tmp_type:=f.ReadWord;
        tmp:=f.ReadString();
        v^:=VarAsType(tmp,tmp_type);
      end;
    end;
  end;

  var doReadFunc:function(f:TFileStreamEx):integer;

begin
try
  Stop;Clear(false);
  f:=TFileStreamEx.Create(FileName,fmOpenRead);
  f.ReadString;
  ver:=f.ReadWord;
  if ver<3 then
    doReadFunc:=doReadWord
  else
    doReadFunc:=doReadInteger;
  if ver>=4 then
  begin
    n1:=f.ReadWord;n2:=f.ReadWord;
  end
  else
  begin
    n1:=DefaultScreenWidth;n2:=DefaultScreenHeight;
  end;
  SetResolution(n1,n2);
  if ChangeShared then
  begin
    s:=FSharedDir;
    s1:=ExtractFileDir(FileName);
    FSharedDir:=s1+'\..\Shared';
    SetWorkDirectory;
    if AnsiStrIComp(PAnsiChar(s),PAnsiChar(FSharedDir))<>0 then
    begin
      FSprites.Clear;
      LoadSprites(FLoadThumbnails);
    end;
  end
  else
    SetWorkDirectory;
  // loading sprites
  n:=doReadFunc(f);
  SList:=TStringList.Create;SList.CaseSensitive:=false;
  s1:='Objects\';
  for a:=0 to n-1 do
  begin
    s:=f.ReadString;c:=Pos('\',s);
    if c>0 then
      begin b:=length(s);s1:='Objects\'+copy(s,1,c-1)+'\';s:=copy(s,c+1,b-c);end;
    c:=f.ReadByte;
    case c of
      1:s2:='.bmp';
      2:s2:='.gif';
      3:s2:='.jpg';
      4:s2:='.jpeg';
      5:s2:='.png';
      6:s2:='.apng'; 
    end;
    s:=s1+s+s2;SList.Add(s);
    if FSprites.FindByName(s)=nil then
      DQE2_Sprite.Create(FSprites).Name:=s;
  end;
  // loading scripts
  scr_n:=doReadFunc(f);
  for a:=0 to scr_n-1 do
  begin
    sc:=DQE2_Script.Create(FScripts);
    sc.Name:=f.ReadString;
    sc.Text:=f.ReadStringList;
    sc.FSuspended:=f.ReadBoolean;
    sc.FAutoStop:=f.ReadBoolean;
    if ver>=3 then sc.UseScriptText:=f.ReadBoolean else sc.UseScriptText:=true;
  end;
  // loading entity types
  if ver>=3 then
  begin
    n:=f.ReadInteger;
    if n>0 then
      for a:=0 to n-1 do
      begin
        et:=DQE2_EntityType.Create(FEntityTypes);
        et.Name:=f.ReadString;
        et.Text:=f.ReadStringList;
        if ver>=3 then et.UseScriptText:=f.ReadBoolean else et.UseScriptText:=true;
      end;
  end;
  // loading objects and zones
  n:=doReadFunc(f);
  if n>0 then
  begin
    for a:=0 to n-1 do
    begin
      _l:=f.ReadWord;_t:=f.ReadWord;_r:=f.ReadWord;_b:=f.ReadWord;
      zn:=dqe2_zone.Create(FZones,_l,_t,_r,_b);
      zn.Name:=f.ReadString;
      zn.DisableEnvironment:=f.ReadBoolean;
      f.ReadBuffer(zn.FEnvironment,sizeof(zn.FEnvironment));
      zn.Color:=f.ReadInteger;
      n1:=doReadFunc(f);
      if n1>0 then
        for b:=0 to n1-1 do
          zn.StartScripts.Add(FScripts[doReadFunc(f)]);
      n1:=doReadFunc(f);
      if n1>0 then
        for b:=0 to n1-1 do
        begin
          if ver>=3 then
          begin
            n3:=f.ReadInteger;
            if n3>=0 then
            begin
              ent:=DQE2_Entity.Create(FObjects,zn,DQE2_EntityType(FEntityTypes[n3]));
              obj:=ent as DQE2_Object;
            end
            else
              obj:=DQE2_Object.Create(FObjects,zn)
          end
          else
            obj:=DQE2_Object.Create(FObjects,zn);
          s:=f.ReadString;
          obj.Marked:=f.ReadBoolean;
          n3:=doReadFunc(f);
          obj.Sprite:=dqe2_sprite(fsprites.FindByName(SList.Strings[n3]));
          obj.name:=s;
          obj.X:=f.ReadSmallint;obj.y:=f.ReadSmallint;
          f.ReadWord;f.ReadWord;//width & height
          obj.Alpha:=f.ReadByte;obj.Angle:=f.ReadSmallint;
          obj.CenterX:=f.ReadSmallint;obj.CenterY:=f.ReadSmallint;
          obj.Color:=f.ReadCardinal;obj.DrawFX:=f.ReadCardinal;
          obj.AnimEnd:=f.ReadByte;obj.AnimStart:=f.ReadByte;
          obj.AnimSpeed:=f.ReadShortint;
          if ver>=2 then obj.Damage:=f.ReadInteger else obj.Damage:=f.ReadShortint;
          obj.Collider:=f.ReadBoolean;
          obj.FlipVert:=f.ReadBoolean;obj.FlipHoriz:=f.ReadBoolean;
          obj.Visible:=f.ReadBoolean;
        end;
    end;
    if ver>=2 then
      for a:=0 to n-1 do
      begin
        zn:=DQE2_Zone(FZones[a]);
        n3:=doReadFunc(f);
        if (n3=-1)or((n3=65535)and(ver<3)) then
          zn.FPreRenderZone:=nil
        else
          zn.FPreRenderZone:=DQE2_Zone(FZones[n3]);
        zn.BackSpeed:=f.ReadSmallint;
        zn.BackOffsetX:=f.ReadSmallint;
        zn.BackOffsetY:=f.ReadSmallint;
      end;
  end;
  // loading music
  n:=doReadFunc(f);
  for a:=0 to n-1 do
    FMusicPlayer.PlayList.AddFile('Music\'+f.ReadString);
  ZeroMemory(@st,sizeof(st));
  if ver>=2 then sz:=f.ReadCardinal else sz:=sizeof(st);
  f.ReadBuffer(st,sz);
  FMusicPlayer.Music.LoadState(st);
  FMusicPlayer.Music.Tag:=integer(f.ReadBoolean);
  FMusicPlayer.PlayList.Mode:=TGPPlayListMode(f.ReadByte);
  mus_n:=doReadFunc(f);
  for a:=1 to mus_n-1 do
  begin
    m:=TGPMusic.Create;m.Open('Music\'+f.ReadString);
    f.ReadBuffer(st,sizeof(st));m.LoadState(st);
    m.Tag:=integer(f.ReadBoolean);
  end;
  // loading fonts
  n:=doReadFunc(f);
  for a:=0 to n-1 do
  begin
    fn:=DQE2_Font.Create(FFonts);
    fn.Name:=f.ReadString;
    fn.FFont.FontName:=f.ReadString;
    fn.Font.Size:=f.ReadByte;
    fn.Font.CharSet:=f.ReadByte;
    fn.Font.Antialias:=f.ReadBoolean;
    fn.FFont.Style:=[];
    b:=f.ReadByte;
    if(b and 1)<>0 then fn.FFont.Style:=fn.FFont.Style+[feBold];
    if(b and 2)<>0 then fn.FFont.Style:=fn.FFont.Style+[feItalic];
    if(b and 4)<>0 then fn.FFont.Style:=fn.FFont.Style+[feShadow];
    if(b and 8)<>0 then fn.FFont.Style:=fn.FFont.Style+[feEmboss];
    fn.FFont.ShadowIntensity:=f.ReadSingle;
    fn.Font.ShadowColor:=f.ReadCardinal;
    fn.Font.ShadowDepth:=f.ReadByte;
    fn.FFont.EmbossStrength:=f.ReadSingle;
    fn.FFont.EmbossColor:=f.ReadCardinal;
    fn.FFont.EmbossDepth:=f.ReadByte;
    fn.FFont.Update;
  end;
  // loading dialogs
  di_n:=doReadFunc(f);
  for a:=0 to di_n-1 do
  begin
    di:=DQE2_Dialog.Create(FDialogs);
    di.Name:=f.ReadString;
    di.Text:=f.ReadStringList;
    di.FPersonName:=f.ReadString;
    if ver>=3 then di.UseScriptText:=f.ReadBoolean else di.UseScriptText:=true;
    n1:=doReadFunc(f);
    if n1>0 then
      for b:=0 to n1-1 do
        di.FQuestions.Add(f.ReadStringList);
    n1:=doReadFunc(f);
    if n1>0 then
      for b:=0 to n1-1 do
      begin
        s1:=f.ReadString;
        s2:=f.ReadStringList;
        di.AddAnswer(s1,s2);
      end;
  end;
  // loading items
  n:=doReadFunc(f);
  for a:=0 to n-1 do
  begin
    it:=DQE2_Item.Create(FItems);
    s:=f.ReadString;
    n1:=doReadFunc(f);
    if n1>=0 then it.Zone:=DQE2_Zone(FZones[n1]) else it.Zone:=nil;
    n3:=doReadFunc(f);
    it.Sprite:=dqe2_sprite(fsprites.FindByName(SList.Strings[n3]));
    it.name:=s;
    it.RealName:=f.ReadString;
    it.Description:=f.ReadStringList;
    it.X:=f.ReadSmallint;it.Y:=f.ReadSmallint;
    f.ReadWord;f.ReadWord;//width & height
    it.Alpha:=f.ReadByte;
    it.AnimEnd:=f.ReadByte;
    it.AnimStart:=f.ReadByte;
    it.AnimSpeed:=f.ReadShortint;
    it.FlipHoriz:=f.ReadBoolean;
    it.FlipVert:=f.ReadBoolean;
    it.Color:=f.ReadCardinal;it.DrawFX:=f.ReadCardinal;
    it.Visible:=f.ReadBoolean;
  end;
  // loading variables
  n:=doReadFunc(f);
  for a:=0 to n-1 do
  begin
    s1:=f.ReadString;s2:=f.ReadString;
    WriteVar(s1,s2);
  end;
  // loading effects
  n:=doReadFunc(f);
  for a:=0 to n-1 do
  begin
    ef:=DQE2_Effect.Create(FEffects);
    s:=f.ReadString;
    ef.Sprite:=dqe2_sprite(fsprites.FindByName(SList.Strings[doReadFunc(f)]));
    ef.Name:=s;
    ef.X:=f.ReadSmallint;ef.Y:=f.ReadSmallint;
    f.ReadWord;f.ReadWord;//width & height
    ef.Alpha:=f.ReadByte;ef.Angle:=f.ReadSmallint;
    ef.CenterX:=f.ReadSmallint;ef.CenterY:=f.ReadSmallint;
    ef.Color:=f.ReadCardinal;ef.DrawFX:=f.ReadCardinal;
    ef.AnimEnd:=f.ReadByte;ef.AnimStart:=f.ReadByte;
    ef.AnimSpeed:=f.ReadShortint;
    ef.Damage:=f.ReadShortint;
    ef.FlipVert:=f.ReadBoolean;ef.FlipHoriz:=f.ReadBoolean;
    ef.FnLoops:=f.ReadInteger;ef.FPingPong:=f.ReadBoolean;
    ef.FAnimPos:=f.ReadShortint;
    ef.Visible:=f.ReadBoolean;
  end;
  // loading state
  FPlayer.Bag.Capacity:=f.ReadByte;
  n:=doReadFunc(f);
  for a:=0 to n-1 do
  begin
    n1:=doReadFunc(f);
    DQE2_Item(FItems[n1]).PutInBag;
  end;
  s:=f.ReadString;
  FPlayer.Sprite:=dqe2_sprite(FSprites.FindByName('Objects\Dizzy\'+s));
  s:=f.readstring;
  FPlayer.Name:=s;
  n:=doReadFunc(f);
  FCurrentZone:=DQE2_Zone(FZones[n]);
  FPlayer.FX:=f.ReadSmallint;
  FPlayer.FY:=f.ReadSmallInt;
  FPlayer.FLifes:=f.ReadByte;
  FPlayer.FEnergy:=f.ReadWord;
  FPlayer.FMaxLifes:=f.ReadByte;
  FPlayer.FMaxEnergy:=f.ReadWord;
  s:=f.ReadString;
  if DirectoryExists(FSharedDir+'\Skins\'+s)then
    LoadSkin(s)
  else LoadSkin('default');
  F_PersonName:=DQE2_Font(FFonts[doReadFunc(f)]);
  F_Question:=DQE2_Font(FFonts[doReadFunc(f)]);
  F_Answer:=DQE2_Font(FFonts[doReadFunc(f)]);
  F_ItemName:=DQE2_Font(FFonts[doReadFunc(f)]);
  F_ItemText:=DQE2_Font(FFonts[doReadFunc(f)]);
  FPlayer.FControl:=f.ReadBoolean;
  FPlayer.FVisible:=f.ReadBoolean;
  if ver>=2 then
  begin
    n:=doReadFunc(f);if n=0 then FSoundWalk:=nil else FSoundWalk:=FMusicPlayer.Musics[n];
    n:=doReadFunc(f);if n=0 then FSoundJump:=nil else FSoundJump:=FMusicPlayer.Musics[n];
    n:=doReadFunc(f);if n=0 then FSoundWound:=nil else FSoundWound:=FMusicPlayer.Musics[n];
    n:=doReadFunc(f);if n=0 then FSoundDie:=nil else FSoundDie:=FMusicPlayer.Musics[n];
  end;
  // loading scripts' internal variables & other runtime stuff
  FRunningScripts.Clear;
  FRunningMusic.Clear;
  if DoResume then
  begin
    if f.Position<f.Size then
    begin
      for a:=0 to scr_n-1 do
      begin
        sc:=dqe2_script(FScripts[a]);
        sc.FRunning:=f.ReadBoolean;
        sc.FPassCount:=f.ReadInteger;
        LoadVars(FScripts[a]);
        if sc.FRunning then
        begin
          sc.FIndexInList:=FRunningScripts.Count;
          FRunningScripts.Add(sc);
        end;
      end;
      for a:=0 to di_n-1 do
        LoadVars(FDialogs[a]);
      state:=TGPMusicState(f.ReadBoolean);
      if state<>msIdle then
      begin
        FMusicPlayer.PlayList.PlayMusic(f.ReadShortint);
        FMusicPlayer.Music.Position:=f.ReadInteger;
        if state=msPaused then FMusicPlayer.Music.Pause;
      end;
      for a:=1 to mus_n-1 do
      begin
        state:=TGPMusicState(f.ReadByte);
        if state<>msIdle then
        begin
          FMusicPlayer.Musics[a].Play(f.ReadWord);
          FMusicPlayer.Musics[a].Position:=f.ReadInteger;
          if state=msPaused then FMusicPlayer.Musics[a].Pause;
        end;
      end;
      FPlayer.FSaved:=f.ReadBoolean;
      if FPlayer.FSaved then
      begin
        n:=doReadFunc(f);
        FPlayer.FLastZone:=DQE2_Zone(FZones[n]);
        FPlayer.FLastX:=f.ReadSmallint;
        FPlayer.FLastY:=f.ReadSmallint;
      end
      else
      begin
        FPlayer.FLastZone:=FCurrentZone;
        FPlayer.FLastX:=0;
        FPlayer.FLastY:=0;
      end;
      if ver>=3 then
        repeat
          n:=f.ReadInteger;
          if n=-1 then break;
          ent:=DQE2_Entity(FObjects[n]);
          if ent.EntityType.Compiled=false then ent.EntityType.Compile;
          ent.FRunning:=true;
          ent.FScriptState:=ent.EntityType.CreateState;
          LoadEntityState(ent);
        until false;
    end;
    World.FState:=wsPlay;
  end;
  FreeAndNil(SList);
  FreeAndNil(f);
  // ending up
  with FPlayer do
  begin
    FAnimSpeed:=10;FAnimNum:=0;
    FlySpeed:=0;OnFly:=false;Dead:=false;FlyTime:=0;Rolling:=false;
    FFootHold:=nil;
  end;
except
  Clear(true);
  FreeAndNil(SList);
  FreeAndNil(f);
end;
end;

procedure DQE2_World.OnRender(Sender:TObject);
var
  a:integer;
  it:dqe2_item;
  x,y:word;
begin
try// suppress all exceptions, because if something goes wrong here, it will be a total crash!
  // rendering
  FDevice.BeginScene;
  if FCurrentZone.FPreRenderZone<>nil then
  begin
    x:=FOffsetX;y:=FOffsetY;
    FOffsetX:=trunc(FCurrentZone.FPreRenderZone.FCurBackSpeed*FOffsetX-FCurrentZone.FBackOffsetX);
    FOffsetY:=trunc(FCurrentZone.FPreRenderZone.FCurBackSpeed*FOffsetY-FCurrentZone.FBackOffsetY);
    for a:=0 to FCurrentZone.FPreRenderZone.FObjects.Count-1 do
      DQE2_Object(FCurrentZone.FPreRenderZone.FObjects[a]).Step;
    FOffsetX:=x;FOffsetY:=y;
  end;
  if Assigned(FPreRender) then OnPreRender;
    for a:=0 to FRunningScripts.Count-1 do
      if FRunningScripts[a]<>nil then
        try DQE2_Script(FRunningScripts[a]).OnPreRender;except end;
  for a:=0 to FCurrentZone.FObjects.Count-1 do
    DQE2_Object(FCurrentZone.FObjects[a]).Step;
  for a:=0 to FItems.Count-1 do
  begin
    it:=DQE2_Item(FItems[a]);
    if(it.Zone=FCurrentZone)and(it.FInBagIndex<0)then
      it.Step;
  end;
  if FEffects.Count>0 then
  begin
    if FState=wsPlay then
      for a:=FEffects.Count-1 downto 0 do
        if not DQE2_Effect(FEffects[a]).DoAnimate then
          DQE2_Effect(FEffects[a]).Free
		    else
          DQE2_Effect(FEffects[a]).Step;
    for a:=0 to FEffects.Count-1 do
      PutSprite(DQE2_Effect(FEffects[a]));
  end;
  if FFadeSpeed=0 then
  begin
    if FPlayer.FVisible then
      world.PutSprite(Fplayer.FSprite.FImages.Item[Fplayer.FAnimFrame],Fplayer.FX,Fplayer.FY,50,50,0,0,0,255,$FFFFFF,fxblend,false,false);
    for a:=0 to FRunningScripts.Count-1 do
      if FRunningScripts[a]<>nil then
        try DQE2_Script(FRunningScripts[a]).OnPostRender;except end;
    if Assigned(FPostRender) then OnPostRender;
    if FCurrentDialog<>nil then
      FCurrentDialog.Step else
    if FPlayer.Bag.Running then
    begin
      a:=FPlayer.Bag.Step;
      case a of
        -1:FPlayer.Bag.Stop;
        0..255:FPlayer.UseItem(DQE2_Item(FPlayer.Bag.Items[a]));
        else ;
      end
    end;
  end
  else
    FCanvas.Rectangle(0,0,FScreenWidth,FScreenHeight,0,(FFadeValue and $FF) shl 24,fxblend);
  FDevice.EndScene;
except
end
end;

procedure DQE2_World.Step;
var
  a:integer;
  zn:dqe2_zone;
  _left,_right,_up:boolean;

  procedure Check;
  begin
    // check player's position in zone
    if (FPlayer.X>_width_div2) then
      if (FPlayer.X<(FCurrentZone.Width-_width_div2))then
        FOffsetX:=FPlayer.X-_width_div2
      else
        FOffsetX:=FCurrentZone.Width-FScreenWidth
    else
      FOffsetX:=0;
    if (FPlayer.Y>_height_div2) then
      if (FPlayer.Y<(FCurrentZone.Height-_height_div2))then
        FOffsetY:=FPlayer.Y-_height_div2
      else
        FOffsetY:=FCurrentZone.Height-FScreenHeight
    else
      FOffsetY:=0;
  end;

begin
try// suppress all exceptions, because if something goes wrong here, it will be a total crash!
  // zone change
  if FFadeSpeed<>0 then
  begin
    FKeysReady:=false;
    FFadeValue:=FFadeValue+FFadeSpeed;
    if FFadeValue>255 then
    begin
      FEffects.Clear;
      if Assigned(FOnZoneChange) then FOnZoneChange(FNextZone);
      FCurrentZone := FNextZone;
      FPlayer.X:=px-FNextZone.Left;
      FPlayer.Y:=py-FNextZone.Top;
      Fplayer.FSaved:=false;
      Check;
      PackRunningScripts;
      if FState<>wsStop then
      begin
        if FCurrentZone.FPreRenderZone<>nil then
          for a:=0 to FCurrentZone.FPreRenderZone.FStartScripts.Count-1 do
            if FCurrentZone.FPreRenderZone.FStartScripts[a]<>nil then
              try DQE2_Script(FCurrentZone.FPreRenderZone.FStartScripts[a]).Run;except end;
         for a:=0 to FCurrentZone.FStartScripts.Count-1 do
           if FCurrentZone.FStartScripts[a]<>nil then
             try DQE2_Script(FCurrentZone.FStartScripts[a]).Run;except end;
         for a:=0 to FCurrentZone.FObjects.Count-1 do
           if DQE2_Dummy(FCurrentZone.FObjects[a]) is DQE2_Entity then
             try DQE2_Entity(FCurrentZone.FObjects[a]).Run;except end;
      end;
      FFadeValue:=255;FFadeSpeed:=-FFadeSpeed;
    end
    else
    if FFadeValue<0 then
    begin
      FFadeValue:=0;FFadeSpeed:=0;
    end;
    render;
    exit;
  end;
  // controls
  FKeysReady:=not(FKeyDown or FKeyUp or FKeyLeft or FKeyRight or FKeyFire or FKeyCancel);
  GetKeyboardState(FKeyboardState);
  {
  FKeyLeft:=  (FKeyboardState[FControlOptions.KeyLeft]and 128)<>0;
  FKeyRight:= (FKeyboardState[FControlOptions.KeyRight]and 128)<>0;
  FKeyUp:=    (FKeyboardState[FControlOptions.KeyUp]and 128)<>0;
  FKeyDown:=  (FKeyboardState[FControlOptions.KeyDown]and 128)<>0;
  FKeyFire:=  (FKeyboardState[FControlOptions.KeyFire]and 128)<>0;
  FKeyCancel:=(FKeyboardState[FControlOptions.KeyCancel]and 128)<>0;
  }
  FKeyLeft:=  KeyPressed[FControlOptions.KeyLeft];
  FKeyRight:= KeyPressed[FControlOptions.KeyRight];
  FKeyUp:=    KeyPressed[FControlOptions.KeyUp];
  FKeyDown:=  KeyPressed[FControlOptions.KeyDown];
  FKeyFire:=  KeyPressed[FControlOptions.KeyFire];
  FKeyCancel:=KeyPressed[FControlOptions.KeyCancel];
  FKeyJump:=FKeyUp;
  if FUseJoy then
    if joyGetPosEx(JOYSTICKID1,@FJoyInfo)=JOYERR_NOERROR then
    begin
      _up:=(JoyInfo.wYpos=_joyymin);
      _left:=(JoyInfo.wXpos=_joyxmin);
      _right:=(JoyInfo.wXpos=_joyxmax);
      FKeyLeft:=FKeyLeft or _left;
      FKeyRight:=FKeyRight or _right;
      if FControlOptions.JoyXasY then
      begin
        FKeyUp:=FKeyUp or _left;
        FKeyDown:=FKeyDown or _right;
      end
      else
      begin
        FKeyUp:=FKeyUp or _up;
        FKeyDown:=FKeyDown or(JoyInfo.wYpos=_joyymax);
      end;
      if FControlOptions.JoyBtnForJump then
        FKeyJump:=FKeyJump or ((JoyInfo.wButtons and FControlOptions.JoyJump)<>0)
      else
        FKeyJump:=FKeyJump or _up;
      FKeyCancel:=FKeyCancel or ((JoyInfo.wButtons and FControlOptions.JoyCancel)<>0);
      FKeyFire:=FKeyFire or ((JoyInfo.wButtons and FControlOptions.JoyFire)<>0);
    end;
  Check;
  // check player's position and possible zone change
  if FState=wsPlay then
  begin
    // scripts processing
    if(not FPlayer.Bag.Running)and(FCurrentDialog=nil)then
    begin
      for a:=0 to FRunningScripts.Count-1 do
        if FRunningScripts[a]<>nil then
          try dqe2_script(FRunningScripts[a]).Step;except end;
      FPlayer.Step;
    end;
    if (FPlayer.X>FCurrentZone.FTempW)or(FPlayer.X<-25)or
       (FPlayer.Y>FCurrentZone.FTempH)or(FPlayer.Y<-25)then
    begin
      px:=FPlayer.X+FCurrentZone.Left;py:=FPlayer.Y+FCurrentZone.Top;
      for a:=0 to FZones.Count-1 do
      begin
        zn:=dqe2_zone(FZones[a]);
        if(px<zn.FTempR)and(px>=zn.FTempL)and(py<zn.FTempB)and(py>=zn.FTempT)then
        begin
          CurrentZone:=zn;
          exit;
        end;
      end;
      FPlayer.x:=FPlayer.X-FPlayer.Width*sgn(FPlayer.X-_width_div2);
      FPlayer.Y:=FPlayer.Y-FPlayer.Width*sgn(FPlayer.Y-_height_div2);
      FPlayer.FlySpeed:=0;
    end;
  end;
  render;
except
end
end;

procedure DQE2_World.Render;
begin
  FDevice.Render(FCurrentZone.CurColor,True);
  FDevice.Flip;
end;

procedure DQE2_World.Stop;
var
  a:integer;
begin
  if Assigned(FOnStateChange)then FOnStateChange(wsStop);
  FState:=wsStop;
  for a:=FScripts.Count-1 downto 0 do
    try DQE2_Script(FScripts[a]).Stop;except end;
  FRunningScripts.Clear;
  if FCurrentDialog<>nil then
  begin
    FCurrentDialog.Stop;
    FCurrentDialog:=nil;
  end;
  for a:=0 to FMusicPlayer.MusicCount-1 do
    FMusicPlayer.Musics[a].Stop;
  if FPlayer.Bag.Running then FPlayer.Bag.Stop;
end;

procedure DQE2_World.StopAllPlayerSounds;
begin
  if World.SoundWalk<>nil then World.SoundWalk.Stop;
  if World.SoundJump<>nil then World.SoundJump.Stop;
  if World.SoundWound<>nil then World.SoundWound.Stop;
  if World.SoundDie<>nil then World.SoundDie.Stop;
end;

procedure DQE2_World.CreateVideoInterface;
const HQ=false;
begin
  FDevice := TAsphyreDevice.Create(nil);
  FDevice.Width := FScreenWidth;
  FDevice.Height := FScreenHeight;
  FDevice.Windowed := not FFullScreen;
  FDevice.WindowHandle := Win;
  FDevice.OnRender := OnRender;
  FCanvas := TAsphyreCanvas.Create(nil);
  FCanvas.Publisher := FDevice;
  FCanvas.AlphaTesting := true;
  if FINI.ReadBool('World.VideoOptions', 'Device.BitDepth', HQ) then
    FDevice.BitDepth := bdHigh
  else
    FDevice.BitDepth := bdLow;
  FDevice.DepthBuffer := FINI.ReadBool('World.VideoOptions', 'Device.ZBuffer', HQ);
  FDevice.HardwareTL := FINI.ReadBool('World.VideoOptions', 'Device.Hardware', HQ);
  FDevice.VSync := FINI.ReadBool('World.VideoOptions', 'Device.VSync', HQ);
  FCanvas.Antialias := FINI.ReadBool('World.VideoOptions', 'Canvas.AntiAliasing', HQ);
  FCanvas.Dithering := FINI.ReadBool('World.VideoOptions', 'Canvas.Dithering', HQ);
  FDevice.Initialize;
  FVideoOptions.BitDepth := boolean(integer(FDevice.BitDepth));
  FVideoOptions.ZBuffer := FDevice.DepthBuffer;
  FVideoOptions.Hardware := FDevice.HardwareTL;
  FVideoOptions.VSync := FDevice.VSync;
  FVideoOptions.AntiAliasing := FCanvas.Antialias;
  FVideoOptions.Dithering := FCanvas.Dithering;
end;

procedure DQE2_World.UpdateDisplay;
var
  a:integer;
  s:TStringList;
begin
  if FDevice<>nil then
  begin
    // unload sprites
    s:=TStringList.Create;
    GetUsedSprites(s);
    for a:=0 to FSprites.Count-1 do
      DQE2_Sprite(FSprites[a]).UnLoad;
    WS_Answer.UnLoad;WS_Question.UnLoad;WS_Bag.UnLoad;WS_Cancel.UnLoad;
    // reset video device
    FCanvas.Free;
    FDevice.Free;
    if Assigned(FOnVideoUpdate)then FOnVideoUpdate();
    CreateVideoInterface;
    // load sprites
    for a:=0 to s.Count-1 do
      DQE2_Sprite(s.Objects[a]).Load;
    WS_Answer.Load;WS_Question.Load;WS_Bag.Load;WS_Cancel.Load;
    // update fonts
    for a:=0 to FFonts.Count-1 do
      DQE2_Font(FFonts[a]).Font.Update;
  end
  else
  begin
    if Assigned(FOnVideoUpdate)then FOnVideoUpdate();
    CreateVideoInterface;
  end;
  _width_div2:=FScreenWidth div 2;_height_div2:=FScreenHeight div 2;
  _maxzonewidth:=FScreenWidth*trunc(65535/FScreenWidth);
  _maxzoneheight:=FScreenHeight*trunc(65535/FScreenHeight);
end;

{ DQE2_Sprite }

procedure DQE2_Sprite.AssignTo(Dest: TPersistent);
var
  x:DQE2_Sprite;
begin
  inherited;
  if not(Dest is DQE2_Sprite) then exit;
  x:=Dest as DQE2_Sprite;
  x.FDefaultName:=FDefaultName;
  x.FDefaultTransparent:=FDefaultTransparent;
  x.FDefaultCollider:=FDefaultCollider;
  x.FDefaultAlpha:=FDefaultAlpha;
  x.FDefaultAnimSpeed:=FDefaultAnimSpeed;
  x.FThumbnail.Assign(FThumbnail);
  x.FINI:=FINI;
  if FLoaded then x.Load;
end;

constructor DQE2_Sprite.Create(Collection: TCollection);
begin
  inherited CreateWithoutName(Collection);
  FLoaded:=false;FImages:=nil;FThumbnail:=nil;
  FDefaultTransparent:=false;FDefaultCollider:=false;
  FDefaultAlpha:=255;FHeight:=0;FWidth:=0;
  FShortname:='';
end;

destructor DQE2_Sprite.Destroy;
begin
  Unload;
  FreeAndNil(FThumbnail);
  inherited;
end;

procedure DQE2_Sprite.Load;
var
  ext,fn:string;
  p:TAsphyreImage;
  a,b,c:integer;
  trans:Cardinal;
  gif:TJvGIFImage;
  bmp:TBitmap;
  f:TFileStream;
  x:TJvGIFFrame;

  procedure AddBMP;
  begin
    p:=FImages.Add;
    p.VisibleSize:=Point(bmp.Width,bmp.Height);
    p.Size:=p.VisibleSize;p.PatternSize:=p.VisibleSize;
    p.LoadFromBitmap(bmp,false,0,0);
  end;

begin
  {$IFDEF RELOADGRAPHICS}
    if loaded then unload;
  {$ELSE}
    if loaded then exit;
      //begin UpdateTransparency(FDefaultTransparent);exit;end;
  {$ENDIF}
  FImages:=TAsphyreImages.Create(nil);
  FImages.Publisher:=World.FDevice;
  fn:=world.SharedDir+'\'+FName;
  if not FileExists(fn) then exit;
  ext:=AnsiLowerCase(ExtractFileExt(fn));
  if(ext='.bmp')or(ext='.jpg')or(ext='.jpeg')or(ext='.tga')then
  begin
    bmp:=TBitmap.Create;
    LoadBitmap(fn,bmp,ifAuto);
    AddBMP;
    bmp.Free;
  end else
  if(ext='.apng')or(ext='.png')then
  begin
    f:=TFileStream.Create(fn,fmOpenRead);
    bmp:=TBitmap.Create;
    repeat
      LoadBitmap(f,bmp,ifPNG);
      AddBMP;
    until(f.Size=f.Position);
    bmp.Free;
  end else
  if(ext='.gif')then
  begin
    gif:=TJvGIFImage.Create;
    gif.LoadFromFile(fn);
    bmp:=TBitmap.Create;
    bmp.Width:=gif.Width;bmp.Height:=gif.Height;
    bmp.Canvas.Brush.Color:=gif.BackgroundColor;
    bmp.Canvas.Pen.Color:=gif.BackgroundColor;
    bmp.Canvas.FillRect(bmp.Canvas.ClipRect);
    for a:=0 to gif.Count-1 do
    begin
      bmp.Canvas.FillRect(bmp.Canvas.ClipRect);
      x:=gif.Frames[a];
      bmp.Canvas.Draw(x.Origin.X,x.Origin.Y,x.Bitmap);
      bmp.Assign(gif.Frames[a].Bitmap);
      AddBMP;
    end;
  end;
  FSingleFrame:=(FImages.Count=1);
  FHeight:=FImages[0].Size.Y;FWidth:=FImages[0].Size.X;
  SetLength(FMask,FImages.Count);
  trans:=FImages[0].Pixels[0,0,0];
  for a:=0 to FImages.Count-1 do
  begin
    SetLength(FMask[a],FWidth);
    for b:=0 to FWidth-1 do
    begin
      SetLength(FMask[a,b],FHeight);
      for c:=0 to FHeight-1 do
        FMask[a,b,c]:=(FImages[a].Pixels[b,c,0]<>trans);
    end;
  end;
  FOriginalAlpha:=trans and $FF000000;
  if FDefaultTransparent then
    UpdateTransparency(FDefaultTransparent);
  FLoaded:=true;
end;

function DQE2_Sprite.Prepare(FileName: string): boolean;
var
  bmp:TBitmap;
  ext:string;
  gif:TJvGIFImage;
  f:TFileStream;
begin
  result:=false;
  ext:=AnsiLowerCase(ExtractFileExt(FileName));
  FreeAndNil(FThumbnail);
  if(ext='.bmp')or(ext='.jpg')or(ext='.jpeg')or(ext='.png')or(ext='.tga')then
  begin
    bmp:=TBitmap.Create;
    try
      if LoadBitmap(FileName,bmp,ifAuto) then
      begin
        FHeight:=bmp.Height;FWidth:=bmp.Width;
        FThumbnail:=TBitmap.Create;
        FThumbnail.Height:=50;FThumbnail.Width:=50;
        FThumbnail.Canvas.StretchDraw(rect(0,0,49,49),bmp);
        result:=true;
      end;
    finally
      FreeAndNil(bmp);
    end;
  end
  else
  if(ext='.gif')then
  begin
    gif:=TJvGIFImage.Create;
    try
      gif.LoadFromFile(FileName);
    except
      gif.Free;
      exit;
    end;
    FHeight:=gif.Height;FWidth:=gif.Width;
    if(gif.Count>0)and(not gif.Corrupted)then
    begin
      FThumbnail:=TBitmap.Create;
      FThumbnail.Height:=50;FThumbnail.Width:=50;
      FThumbnail.Canvas.StretchDraw(rect(0,0,49,49),gif.Frames[0].Bitmap);
      result:=true;
    end;
    FreeAndNil(gif);
  end
  else
  if(ext='.apng')then
  begin
    f:=TFileStream.Create(FileName,fmOpenRead);
    if f.Size>0 then
    begin
      bmp:=TBitmap.Create;
      try
        if LoadPNGtoBMP(f,bmp) then
        begin
          FHeight:=bmp.Height;FWidth:=bmp.Width;
          FThumbnail:=TBitmap.Create;
          FThumbnail.Height:=50;FThumbnail.Width:=50;
          FThumbnail.Canvas.StretchDraw(rect(0,0,49,49),bmp);
          result:=true;
        end;
      finally
        FreeAndNil(bmp);
        FreeAndNil(f);
      end;
    end;
  end;
end;

procedure DQE2_Sprite.SetDefaultAlpha(const Value: byte);
begin
  FDefaultAlpha := Value;
  if FINI<>nil then
    FINI.WriteInteger(FShortName,'Alpha',Value);
end;

procedure DQE2_Sprite.SetDefaultAnimSpeed(const Value: ShortInt);
begin
  FDefaultAnimSpeed := Value;
  if FINI<>nil then
    FINI.WriteInteger(FShortName,'AnimSpeed',Value);
end;

procedure DQE2_Sprite.SetDefaultCollider(const Value: Boolean);
begin
  FDefaultCollider := Value;
  if FINI<>nil then
    FINI.WriteBool(FShortName,'Collider',Value);
end;

procedure DQE2_Sprite.SetDefaultName(const Value: string);
begin
  FDefaultName:=Value;
  if FINI<>nil then
    FINI.WriteString(FShortName,'Name',Value);
end;

procedure DQE2_Sprite.SetDefaultTransparent(const Value: boolean);
begin
  if FLoaded and (value<>FDefaultTransparent)then
    UpdateTransparency(Value);
  if FINI<>nil then
    FINI.WriteBool(FShortName,'Transparent',Value);
end;

procedure DQE2_Sprite.SetName(const Value: string);
begin
  inherited;
  FShortName:=ExtractFileName(FName);
end;

procedure DQE2_Sprite.UnLoad;
begin
  if not loaded then exit;
  FreeAndNil(FImages);
  FLoaded:=false;
end;

procedure DQE2_Sprite.UpdateFromINI(_nam:string;_tr,_col:boolean;_spd:ShortInt;_alp:byte);
var
  ok:boolean;
begin
  ok:=(FINI<>nil);
  if ok then ok:=(FINI.SectionExists(ShortName));
  if ok then
  begin
    FDefaultName:=FINI.ReadString(FShortName,'Name',_nam);
    FDefaultTransparent:=FINI.ReadBool(FShortName,'Transparent',_tr);
    FDefaultCollider:=FINI.ReadBool(FShortName,'Collider',_col);
    FDefaultAlpha:=FINI.ReadInteger(FShortName,'Alpha',_alp);
    FDefaultAnimSpeed:=FINI.ReadInteger(FShortName,'AnimSpeed',_spd);
  end
  else
  begin
    FDefaultName:=_nam;
    FDefaultTransparent:=_tr;
    FDefaultCollider:=_col;
    FDefaultAlpha:=_alp;
    FDefaultAnimSpeed:=_spd;
  end;
  if FLoaded then Load;
end;

procedure DQE2_Sprite.UpdateTransparency(T: boolean);
var
  a,x,y:integer;
begin
  FDefaultTransparent:=T;
  if FDefaultTransparent then
  begin
    for a:=0 to FImages.Count-1 do
      for x:=0 to FImages[a].Size.X-1 do
        for y:=0 to FImages[a].Size.Y-1 do
          if FMask[a,x,y]=false then
            FImages[a].Pixels[x,y,0]:=FImages[a].Pixels[x,y,0] and $00FFFFFF;
  end
  else
    for a:=0 to FImages.Count-1 do
      for x:=0 to FImages[a].Size.X-1 do
        for y:=0 to FImages[a].Size.Y-1 do
          if FMask[a,x,y]=false then
            FImages[a].Pixels[x,y,0]:=FImages[a].Pixels[x,y,0] or FOriginalAlpha;
end;

{ DQE2_Zone }

procedure DQE2_Zone.AssignTo(Dest: TPersistent);
var
  x:DQE2_Zone;
begin
  inherited;
  if not(Dest is DQE2_Zone) then exit;
  x:=Dest as DQE2_Zone;
  x.FColor:=FColor;
  x.FStartScripts.Assign(FStartScripts);
  x.FLeft:=FLeft;
  x.FTop:=FTop;
  x.FRight:=FRight;
  x.FBottom:=FBottom;
  x.FThumbnail.Assign(FThumbnail);
  x.FEnvironment:=FEnvironment;
  x.FDisableEnvironment:=FDisableEnvironment;
  x.FTagRect:=FTagRect;
  x.FHeight:=FHeight;
  x.FWidth:=FWidth;
  x.FPreRenderZone:=FPreRenderZone;
  x.FBackSpeed:=FBackSpeed;
  x.FBackOffsetX:=FBackOffsetX;
  x.FBackOffsetY:=FBackOffsetY;
  x.FCurBackSpeed:=FCurBackSpeed;
end;

constructor DQE2_Zone.Create(Collection: TCollection;L,T,R,B:Word);
begin
  inherited Create(Collection);
  FObjects:=TList.Create;
  Color:=0;FStartScripts:=TList.Create;FThumbnail:=nil;
  FLeft:=L;FTop:=T;FRight:=R;FBottom:=B;
  fillchar(FEnvironment,sizeof(FEnvironment),0);
  FEnvironment.Volume:=100;
  FWidth:=FRight-FLeft;FHeight:=FBottom-FTop;
  UpdateTempPos;
  FDisableEnvironment:=true;
  FPreRenderZone:=nil;
  BackSpeed:=0;FBackOffsetX:=0;FBackOffsetY:=0;
end;

destructor DQE2_Zone.Destroy;
begin
  FreeAndNil(FObjects);
  FreeAndNil(FStartScripts);
  FreeAndNil(FThumbnail);
  inherited;
end;

procedure DQE2_Zone.SetBackSpeed(const Value: SmallInt);
begin
  FBackSpeed := Value;
  FCurBackSpeed:=Value/100;
end;

procedure DQE2_Zone.SetColor(const Value: TColor);
begin
  FColor := Value;
  CurColor:=FColor or (255 shl 24);
end;

procedure DQE2_Zone.SetNewPos(L, T, R, B: Word);
var
  a:integer;
  zn:DQE2_Zone;
begin
  if(L<=World._maxzonewidth)and(R<=World._maxzonewidth)and
    (T<=World._maxzoneheight)and(B<=World._maxzoneheight)and
    ((R-L)>=World.ScreenHeight)and((B-T)>=World.ScreenHeight)then
  begin
    for a:=0 to world.zones.count-1 do
    begin
      zn:=DQE2_Zone(world.Zones[a]);
      if zn<>self then
        if(r>zn.FLeft)and(zn.FRight>l)and(b>zn.FTop)and(zn.FBottom>t)then
          exit;
    end;
    FLeft:=L;FRight:=R;FTop:=T;FBottom:=B;
    FWidth:=FRight-FLeft;FHeight:=FBottom-FTop;
    UpdateTempPos;
  end;
end;

procedure DQE2_Zone.UpdateTempPos;
begin
  FTempL:=FLeft-25;FTempR:=FRight-25;FTempT:=FTop-25;FTempB:=FBottom-25;
  FTempW:=FWidth-25;FTempH:=FHeight-25;
end;

{ DQE2_Player }

procedure DQE2_Player.AssignTo(Dest: TPersistent);
var
  x:DQE2_Player;
begin
  inherited;
  if not(Dest is DQE2_Player) then exit;
  x:=Dest as DQE2_Player;
  x.FMaxLifes:=FMaxLifes;
  x.FMaxEnergy:=FMaxEnergy;
  x.FLifes:=FLifes;
  x.FEnergy:=FEnergy;
  x.FControl:=FControl;
  x.FOnEnergyChange:=FOnEnergyChange;
  x.FOnSlowEnergyChange:=FOnSlowEnergyChange;
  x.FOnLifesChange:=FOnLifesChange;
end;

function DQE2_Player.CheckCollision: boolean;
var
  a,ox,oy,_fs,_as,climb,x1,dx,n:integer;
  obj,lst:DQE2_Object;
  ok:boolean;
  eff:DQE2_Effect;
  dmg:boolean;

function RectCol(dir:boolean;p1,p2,p3:integer):boolean;
var
  _a,_b:integer;
begin
  for _a:=0 to n do
  begin
    obj:=DQE2_Object(world.CurrentZone.FObjects[_a]);
    if(obj.FCollider)then
      if dir then
      begin
        _b:=p1;
        while _b<p2 do
        begin
          if obj.PixelCollision(p3,_b) then begin result:=true;lst:=obj;exit;end;
          _b:=_b+5;
        end;
      end
      else
      begin
        _b:=p1;
        while _b<p2 do
        begin
          if obj.PixelCollision(_b,p3) then begin result:=true;lst:=obj;exit;end;
          _b:=_b+5;
        end;
      end;
  end;
  result:=false;
end;

begin
  FFootHold:=nil;
  n:=World.FCurrentZone.FObjects.Count;
  if n=0 then
  begin
    FX:=FX+FlySpeed;
    FY:=FY-sgn(FlyTime)*Dairspeed;
    result:=false;
    exit;
  end
  else dec(n);
  ox:=FX;oy:=FY;_fs:=sgn(FlySpeed);_as:=-Sgn(FlyTime);climb:=0;
  // damage check
  dmg:=false;
  for a:=0 to n do
  begin
    obj:=DQE2_Object(world.FCurrentZone.FObjects[a]);
    if obj.Damage<>0 then
      if CollideWith(obj) then
      begin
        Energy:=FEnergy-obj.Damage;
        if FEnergy>0 then
          dmg:=true
        else
        begin
          result:=true;
          exit;
        end;
      end;
  end;
  if World.FSoundWound<>nil then
    if dmg then
    begin
      if World.FSoundWound.MusicState=msIdle then
        World.FSoundWound.Play(0);
    end
    else
      World.FSoundWound.Stop;
  for a:=0 to world.Effects.Count-1 do
  begin
    eff:=DQE2_Effect(world.Effects[a]);
    if eff.Damage<>0 then
      if CollideWith(eff) then
        Energy:=FEnergy-eff.Damage;
  end;
  // check by "X"
  ok:=true;
  if _fs<>0 then
  begin
    if _fs<0 then x1:=DRect.Left else x1:=DRect.Right;
    dx:=_fs*10;
    for a:=1 to DSpeed do
    begin
      ox:=ox+_fs;
      if RectCol(true,DRect.Top+oy,DRect.Bottom+oy,x1+ox) then
      begin
        while TRUE do
        begin
          IF climb>DClimb then begin ok:=false;break;end;
          oy:=oy-1;inc(climb,1);
          if not RectCol(true,DRect.Top+oy,DRect.Bottom+oy,x1+ox+dx) then
            if not RectCol(false,DRect.Left+ox+dx,DRect.Right+ox+dx,DRect.Bottom+oy) then
              if not RectCol(false,DRect.Left+ox+dx,DRect.Right+ox+dx,DRect.Top+oy) then
                break;
        end;
      end;
      if not ok then begin oy:=FY;ox:=ox-_fs;break;end;
    end;
  end;
  // check by "Y"
  ok:=true;
  if _as<>0 then
  begin
    if _as<0 then x1:=DRect.Top else x1:=DRect.Bottom;
    for a:=1 to DAirSpeed do
    begin
      oy:=oy+_as;
      if RectCol(false,DRect.left+ox,DRect.right+ox,x1+oy) then
      begin
        oy:=oy-_as;
        if _as>0 then FFootHold:=lst;
        ok:=false;
        break;
      end
    end;
  end;
  FX:=OX;FY:=OY;result:=not ok;
end;

function DQE2_Player.CollideWith(obj: DQE2_Graphical): boolean;
var
  b,b1,b2,b3,b4:integer;
begin
  b1:=FX+DRect.left-3;b2:=FX+DRect.Right+3;
  b3:=FY+DRect.top-3;b4:=FY+DRect.Bottom+3;
  b:=b1;
  while b<b2 do
  begin
    if obj.PixelCollision(b,b3)then begin result:=true;exit;end else
    if obj.PixelCollision(b,b4)then begin result:=true;exit;end;
    b:=b+5;
  end;
  b:=b3;
  while b<b4 do
  begin
    if obj.PixelCollision(b1,b)then begin result:=true;exit;end else
    if obj.PixelCollision(b2,b)then begin result:=true;exit;end;
    b:=b+5;
  end;
  result:=false;
end;

constructor DQE2_Player.Create;
begin
  inherited Create(nil);
  Sprite:=World.FPlayerSprite;
  if not Sprite.Loaded then Sprite.Load;
  FX:=0;FY:=0;
  FAnimSpeed:=10;FAnimNum:=0;
  FlySpeed:=0;OnFly:=false;Dead:=false;FlyTime:=0;Rolling:=false;
  FFootHold:=nil;
  FBag:=DQE2_Bag.Create;FName:='Dizzy';
  FSaved:=false;
  FLastZone:=world.FCurrentZone;
  FLastX:=0;FLastY:=0;
  FMaxEnergy:=1000;FEnergy:=1000;FSlowEnergy:=0;
  FMaxLifes:=5;FLifes:=5;FControl:=true;
  FOnEnergyChange:=nil;FOnSlowEnergyChange:=nil;FOnLifesChange:=nil;
  FOnCustomStep:=nil;
end;

destructor DQE2_Player.Destroy;
begin
  FreeAndNil(FBag);
  inherited;
end;

procedure DQE2_Player.MoveToZone(NewZone: DQE2_Zone; NewX, NewY: Smallint);
begin
  World.CurrentZone:=newZone;
  World.px:=NewZone.Left+NewX;
  World.pY:=NewZone.Top+NewY;
  FSaved:=false;OnFly:=true;FFootHold:=nil;
end;

procedure DQE2_Player.SetEnergy(const Value: Integer);
begin
  if Value>FMaxEnergy then FEnergy:=FMaxEnergy else
  if Value<=0 then
  begin
    FEnergy:=0;
    FAnimPos:=0;
    FAnimNum:=6;
    FAnimFrame:=DFrame[FAnimNum,0];
    FDeadTimer:=250;
    OnFly:=false;
    FlySpeed:=0;
    FlyTime:=0;
    Rolling:=false;
    World.StopAllPlayerSounds;
    if World.SoundDie<>nil then World.SoundDie.Play;
  end
  else
    FEnergy := Value;
  if Assigned(FOnEnergyChange) then FOnEnergyChange;
end;

procedure DQE2_Player.SetMaxEnergy(const Value: Word);
begin
  if Value>0 then
  begin
    FMaxEnergy := Value;
    if FEnergy>FMaxEnergy then Energy:=FMaxEnergy;
  end;
end;

procedure DQE2_Player.SetMaxLifes(const Value: byte);
begin
  if Value>0 then
  begin
    FMaxLifes := Value;
    if FLifes>FMaxLifes then FLifes:=FMaxLifes;
  end;
end;

procedure DQE2_Player.Step;
var
  b,n:byte;
  col:boolean;
  it:dqe2_item;
  a,c:integer;
begin
  inherited;
  // energy change handling
  a:=FEnergy-FSlowEnergy;
  if a<>0 then
  begin
    if a>0 then
      if a>10 then
        FSlowEnergy:=FSlowEnergy+10
      else
        FSlowEnergy:=FSlowEnergy+a
    else
      if a<-10 then
        FSlowEnergy:=FSlowEnergy-10
      else
        FSlowEnergy:=FSlowEnergy+a;
    if Assigned(FOnSlowEnergyChange)then FOnSlowEnergyChange();
  end;
  if(world.State=wsPlay)and(world.CurrentDialog=nil)and(not FBag.Running)then
  begin
    if FControl then
    begin
      // custom controls
      if Assigned(FOnCustomStep)then
      begin
        FOnCustomStep();
        exit;
      end
      else
      // standart controls
      if(not OnFly)and(not Rolling)then
      begin
        if world.FKeysReady and world.FKeyFire then
        begin
          for c:=0 to world.Items.Count-1 do
          begin
            it:=dqe2_item(world.Items[c]);
            if (it.Zone=world.CurrentZone)and(it.InBagIndex=-1)and(it.Visible) then
              if((it.X<(FX+50))and((it.X+it.Width)>FX)and
                 (it.Y<(FY+50))and((it.Y+it.Height)>FY))then
              begin
                it.PutInBag;Bag.Run(-1);
                exit;
              end;
          end;
          if not UseItem(nil) then
            Bag.Run(0);
        end;
        if FDeadTimer=0 then
        begin
          if world.FKeyJump then
          begin
            if World.FSoundJump<>nil then World.FSoundJump.Play;
            Rolling:=true;
            Onfly:=true;
            FlyTime:=Dair;
          end;
          if world.FKeyLeft then
          begin
            if Onfly then begin rolling:=true;b:=5;end else b:=2;
            if world.FSoundWalk<>nil then
              if world.FSoundWalk.MusicState=msIdle then
                world.FSoundWalk.Play(0)
              else
                world.FSoundWalk.UnPause;
            if FAnimNum<>b then
            begin
              FAnimPos:=0;
              FAnimNum:=b;
              FAnimFrame:=DFrame[FAnimNum,0];
              FlySpeed:=-DSpeed;
            end;
          end
          else
          if world.FKeyRight then
          begin
            if Onfly then begin rolling:=true;b:=4;end else b:=1;
            if world.FSoundWalk<>nil then
              if world.FSoundWalk.MusicState=msIdle then
                world.FSoundWalk.Play(0)
              else
                world.FSoundWalk.UnPause;
            if FAnimNum<>b then
            begin
              FAnimPos:=0;
              FAnimNum:=b;
              FAnimFrame:=DFrame[FAnimNum,0];
              FlySpeed:=Dspeed;
            end;
          end
          else
          begin
            if Onfly then b:=3 else b:=0;
            if world.FSoundWalk<>nil then
              world.FSoundWalk.Pause;
            if FAnimNum<>b then
            begin
              FAnimPos:=0;
              FAnimNum:=b;
              FAnimFrame:=DFrame[FAnimNum,0];
              FlySpeed:=0;
            end;
          end;
        end
      end
      else
      begin
        if world.FSoundWalk<>nil then
          world.FSoundWalk.Pause;
      end;
      // common end
      dec(FlyTime);
      if FDeadTimer=0 then
      begin
        col:=CheckCollision;
        n:=trunc(FAnimFrame);
        if col then
          OnFly:=false
        else
          OnFly:=true;
        if FFootHold<>nil then if Rolling then
        if(n=DFrame[3,0])or(n=DFrame[4,0])or(n=DFrame[5,0])then
        begin
          FAnimPos:=0;
          Rolling:=false;
        end;
        if(not FSaved)and(FFootHold<>nil)then
        begin
          FSaved:=true;
          FLastZone:=world.CurrentZone;
          FLastX:=FX;
          FLastY:=FY;
        end;
      end
      else
      begin
        FDeadTimer:=FDeadTimer-1;
        if FDeadTimer=0 then
        begin
          Energy:=FMaxEnergy;
          if FLifes>0 then FLifes:=FLifes-1;
          MoveToZone(FLastZone,FLastX,FLastY);
          FAnimNum:=0;FAnimFrame:=0;
          if Assigned(FOnLifesChange) then FOnLifesChange;
          exit;
        end;
      end;
    end;
    // standart animation
    if FAnimNum=0 then
      FAnimPos:=FAnimPos+DAnimSpeed div 2
    else
      FAnimPos:=FAnimPos+DAnimSpeed;
    if FAnimPos>=100 then
    begin
      FAnimPos:=FAnimPos-100;inc(FAnimFrame,1);
      if FAnimFrame>=DFrame[FAnimNum,1] then
        if FDeadTimer=0 then
          FAnimFrame:=DFrame[FAnimNum,0]
        else
          FAnimFrame:=DFrame[FAnimNum,1];
    end;
  end;
end;

function DQE2_Player.UseItem(Item:DQE2_Item): boolean;
var
  a:integer;
begin
  world.FKeysReady:=true;
  if Item<>nil then Item.Drop;
  for a:=0 to world.RunningScripts.Count-1 do
    if world.RunningScripts[a]<>nil then
      if DQE2_Script(world.RunningScripts[a]).OnUse(Item) then
        begin result:=true;exit;end;
  result:=false;
end;

{ DQE2_Dialog }

function DQE2_Dialog.AddAnswer(InShort, InLong: string):integer;
begin
  FAnswerTitles.Add(InShort);
  FAnswers.Add(InLong);
  inc(FAnswersCount);
  result:=FAnswersCount;
end;

function DQE2_Dialog.Answer(Q, A: integer):boolean;
var
  res:integer;
begin
  CurText.Clear;
  FFirstAnswer:=0;
  result:=true;
  if Assigned(FOnCustomAnswer) then
    res:=FOnCustomAnswer(Self,Q,A)
  else
    if not Exec(FFuncAnswer,[Q,A],res,false,1) then exit;
  _OnAnswer(Q,A,res);
end;

procedure DQE2_Dialog.AssignTo(Dest: TPersistent);
var
  x:DQE2_Dialog;
begin
  inherited;
  if not(Dest is DQE2_Dialog) then exit;
  x:=Dest as DQE2_Dialog;
  x.FQuestions.Assign(FQuestions);
  x.FAnswers.Assign(FAnswers);
  x.FAnswerTitles.Assign(FAnswerTitles);
  x.FPersonName:=FPersonName;
  x.FOnCustomPrepare:=FOnCustomPrepare;
  x.FOnCustomAnswer:=FOnCustomAnswer;
end;

function DQE2_Dialog.Compile(AdditionalCode: string=''): boolean;
begin
  result:=inherited Compile(AdditionalCode);
  if result then
  begin
    FFuncAnswer:=FProg.GetMemberID(FuncAnswerName);
    FFuncPrepare:=FProg.GetMemberID(FuncPrepareName);
  end
  else
  begin
    FFuncAnswer:=0;FFuncPrepare:=0;
  end;
end;

constructor DQE2_Dialog.Create(Collection: TCollection);
begin
  inherited;
  FChanged:=true;
  FCurAnswers:=TStringList.Create;
  FPlayerSpeech:=false;
  CurText:=TStringList.Create;
  FQuestions:=TStringList.Create;
  FAnswers:=TStringList.Create;
  FAnswerTitles:=TStringList.Create;
  FAnswersCount:=0;CurPerson:='';
  FOnCustomPrepare:=nil;FOnCustomAnswer:=nil;
end;

function DQE2_Dialog.DeleteAnswer(Index: integer): integer;
begin
  FAnswers.Delete(index);
  FAnswerTitles.Delete(Index);
  dec(FAnswersCount);
  result:=FAnswersCount;
end;

destructor DQE2_Dialog.Destroy;
begin
  if World.FCurrentDialog=self then Stop;
  FreeAndNil(FCurAnswers);
  FreeAndNil(CurText);
  FreeAndNil(FQuestions);
  FreeAndNil(FAnswers);
  FreeAndNil(FAnswerTitles);
  inherited;
end;

function DQE2_Dialog.GetLong(Index: integer): string;
begin
  result:=FAnswers[Index];
end;

function DQE2_Dialog.GetShort(Index: integer): string;
begin
  result:=FAnswerTitles[Index];
end;

function DQE2_Dialog.OnBreakCustomAction(FuncID: integer;
  const Params: array of const): boolean;
begin
  result:=inherited OnBreakCustomAction(FuncID,Params);
  if FBreakFuncID=FFuncAnswer then
  begin
    _LastQ:=TVarRec(Params[0]).VInteger;
    _LastA:=TVarRec(Params[1]).VInteger;
  end
  else
  if FBreakFuncID=FFuncPrepare then
    _LastQ:=TVarRec(Params[0]).VInteger;
end;

function DQE2_Dialog.Resume(NextLine:boolean=false): shortint;
begin
  result:=inherited Resume(NextLine);
  if result>0 then
  begin
    if FBreakFuncID=FFuncAnswer then
      _OnAnswer(_LastQ,_LastA,integer(FProg.GetLastResult))
    else if FBreakFuncID=FFuncPrepare then
      _Prepare;
    FBreakFuncID:=0;
  end;
end;

function DQE2_Dialog.Run:boolean;
begin
  if world.CurrentDialog<>nil then
  begin
    result:=false;DoMsg(5,false);exit;
  end;
  if FUseScriptText then
    if not Compile then
      begin result:=false;exit;end;
  inherited Run;
  world.FCurrentDialog:=Self;
  World.StopAllPlayerSounds;
  FRunning:=true;
  FCurAns:=0;FCurQst:=0;
  world.W_Question.X:=((world.FScreenWidth-world.W_Question.Width)div 2)+world.OffsetX;
  world.W_Question.Y:=10+World.OffsetY;
  world.W_Answer.X:=((world.FScreenWidth-world.W_Answer.Width)div 2)+world.OffsetX;
  world.W_Answer.Y:=world.FScreenHeight-world.W_Answer.Height-10+World.OffsetY;
  CurPerson:=FPersonName;
  Answer(-1,-1);
  world.FKeysReady:=False;
  result:=true;
end;

procedure DQE2_Dialog.Step;
var
  a,x,y,n:integer;
  g:DQE2_Graphical;
begin
  // dialogue render
  world.Canvas.PutPixel(0,0,0,0);
  g:=World.W_Question;
  g.DoAnimate;
  world.PutSprite(g.FSprite.FImages.Item[0],
    g.X,g.Y,g.FWidth,g.FHeight,g.CenterX,g.CenterY,g.Angle,
    g.Alpha,g.Color,g.DrawFX,g.FlipHoriz,g.FlipVert);
  g:=world.W_Answer;
  g.DoAnimate;
  world.PutSprite(g.FSprite.FImages.Item[0],
  g.X,g.Y,g.FWidth,g.FHeight,g.CenterX,g.CenterY,g.Angle,
  g.Alpha,g.Color,g.DrawFX,g.FlipHoriz,g.FlipVert);
  // i don't know why it must be here, but that's not working without that pixel :)
  world.Canvas.PutPixel(0,0,0,0);
  x:=world._width_div2-world.F_PersonName.FFont.TextWidth(CurPerson) div 2;y:=25;
  world.F_PersonName.FFont.TextOut(CurPerson,x,y,$FF0000FF);
  for a:=0 to CurText.Count-1 do
  begin
    x:=world._width_div2-world.F_Question.FFont.TextWidth(CurText.Strings[a])div 2;
    y:=150-(CurText.Count*20)div 2+a*20;
    world.F_Question.FFont.TextOut(CurText.Strings[a],x,y,$FFFFFFFF);
  end;
  if FPlayerSpeech then
  begin
    x:=world.W_Answer.X-world.OffsetX+20;
    y:=world.W_Answer.Y-world.OffsetY+20;
    world.F_Answer.FFont.TextOut('...',x,y,$FFFFFFFF)
  end
  else
  begin
    for a:=0 to _dispanswers-1 do
    begin
      x:=world.W_Answer.X-world.OffsetX+20;
      y:=world.W_Answer.Y-world.OffsetY+20*(a+1);
      if a=Fcursel then
        world.F_Answer.FFont.TextOut(FCurAnswers.Strings[a+FFirstAnswer],x,y,$FFFFFFFF)
      else
        world.F_Answer.FFont.TextOut(FCurAnswers.Strings[a+FFirstAnswer],x,y,$FF00FFFF);
    end;
  end;
  // keys processing
  if world.FKeysReady then
    if(world.FKeyUp)and(FPlayerSpeech=false)and(FCurSel<>0) then
      if(FCurSel=DialogScrollEdge)and(FFirstAnswer>0)then
        FFirstAnswer:=FFirstAnswer-1
      else
        FCurSel:=FCurSel-1
    else
    if(world.FKeyDown)and(FPlayerSpeech=false)and
      (FCurSel<>(MaxDialogAnswers-1))and(FCurSel<>(FCurAnswers.Count-1)) then
      if(FCurSel=DialogScrollEdge)and(FFirstAnswer<(FCurAnswers.Count-MaxDialogAnswers))then
        FFirstAnswer:=FFirstAnswer+1
      else
        FCurSel:=FCurSel+1
    else
    if(world.FKeyFire)then
    begin
      if FPlayerSpeech then
      begin
        Answer(FCurQst,FCurAns);
        FPlayerSpeech:=false;
      end
      else
      begin
        n:=FAnswerTitles.IndexOf(FCurAnswers.strings[FCurSel+FFirstAnswer]);
        WordWrapText(world.F_Question.FFont,FAnswers.strings[n],570,CurText);
        FCurAns:=n;FCurSel:=0;CurPerson:=World.Player.Name;
        FPlayerSpeech:=true;
      end;
    end;
end;

procedure DQE2_Dialog.Stop;
begin
  if world.FCurrentDialog<>Self then exit;
  inherited Stop;
  world.FCurrentDialog:=nil;
  FRunning:=false;
  World.FKeysReady:=false;
end;

procedure DQE2_Dialog.WriteLong(Index: integer; const Value: string);
begin
  FAnswers.Strings[Index]:=Value;
end;

procedure DQE2_Dialog.WriteShort(Index: integer; const Value: string);
begin
  FAnswerTitles.Strings[Index]:=Value;
end;

procedure DQE2_Dialog._OnAnswer(Q, A, res: integer);
var
  tmp:integer;
begin
  if(res<0)or(res>=FQuestions.Count)then
    begin DoMsg(3,true);Stop;end
  else
  begin
    FCurQst:=res;
    WordWrapText(world.F_Question.FFont,FQuestions.Strings[FCurQst],565,CurText);
    CurPerson:=FPersonName;
    FCurAnswers.Clear;
    if Assigned(FOnCustomPrepare) then
      FOnCustomPrepare(Self,res)
    else
      if not Exec(FFuncPrepare,[res],tmp,false,2) then exit;
    _Prepare;
  end;
end;

procedure DQE2_Dialog._Prepare;
begin
  if FCurAnswers.Count=0 then
    DoMsg(4,false)
  else
  begin
    _dispanswers:=min(FCurAnswers.Count,MaxDialogAnswers);
    FCurSel:=0;
  end;
end;

{ DQE2_Script }

procedure DQE2_Script.AssignTo(Dest: TPersistent);
var
  x:DQE2_Script;
begin
  inherited;
  if not(Dest is DQE2_Script) then exit;
  x:=Dest as DQE2_Script;
  x.FOnCustomInit:=FOnCustomInit;
  x.FOnCustomStep:=FOnCustomStep;
  x.FOnCustomUse:=FOnCustomUse;
  x.FOnCustomPreRender:=FOnCustomPreRender;
  x.FOnCustomPostRender:=FOnCustomPostRender;
end;

function DQE2_Script.Compile(AdditionalCode: string): boolean;
begin
  result:=inherited Compile(AdditionalCode);
  if result then
  begin
    FFuncRun:=FProg.GetMemberID(FuncRunName);
    FFuncStep:=FProg.GetMemberID(FuncStepName);
    FFuncUse:=FProg.GetMemberID(FuncUseName);
    FFuncPrerender:=FProg.GetMemberID(FuncPrerenderName);
    FFuncPostrender:=FProg.GetMemberID(FuncPostRenderName);
  end
  else
  begin
    FFuncRun:=0;FFuncStep:=0;FFuncUse:=0;
    FFuncPrerender:=0;FFuncPostrender:=0;
  end;
end;

constructor DQE2_Script.Create(Collection: TCollection);
begin
  inherited;
  FRunning:=false;FSuspended:=false;FPassCount:=0;
  FOnCustomInit:=nil;FOnCustomStep:=nil;FOnCustomUse:=nil;
  FOnCustomPreRender:=nil;FOnCustomPostRender:=nil;
end;

destructor DQE2_Script.Destroy;
var
  a,b:integer;
  zn:dqe2_zone;
begin
  if FRunning then Stop;
  for a:=0 to world.Zones.Count-1 do
  begin
    zn:=dqe2_zone(world.zones[a]);
    for b:=0 to zn.StartScripts.Count-1 do
      if zn.StartScripts[b]=Self then
        zn.StartScripts[b]:=nil;
    zn.StartScripts.Pack;zn.StartScripts.Capacity:=zn.StartScripts.Count;
  end;
  for a:=0 to World.FRunningScripts.Count-1 do
    if World.FRunningScripts[a]=Self then
      World.FRunningScripts[a]:=nil;
  if World.FCurrentScript=Self then
    World.FCurrentScript:=nil;
  inherited;
end;

function DQE2_Script.GetCurrentStdFunctionName: string;
begin
  if FBreakFuncID=FFuncRun then result:=FuncRunName else
  if FBreakFuncID=FFuncStep then result:=FuncStepName else
  if FBreakFuncID=FFuncUse then result:=FuncUseName else
  if FBreakFuncID=FFuncPrerender then result:=FuncPrerenderName else
  if FBreakFuncID=FFuncPostrender then result:=FuncPostRenderName else
    result:='';
end;

function DQE2_Script.OnBreakCustomAction(FuncID: integer;const Params:array of const): boolean;
begin
  result:=inherited OnBreakCustomAction(FuncID,Params);
  if FuncID=FFuncUse then
    FBreakItem:=DQE2_Item(TVarRec(Params[0]).VPointer);
end;

function DQE2_Script.OnPostRender:boolean;
var
  res:integer;
begin
  world.FCurrentScript:=Self;
  if Assigned(FOnCustomPostRender) then
    FOnCustomPostRender(Self)
  else
    if FFuncPostrender<>0 then
      Exec(FFuncPostrender,[],res,false,11);
end;

function DQE2_Script.OnPreRender: boolean;
var
  res:integer;
begin
  world.FCurrentScript:=Self;
  if Assigned(FOnCustomPreRender) then
    FOnCustomPreRender(Self)
  else
    if FFuncPrerender<>0 then
      Exec(FFuncPrerender,[],res,false,12);
end;

function DQE2_Script.OnUse(Item:DQE2_Item):boolean;
var
  res:integer;
begin
  world.FCurrentScript:=Self;
  if Item<>nil then Item.Drop;
  if Assigned(FOnCustomUse) then
    res:=FOnCustomUse(Self,Item)
  else
    if FFuncUse=0 then
    begin
      result:=false;
      exit;
    end
    else
      if not Exec(FFuncUse,[Item],res,false,3) then
      begin
        result:=false;
        exit;
      end;
  _OnUse(Item,res);
  result:=(res<>0);
end;

function DQE2_Script.Resume(NextLine:boolean=false): shortint;
begin
  result:=inherited Resume(NextLine);
  if result>0 then // if leaving debug
  begin
    if FBreakFuncID=FFuncRun then
      _Run(boolean(FProg.GetLastResult)) else
    if FBreakFuncID=FFuncStep then
      _Step(boolean(FProg.GetLastResult)) else
    if FBreakFuncID=FFuncUse then
      _OnUse(FBreakItem,integer(FProg.GetLastResult));
    FBreakFuncID:=0;
  end;
end;

function DQE2_Script.Run(AutoStop: boolean):boolean;
var
  res:integer;
begin
  if FUseScriptText then
    if not Compile then
    begin
      result:=false;
      exit;
    end;
  FAutoStop:=AutoStop;
  world.FCurrentScript:=Self;
  result:=true;
  if Assigned(FOnCustomInit) then
    res:=integer(FOnCustomInit(Self))
  else
    if not Exec(FFuncRun,[],res,true,1) then exit;
  _Run((res<>0));
end;

function DQE2_Script.Step:boolean;
var
  res:integer;
begin
  world.FCurrentScript:=Self;
  result:=true;
  if Assigned(FOnCustomStep) then
    res:=integer(FOnCustomStep(Self))
  else
    if not Exec(FFuncStep,[],res,false,2)then exit;
  _Step(res<>0);
end;

procedure DQE2_Script.Stop;
begin
  inherited Stop;
end;

procedure DQE2_Script._OnUse(Item:DQE2_Item;res: integer);
begin
  if res<0 then
  begin
    if Item<>nil then Item.Visible:=false;
    DoMsg(8,true); // item was used and should be deleted
  end
  else
  if res>0 then
  begin
    if Item<>nil then Item.PutInBag;
    DoMsg(7,true); // item was used, but should be returned to the bag
  end
  else
    DoMsg(6,true); // can't use the item
end;

procedure DQE2_Script._Run(res: boolean);
begin
  if res=false then
  begin
    DoMsg(4,true);Stop;FRunning:=false;exit; // script not allow to proceed
  end
  else
  begin
    FRunning:=true;
    FSuspended:=false;
    FPassCount:=0;
    FIndexInList:=world.FRunningScripts.IndexOf(Self);
    if FIndexInList=-1 then
    begin
      FIndexInList:=world.FRunningScripts.Count;
      world.FRunningScripts.Add(self);
    end;
    DoMsg(0,true);
  end;
end;

procedure DQE2_Script._Step(res: boolean);
begin
  if res=false then
  begin
    World.RunningScripts[FIndexInList]:=nil;
    FRunning:=false;
  end
  else
    inc(FPassCount);
end;

{ DQE2_Item }

procedure DQE2_Item.AssignTo(Dest: TPersistent);
var
  x:DQE2_Item;
begin
  inherited;
  if not(Dest is DQE2_Item) then exit;
  x:=Dest as DQE2_Item;
  x.FZone:=FZone;
  x.FDescription:=FDescription;
  x.FRealName:=FRealName;
end;

procedure DQE2_Item.BringToFront;
begin
  Index:=World.Items.Count-1;
end;

constructor DQE2_Item.Create(Collection: TCollection);
begin
  inherited;
  Fsprite:=nil;FZone:=nil;FDescription:='';
  FX:=0;Fy:=0;FInBagIndex:=-1;FVisible:=true;
end;

destructor DQE2_Item.Destroy;
begin
  Drop;
  inherited;
end;

procedure DQE2_Item.Drop;
begin
  if FInBagIndex>=0 then
  begin
    world.Player.Bag.Items[FInBagIndex]:=nil;FInBagIndex:=-1;
    world.PackItems;
    FZone:=world.CurrentZone;
    X:=world.Player.X;
    Y:=world.Player.Y+45-FHeight;
  end;
end;

procedure DQE2_Item.PutInBag;
begin
  if(FInBagIndex=-1)and(world.Player.Bag.Capacity>0)then
  begin
    Visible:=true;
    if world.Player.Bag.items.Count=world.Player.bag.Capacity then
      DQE2_Item(world.Player.Bag.Items[0]).Drop;
    FInBagIndex:=world.Player.Bag.items.Count;
    world.Player.Bag.items.Add(Self);
  end;
end;

procedure DQE2_Item.SendToBack;
begin
  Index:=0;
end;

procedure DQE2_Item.SetVisible(const Value: boolean);
begin
  FVisible := Value;
  if not FVisible then Drop;
end;

procedure DQE2_Item.Step;
begin
  inherited;
  DoAnimate;
  if FInBagIndex=-1 then world.PutSprite(Self);
end;

{ DQE2_Graphical }

procedure DQE2_Graphical.AssignTo(Dest: TPersistent);
var
  x:DQE2_Graphical;
begin
  inherited;
  if not(Dest is DQE2_Graphical) then exit;
  x:=Dest as DQE2_Graphical;
  x.Sprite:=FSprite;
  x.FX:=FX;
  x.FY:=FY;
  x.FAlpha:=FAlpha;
  x.FDrawFX:=FDrawFX;
  x.FColor:=FColor;
  x.FAnimFrame:=FAnimFrame;
  x.FAnimCount:=FAnimCount;
  x.FAnimStart:=FAnimStart;
  x.FAnimEnd:=FAnimEnd;
  x.FAnimSpeed:=FAnimSpeed;
  x.FAnimPos:=FAnimPos;
  x.FAngle:=FAngle;
  x.FFlipVert:=FFlipVert;
  x.FFlipHoriz:=FFlipHoriz;
  x.FCenterX:=FCenterX;
  x.FCenterY:=FCenterY;
  x.FState:=FState;
  x.FChanged:=FChanged;
  x.FVisible:=FVisible;
  x.FSaveName:=FSaveName;
  x.ChangePos;
  x.ChangeColor;
end;

procedure DQE2_Graphical.ChangeColor;
begin
  CurColor:=GetColor(FColor,FAlpha);
end;

procedure DQE2_Graphical.ChangePos;
begin
  FLocalMatrix:=GetPos(FX,FY,FWidth,FHeight,FCenterX,FCenterY,{Cur}Angle,FFlipHoriz,FFlipVert);
end;

function DQE2_Graphical.PixelCollision(_x, _y: integer): boolean;
var
  mx,my,xx,yy:integer;
  SinVal,CosVal:Single;
begin
  if FAngle=0 then
  begin
    mx:=_x-FX;my:=_y-FY;
  end
  else
  begin
    SinVal:=_sin[360-FAngle];
    CosVal:=_cos[360-FAngle];
    xx:=_X-FCenterX-FX;
    yy:=_Y-FCenterY-FY;
    mx:=trunc(CosVal*xx-SinVal*yy+FCenterX);
    my:=trunc(SinVal*xx+CosVal*yy+FCenterY);
  end;
  if(mx>=0)and(mx<FWidth)and(my>=0)and(my<FHeight)then
    if FSprite.DefaultTransparent then
    begin
      if FFlipVert then mx:=FWidth-mx-1;
      if FFlipHoriz then my:=FHeight-my-1;
      result:=FSprite.FMask[FAnimFrame,mx,my];
    end
    else
      result:=true
  else
    result:=false;
end;

constructor DQE2_Graphical.Create(Collection: TCollection);
begin
  inherited;
  FSprite:=nil;FX:=0;FY:=0;FAlpha:=255;FDrawFX:=fxBlend;
  FAnimStart:=0;FAnimEnd:=0;FAnimSpeed:=0;FAngle:=0;
  FFlipVert:=false;FFlipHoriz:=false;FCenterX:=0;FCenterY:=0;
  FChanged:=true;FVisible:=true;FSaveName:=true;
  FColor:=$FFFFFF;
end;

destructor DQE2_Graphical.Destroy;
begin
  SetSprite(nil);
  inherited;
end;

procedure DQE2_Graphical.DoAnimate;
begin
  try
  if not FSprite.SingleFrame then
  begin
    FAnimPos:=FAnimPos+FAnimSpeed;
    if FAnimPos>=100 then
    begin
      inc(FAnimFrame);FAnimPos:=FAnimPos-100;
      if FAnimFrame>=FAnimEnd then FAnimFrame:=FAnimStart;
    end
    else
    if FAnimPos<=-100 then
    begin
      dec(FAnimFrame);FAnimPos:=FAnimPos+100;
      if FAnimFrame<FAnimStart then FAnimFrame:=FAnimEnd;
    end
  end;
  except;
  end;
end;

procedure DQE2_Graphical.LoadState;
begin
  SetX(FState.X);
  SetY(FState.Y);
  SetAlpha(FState.Alpha);
  SetAngle(FState.Angle);
  SetFlipVert(FState.FlipVert);
  SetFlipHoriz(FState.FlipHoriz);
  SetCenterX(FState.CenterX);
  SetCenterY(FState.CenterY);
  SetAnimStart(FState.AnimStart);
  SetAnimEnd(FState.AnimEnd);
  SetAnimSpeed(FState.AnimSpeed);
  SetDrawFX(FState.DrawFX);
  SetColor(FState.Color);
  FVisible:=FState.Visible;
end;

procedure DQE2_Graphical.OnSpriteChange(Value: DQE2_Sprite);
begin
  // Empty event here!  asm nop end; :)
end;

procedure DQE2_Graphical.SaveState;
begin
  FState.X:=FX;
  FState.Y:=FY;
  FState.Alpha:=FAlpha;
  FState.Angle:=FAngle;
  FState.FlipVert:=FFlipVert;
  FState.FlipHoriz:=FFlipHoriz;
  FState.CenterX:=FCenterY;
  FState.CenterY:=FCenterY;
  FState.AnimStart:=FAnimStart;
  FState.AnimEnd:=FAnimEnd;
  FState.AnimSpeed:=FAnimSpeed;
  FState.DrawFX:=FDrawFX;
  FState.Color:=FColor;
  FState.Visible:=FVisible;
end;

procedure DQE2_Graphical.SetAlpha(const Value: byte);
begin
  FAlpha:=Value;ChangeColor;
end;

procedure DQE2_Graphical.SetAngle(const Value: SmallInt);
begin
  FAngle:=trunc(alkatraz.Angle(Value));
  FChanged:=true;
end;

procedure DQE2_Graphical.SetAnimEnd(const Value: Byte);
begin
  if(Value>0)and(Value<=FAnimCount)and(Value>=FAnimStart)then
  FAnimEnd:=Value;
end;

procedure DQE2_Graphical.SetAnimSpeed(const Value: Shortint);
begin
  FAnimSpeed := Value;
end;

procedure DQE2_Graphical.SetAnimStart(const Value: Byte);
begin
  if(Value<FAnimCount)and(Value<=FAnimEnd)then
  FAnimStart:=Value;
end;

procedure DQE2_Graphical.SetCenterX(const Value: SmallInt);
begin
  FCenterX:=Value;FChanged:=true;
end;

procedure DQE2_Graphical.SetCenterY(const Value: SmallInt);
begin
  FCenterY:=Value;FChanged:=true;
end;

procedure DQE2_Graphical.SetColor(const Value: TColor);
begin
  FColor:=Value;ChangeColor;
end;

procedure DQE2_Graphical.SetDrawFX(const Value: Cardinal);
begin
  FDrawFX:=Value;
end;

procedure DQE2_Graphical.SetFlipHoriz(const Value: boolean);
begin
  FFlipHoriz:=Value;FChanged:=true;
end;

procedure DQE2_Graphical.SetFlipVert(const Value: boolean);
begin
  FFlipVert:=Value;FChanged:=true;
end;

procedure DQE2_Graphical.SetSprite(const Value: DQE2_Sprite);
begin
  if Value=FSprite then exit;
  if FSprite<>nil then dec(FSprite.FTimesUsed);
  if Value<>nil then
  begin
    inc(Value.FTimesUsed);
    if not Value.Loaded then Value.Load;
    if(FName='')or(FSaveName=false)then
      SetName(Value.DefaultName);
    FWidth:=Value.Images.Item[0].VisibleSize.X;
    FHeight:=Value.Images.Item[0].VisibleSize.Y;
    SetAlpha(Value.DefaultAlpha);
    SetAnimSpeed(Value.DefaultAnimSpeed);
    FAnimCount:=Value.Images.Count;SetAnimEnd(FAnimCount);
    FAnimFrame:=0;
    SetAngle(0);
    SetFlipVert(false);SetFlipHoriz(False);
    SetCenterX(FWidth div 2);SetCenterY(FHeight div 2);
    OnSpriteChange(Value);
  end;
  FSprite:=Value;
end;

procedure DQE2_Graphical.SetState(const Value: DQE2_ObjectState);
begin
  FState:=Value;
end;

procedure DQE2_Graphical.SetX(const Value: Smallint);
begin
  if (world.State=wsplay)and(world.Player.FFootHold=Self) then
    world.Player.X:=world.Player.x+(Value-Fx);
  FX:=Value;FChanged:=true;
end;

procedure DQE2_Graphical.SetY(const Value: Smallint);
var
  d:Smallint;
begin
  if (world.State=wsplay)and(world.Player.FFootHold=Self) then
  begin
    d:=(Value-FY);
    if d<0 then world.Player.Y:=world.Player.Y+d;
  end;
  FY:=Value;FChanged:=true;
end;

procedure DQE2_Graphical.Step;
begin
  if FChanged then ChangePos;
  FChanged:=false;
  if not FVisible then exit;
end;

{ DQE2_Bag }

procedure DQE2_Bag.AssignTo(Dest: TPersistent);
var
  x:DQE2_Bag;
begin
  inherited;
  if not(Dest is DQE2_Bag) then exit;
  x:=Dest as DQE2_Bag;
  x.Capacity:=FCapacity;
  x.FItems.Assign(FItems);
end;

procedure DQE2_Bag.ChangeSelection;
var
  n:integer;
begin
  n:=FCurSel+FFirstItem;
  if n>=FItems.Count then
    FCurItem:=nil
  else
  begin
    FCurItem:=DQE2_Item(Items[n]);
    WordWrapText(world.F_ItemText.FFont,FCurItem.Description,480,FCurText);
  end;
  UpdateBagCache;
end;

constructor DQE2_Bag.Create;
begin
  inherited Create(nil);
  FItems:=TList.Create;FCapacity:=3;
  FCurText:=TStringList.Create;
end;

destructor DQE2_Bag.Destroy;
begin
  FreeAndNil(FItems);
  FreeAndNil(FCurText);
  inherited;
end;

procedure DQE2_Bag.Run(Selected:integer);
begin
  if not world.FKeysReady then exit;
  if FCapacity=0 then exit;
  if FItems.Count>=MaxBagItems then
    _dispitems:=MaxBagItems
  else
    _dispitems:=FItems.Count+1;
  FRunning:=true;
  if Selected>=MaxBagItems then
  begin
    FCurSel:=MaxBagItems-1;
    FFirstItem:=Selected-MaxBagItems;
  end
  else
    if Selected=-1 then
      if FItems.Count>=MaxBagItems then
      begin
        FFirstItem:=FItems.Count-MaxBagItems+1;
        FCurSel:=MaxBagItems-1;
      end
      else
      begin
        FFirstItem:=0;
        FCurSel:=FItems.Count;
      end
    else
    begin
      FCurSel:=Selected;
      FFirstItem:=0;
    end;
  ChangeSelection;
  world.FKeysReady:=false;
  X:=((world.FScreenWidth-FWidth)div 2)+World.OffsetX;
  Y:=((world.FScreenHeight-FHeight)div 2)+World.OffsetY;
  World.W_Cancel.X:=FX+19+(_dispitems-1)*60;
  World.W_Cancel.Y:=FY+18;
  UpdateBagCache;
  Step;World.W_Cancel.Step;
end;

procedure DQE2_Bag.SetCapacity(const Value: byte);
var
  a:integer;
  b:integer;
begin
  b:=Items.Count-Value-1;
  for a:=0 to b do
    DQE2_Item(Items[a]).Drop;
  FCapacity := Value;
end;

function DQE2_Bag.Step: integer;
var
  a,_a:integer;
  it:DQE2_Item;
  c:Cardinal;
  _x,_y:integer;
  _end:boolean;
  n:integer;
begin
  inherited Step;
  result:=-2;
  if not FRunning then exit;
  DoAnimate;world.PutSprite(Self);
  world.FCanvas.PutPixel(0,0,0,0); {!}
  if FCol=250 then Fcol:=0 else FCol:=FCol+10;
  c:=cRGB1(FCol,255-FCol,0);
  world.Canvas.FrameRect(BagCache[FCurSel]._CurSel,FY-World.OffsetY+20,50,50,c,fxblend);
  _end:=(Fitems.Count-FFirstItem)<MaxBagItems;
  if FItems.Count<>0 then
  begin
    if _end then
      n:=_dispitems-2
    else
      n:=_dispitems-1;
    for a:=0 to n do
    begin
      it:=dqe2_item(FItems[a+FFirstItem]);
      it.DoAnimate;
      world.PutSprite(it.Sprite.images.Item[it.AnimFrame],
      BagCache[a]._IX1,BagCache[a]._IY1,it.Width,it.Height,
      0,0,0,it.Alpha,it.Color,it.DrawFX,false,false);
    end;
    if FCurItem<>nil then
    begin
      _x:=FX-World.OffsetX+100;_y:=FY-World.OffsetY+90;
      _a:=FCurItem.InBagIndex-FFirstItem;
      world.F_ItemName.FFont.TextOut(FCurItem.RealName,_x,_y,$FFFF00FF);
      for a:=0 to FCurText.Count-1 do
        World.F_ItemText.FFont.TextOut(FCurText.Strings[a],
          _x,_y+a*20+50,$FFFFFFFF);
      world.PutSprite(FCurItem.Sprite.images.Item[FCurItem.AnimFrame],
        BagCache[_a]._IX2,BagCache[_a]._IY2,
        FCurItem.Width,FCurItem.Height,0,0,0,FCurItem.Alpha,FCurItem.Color,FCurItem.DrawFX,false,false);
    end;
  end;
  if _end then
  begin
    world.W_Cancel.DoAnimate;
    world.PutSprite(world.W_Cancel);
  end;
  if world.FKeysReady then
  begin
    if World.FKeyFire then
    begin
      Stop;
      n:=FCurSel+FFirstItem;
      if n=FItems.Count then result:=-1 else result:=n;
    end
    else
    if world.FKeyCancel then
    begin
      Stop;
      result:=-1;
    end
    else
    if world.FKeyLeft then
    begin
      if FCurSel=0 then
        if FFirstItem>0 then
          FFirstItem:=FFirstItem-1
        else
          exit
      else
        FCurSel:=FCurSel-1;
      ChangeSelection;
    end
    else
    if world.FKeyRight then
    begin
      if FCurSel>=Fitems.Count then exit;
      if FCurSel>=(MaxBagItems-1) then
        if _end=false then
          FFirstItem:=FFirstItem+1
        else
          exit
      else
        FCurSel:=FCurSel+1;
      ChangeSelection;
    end
  end;
end;

procedure DQE2_Bag.Stop;
begin
  FRunning:=False;
end;

procedure DQE2_Bag.UpdateBagCache;
var
  a,n,n1:integer;
  it:DQE2_Graphical;
  _left,_top:integer;
begin
  n:=_dispitems;
  SetLength(BagCache,n+1);
  _left:=FX-World.OffsetX+20;
  _top:=FY-World.OffsetY+20;
  if (Fitems.Count-FFirstItem)<MaxBagItems then
    n1:=n-2
  else
    n1:=n-1;
  for a:=0 to n1 do
  begin
    it:=DQE2_Graphical(FItems[a+FFirstItem]);
    BagCache[a]._CurSel:=_left+a*60;
    BagCache[a]._IX1:=(_left+a*60)+(50-it.Width)div 2+World.OffsetX;
    BagCache[a]._IY1:=_top+(50-it.Height)div 2+World.OffsetY;
    BagCache[a]._IX2:=_left+(50-it.Width)div 2+World.OffsetX;
    BagCache[a]._IY2:=_top+(50-it.Height)div 2+World.OffsetY+83;
  end;
  it:=World.W_Cancel;
  BagCache[n1+1]._CurSel:=_left+(n1+1)*60;
  BagCache[n1+1]._IX1:=(_left+(n1+1)*60)+(50-it.Width)div 2+World.OffsetX;
  BagCache[n1+1]._IY1:=_top+(50-it.Height)div 2+World.OffsetY;
  BagCache[n1+1]._IX2:=_left+(50-it.Width)div 2+World.OffsetX;
  BagCache[n1+1]._IY2:=_top+(50-it.Height)div 2+World.OffsetY+83;
end;

{ DQE2_Font }

procedure DQE2_Font.AssignTo(Dest: TPersistent);
var
  x:DQE2_Font;
begin
  inherited;
  if not(Dest is DQE2_Font) then exit;
  x:=Dest as DQE2_Font;
  x.FFont.Size:=FFont.Size;
  x.FFont.FontName:=FFont.FontName;
  x.FFont.CharSet:=FFont.CharSet;
  x.FFont.Antialias:=FFont.Antialias;
  x.FFont.Style:=FFont.Style;
  x.FFont.ShadowIntensity:=FFont.ShadowIntensity;
  x.FFont.ShadowColor:=FFont.ShadowColor;
  x.FFont.ShadowDepth:=FFont.ShadowDepth;
  x.FFont.EmbossStrength:=FFont.EmbossStrength;
  x.FFont.EmbossColor:=FFont.EmbossColor;
  x.FFont.EmbossDepth:=FFont.EmbossDepth;
end;

constructor DQE2_Font.Create(Collection: TCollection);
begin
  inherited;
  FFont:=TAsphyreFontAlt.Create(world.FontStore);
  FFont.ShadowIntensity:=0.25;
  FFont.EmbossStrength:=0.25;
  FFont.Size:=36;
  FFont.FontName:='Arial';
  FFont.CharSet:=ANSI_CHARSET;
  FFont.Style:=[];
  FFont.ShadowColor:=clGray or $FF000000;
  FFont.EmbossColor:=clRed or $FF000000;
  FFont.ShadowDepth:=1;
  FFont.EmbossDepth:=1;
  FFont.Update;
end;

destructor DQE2_Font.Destroy;
begin
  FreeAndNil(FFont);
  inherited;
end;

function DQE2_Font.TextOut(X, Y: Integer; Text: string; Color: Cardinal; W,
  H: Integer):boolean;
begin
  try
    FFont.TextOut(Text,X-World.OffsetX,Y-World.OffSetY,Color);
    result:=true;
  except
    result:=false;
  end;
end;

{ DQE2_Scriptable }

procedure DQE2_Scriptable.AddBreakpoint(Line: Integer);
var
  a:integer;
begin
  for a:=0 to FBreakpointsCount-1 do
    if FBreakpoints[a]=Line then exit;
  SetLength(FBreakpoints,FBreakpointsCount+1);
  FBreakpoints[FBreakpointsCount]:=Line;
  inc(FBreakpointsCount);
  if (FProg<>nil)and FRunning then
    FProg.AddBreakpoint('main',Line-1);
end;

procedure DQE2_Scriptable.AssignTo(Dest: TPersistent);
var
  x:DQE2_Scriptable;
  a:integer;
begin
  inherited;
  if not(Dest is DQE2_Scriptable) then exit;
  x:=Dest as DQE2_Scriptable;
  x.FSuspended:=FSuspended;
  x.FAutoStop:=FAutoStop;
  x.FChanged:=FChanged;
  x.FText:=FText;
  x.FDefaultIgnore:=FDefaultIgnore;
  x.FOnStart:=FOnStart;
  x.FOnStop:=FOnStop;
  for a:=0 to FBreakpointsCount-1 do
    x.AddBreakpoint(FBreakpoints[a]);
  x.FUseScriptText:=FUseScriptText;
end;

procedure DQE2_Scriptable.ClearBreakpoints;
begin
  SetLength(FBreakpoints,0);
  FBreakpointsCount:=0;
  if FProg<>nil then
    FProg.RemoveAllBreakpoints;
end;

function DQE2_Scriptable.Compile(AdditionalCode:string=''): boolean;
var
  ok:boolean;
  xcode:string;
  a:integer;
begin
  FChanged:=true;
  try FreeAndNil(FProg);except;end;
  FProg:=TPaxScripter.Create(nil);
  Fprog.RegisterLanguage(world.FScriptLang);
  FProg.OnShowError:=OnShowError;
  FProg.RegisterObjectSimple('World',World);
  ZeroMemory(@FLastError,sizeof(FLastError));
  if FChanged then
  begin
    FProg.ResetScripter;
    FProg.Modules.Clear;
    FProg.AddModule('main','paxPascal');
    xcode:=StringReplace(FText+#13#10#13#10+AdditionalCode,'\','\\',[rfReplaceAll]);
    FProg.AddCode('main',xcode);
    result:=FProg.Compile;
    for a:=0 to FBreakpointsCount-1 do
      FProg.AddBreakpoint('main',FBreakpoints[a]-1);
    if not result then
    begin
      FLastError.IsError:=true;
      FLastError.IsSyntaxError:=true;
      FLastError.Line:=FProg.ErrorLine;
      FLastError.Row:=FProg.ErrorPos;
      FLastError.Pos:=FProg.ErrorTextPos;
      FLastError.Text:=FProg.ErrorDescription;
      FLastError.MsgID:=100;
      ok:=false;
      if Assigned(FOnError) then FOnError(Self,ok);
      if not ok then world.Stop;
    end
    else
    begin
      FLastError.IsError:=false;
      FLastError.IsSyntaxError:=false;
      FLastError.MsgID:=0;
      FLastError.Text:='0 OK 0:1';
      FChanged:=false;
    end;
  end else result:=true;
end;

constructor DQE2_Scriptable.Create(Collection: TCollection);
begin
  inherited;
  FProg:=TPaxScripter.Create(nil);
  Fprog.RegisterLanguage(world.FScriptLang);
  FProg.OnShowError:=OnShowError;
  FProg.RegisterObjectSimple('World',World);
  FChanged:=True;FDefaultIgnore:=false;
  ZeroMemory(@FLastError,sizeof(FLastError));
  FOnStart:=nil;FOnStop:=nil;
  SetLength(FBreakpoints,0);
  FBreakFuncID:=0;
  FUseScriptText:=true;
end;

destructor DQE2_Scriptable.Destroy;
begin
  FreeAndNil(FProg);
  inherited;
end;

function DQE2_Scriptable.DoMsg(msgID:integer;Def:boolean):boolean;
begin
  FLastError.IsError:=(msgID<>0);
  if FLastError.IsError then
  begin
    FLastError.IsSyntaxError:=false;
    FLastError.MsgID:=msgID;
    FLastError.Text:=FProg.ErrorDescription;
    FLastError.Line:=Fprog.ErrorLine+1;
    FLastError.Row:=fprog.ErrorPos;
    FLastError.Pos:=Fprog.ErrorTextPos;
    result:=Def;
    if(Assigned(FOnError))and(FLastError.IsError)then
      FOnError(Self,result);
    if not result then
    begin
      FChanged:=true;
      World.Stop;
    end;
  end;
end;

function DQE2_Scriptable.Eval(Expression: string; out output: Variant): boolean;
begin
  try
    if FProg<>nil then
      result:=FProg.Eval(Expression,World.FScriptLang.LanguageName,output)
    else
    begin
      output:=0;
      result:=false;
    end;
  except
    output:=0;
    result:=false;
  end;
end;

function DQE2_Scriptable.Exec(FuncID: Integer; Params: array of const;
  out FuncResult: integer;ShouldBeIdle:boolean; ExceptionErr:integer): boolean;
var
  br:boolean;
begin
  if ShouldBeIdle then
  begin
    if(FRunning)then
    begin
      result:=false;DoMsg(13,true);exit; // already running
    end;
  end
  else
  begin
    if(not FRunning)then
    begin
      result:=false;DoMsg(9,true);exit; // not running
    end;
    if(FSuspended)then
    begin
      result:=false;DoMsg(10,true);exit; // script suspended
    end;
  end;
  if(FBreakFuncID<>0)then
  begin
    result:=false;DoMsg(101,true);exit; // in debug state
  end;
  try
    FuncResult:=FProg.CallFunctionByID(FuncID,Params);
    if FProg.IsError then
      begin result:=false;DoMsg(ExceptionErr,false);exit;end;
    result:=(FProg.CurrentInstructionNumber=0);
    if result=false then
    begin
      br:=true;
      FBreakFuncID:=FuncID;
      if Assigned(World.OnBreakpoint)then
        World.OnBreakpoint(Self,br);
      if br then
      begin
        if OnBreakCustomAction(FuncID,Params) then
        begin
          FExceptionResult:=ExceptionErr;
          DoMsg(102,true); // breakpoint reached
          exit;
        end
      end
      else
        FBreakFuncID:=0;
    end;
  except
    result:=false;DoMsg(ExceptionErr,false);exit; // error while executing
  end;
  result:=true;
end;

function DQE2_Scriptable.GetBreakpoint(Index: Integer): Integer;
begin
  result:=FBreakpoints[Index];
end;

function DQE2_Scriptable.GetCurrentLine: integer;
begin
  if FProg<>nil then
    result:=FProg.CurrentSourceLine+1
  else
    result:=0;
end;

function DQE2_Scriptable.GetCurrentStdFunctionName: string;
begin
  result:='';
end;

function DQE2_Scriptable.GetInDebug: boolean;
begin
  result:=(FBreakFuncID<>0);
end;

procedure DQE2_Scriptable.GetLocals(VarsList,ValuesList:TStrings);
var
  a:integer;
  tmp:DQE2_ScriptCallbackData;
begin
  VarsList.Clear;ValuesList.Clear;
  tmp.Vars:=VarsList;tmp.Values:=ValuesList;
  if FBreakFuncID<>0 then
  begin
    FProg.EnumMembers(FProg.GetRootID,0,MembersCallback,@tmp);
    FProg.EnumMembers(FProg.GetRootID,0,ParamsCallback,@tmp);
    for a:=0 to FProg.IDCount-1 do
    try
      if FProg.IsLocalVariable(a) and IsValidIdent(FProg.GetFullName(a),true) then
      begin
        VarsList.Add(FProg.GetFullName(a));
        try
          ValuesList.Add(FProg.GetValueByID(a));
        except
          VarsList.Delete(VarsList.Count-1);
        end;
      end;
    except
    end;
  end;
end;

procedure DQE2_Scriptable.MembersCallback(const Name: string; ID: Integer;
  Kind: TPAXMemberKind; ml: TPAXModifierList; Data: Pointer);
var
  tmp:^DQE2_ScriptCallbackData;
begin
  tmp:=Data;
  try
    if Kind=mkField then
    begin
      tmp.Vars.Add(Name);
      try
        tmp.Values.Add(string(FProg.GetValueByID(ID)));
      except
        tmp.Vars.Delete(tmp.Vars.Count-1);
      end;
    end;
  except
  end;
end;

procedure DQE2_Scriptable.ParamsCallback(const Name: string; ID: Integer;
  Kind: TPAXMemberKind; ml: TPAXModifierList; Data: Pointer);
var
  tmp:^DQE2_ScriptCallbackData;
begin
  tmp:=Data;
  try
    if Kind=mkParam then
    begin
      tmp.Vars.Add(FProg.GetFullName(ID));
      try
        tmp.Values.Add(string(FProg.GetValueByID(ID)));
      except
        tmp.Vars.Delete(tmp.Vars.Count-1);
      end;
    end;
    FProg.EnumMembers(ID,0,ParamsCallback,Data);
  except
  end;
end;

function DQE2_Scriptable.OnBreakCustomAction(FuncID: integer;const Params:array of const): boolean;
begin
  result:=true;
end;

procedure DQE2_Scriptable.OnShowError(Sender: TPaxScripter);
begin
// Empty event here too!  asm nop end; one more time! :)
end;

procedure DQE2_Scriptable.RemoveBreakpoint(Line: Integer);
var
  a,b:integer;
begin
  for a:=0 to FBreakpointsCount-1 do
    if FBreakpoints[a]=Line then
    begin
      for b:=a to FBreakpointsCount-2 do
        FBreakpoints[b]:=FBreakpoints[b+1];
      FBreakpointsCount:=FBreakpointsCount-1;
      SetLength(FBreakpoints,FBreakpointsCount);
      if (FProg<>nil)and FRunning then
        FProg.RemoveBreakpoint('main',Line-1);
    end;
end;

function DQE2_Scriptable.Resume(NextLine:boolean=false): shortint; // >0-run,=0-debug,<0-error :)
begin
  if FBreakFuncID=0 then
  begin
    result:=1;DoMsg(103,true);exit; // not in debug state
  end;
  try
    if NextLine then FProg.Run(rmTraceInto) else FProg.Run;
    result:=shortint(FProg.CurrentInstructionNumber=0);
  except
    result:=-1;
    DoMsg(FExceptionResult,false);
    exit;
  end;
end;

function DQE2_Scriptable.Run: boolean;
begin
  if Assigned(FOnStart)then FOnStart(Self);result:=true;
end;

procedure DQE2_Scriptable.SetText(const Value: string);
begin
  FText := Value;
  FChanged:=True;
end;

procedure DQE2_Scriptable.Stop;
begin
  if Assigned(FOnStop)then FOnStop(Self);
  FRunning:=false;
  FBreakFuncID:=0;
end;

{ DQE2_Effect }

procedure DQE2_Effect.AssignTo(Dest: TPersistent);
var
  x:DQE2_Effect;
begin
  inherited;
  if not(Dest is DQE2_Effect) then exit;
  x:=Dest as DQE2_Effect;
  x.FnLoops:=FnLoops;
  x.FPingPong:=FPingPong;
  x.FDamage:=FDamage;
end;

constructor DQE2_Effect.Create(Collection: TCollection);
begin
  inherited;
  FnLoops:=1;FPingPong:=false;
end;

destructor DQE2_Effect.Destroy;
begin
  inherited;
end;

function DQE2_Effect.DoAnimate: boolean;
begin
  try
    FAnimPos:=FAnimPos+FAnimSpeed;
    if FAnimPos>=100 then
    begin
      inc(FAnimFrame);FAnimPos:=FAnimPos-100;
      if FAnimFrame>=FAnimEnd then
        if FnLoops>0 then
        begin
          dec(FnLoops);if FnLoops=0 then begin result:=false;exit;end;
          if FPingPong then
            begin FAnimFrame:=FAnimEnd;FAnimSpeed:=-FAnimSpeed;FAnimPos:=-FAnimPos;end
          else
            FAnimFrame:=FAnimStart;
        end;
    end
    else
    if FAnimPos<=-100 then
    begin
      dec(FAnimFrame);FAnimPos:=FAnimPos+100;
      if FAnimFrame<FAnimStart then
        if FnLoops>0 then
        begin
          dec(FnLoops);if FnLoops=0 then begin result:=false;exit;end;
          if FPingPong then
            begin FAnimFrame:=FAnimStart;FAnimSpeed:=-FAnimSpeed;FAnimPos:=-FAnimPos;end
          else
            FAnimFrame:=FAnimEnd;
        end;
    end;
  except;
  end;
  result:=true;
end;

{ DQE2_EntityType }

function DQE2_EntityType.Compile(AdditionalCode:string=''): boolean;
begin
  if FUseScriptText then
  begin
    result:=inherited Compile(AdditionalCode);
    if result then
    begin
      FFuncInit:=FProg.GetMemberID(FuncRunName);
      FFuncStep:=FProg.GetMemberID(FuncStepName);
      FFuncDie:=FProg.GetMemberID(FuncDieName);
    end
    else
    begin
      FFuncInit:=0;FFuncStep:=0;
      FFuncDie:=0;
    end;
  end
  else
    result:=true;
  FCompiled:=result;
  FRunning:=true;
end;

constructor DQE2_EntityType.Create(Collection: TCollection);
begin
  inherited;
  FOnCustomInit:=nil;FOnCustomStep:=nil;FOnCustomDie:=nil;
  FSender:=nil;
  FRunning:=true;
end;

function DQE2_EntityType.CreateState: Variant;
begin
  if Eval('DQE2_State.Create',result)=false then
    result:=0;
end;

destructor DQE2_EntityType.Destroy;
begin
  //
  inherited;
end;

procedure DQE2_EntityType.AssignTo(Dest: TPersistent);
var
  x:DQE2_EntityType;
begin
  inherited;
  if not(Dest is DQE2_EntityType) then exit;
  x:=Dest as DQE2_EntityType;
  x.FOnCustomInit:=FOnCustomInit;
  x.FOnCustomStep:=FOnCustomStep;
  x.FOnCustomDie:=FOnCustomDie;
end;

procedure DQE2_EntityType.Die(Entity: DQE2_Entity);
var
  res:integer;
begin
  if Assigned(FOnCustomDie) then
    FOnCustomDie(Entity)
  else
    if FFuncDie<>0 then
      Exec(FFuncDie,[Entity.FScriptState],res,false,3);
  FRunning:=false;
end;

procedure DQE2_EntityType.Init(Entity: DQE2_Entity);
var
  res:integer;
begin
  World.FCurrentEntity:=Entity;
  if not FCompiled then
    if not Compile() then exit;
  Entity.FScriptState:=CreateState();
  if Assigned(FOnCustomInit) then
    res:=integer(FOnCustomInit(Entity))
  else
    if not Exec(FFuncInit,[Entity.FScriptState],res,false,1)then exit;
  _Init(entity,res<>0);
end;

procedure DQE2_EntityType.Step(Entity: DQE2_Entity);
var
  res:integer;
begin
  World.FCurrentEntity:=Entity;
  if Assigned(FOnCustomStep) then
    res:=integer(FOnCustomStep(Entity))
  else {TODO: Bug with using objects in ScriptState}
    if not Exec(FFuncStep,[Entity.FScriptState],res,false,2)then exit;
  _Step(entity,res<>0);
end;

function DQE2_EntityType.OnBreakCustomAction(FuncID: integer;
  const Params: array of const): boolean;
begin
  result:=inherited OnBreakCustomAction(FuncID,Params);
  FSender:=World.CurrentEntity;
end;

function DQE2_EntityType.Resume(NextLine: boolean): shortint;
begin
  result:=inherited Resume(NextLine);
  if result>0 then // if leaving debug
  begin
    if FBreakFuncID=FFuncInit then
      _Init(FSender,boolean(FProg.GetLastResult)) else
    if FBreakFuncID=FFuncStep then
      _Step(FSender,boolean(FProg.GetLastResult));
    FBreakFuncID:=0;
  end;
end;

procedure DQE2_EntityType._Init(Sender: DQE2_Entity; res: boolean);
begin
  Sender.FRunning:=res;
end;

procedure DQE2_EntityType._Step(Sender: DQE2_Entity; res: boolean);
begin
  if res=false then Sender.Die else Sender.FPassCount:=Sender.FPassCount+1;
end;

{ DQE2_Entity }

procedure DQE2_Entity.AssignTo(Dest: TPersistent);
var
  x:DQE2_Entity;
begin
  inherited;
  if not(Dest is DQE2_Entity) then exit;
  x:=Dest as DQE2_Entity;
  x.FEntityType:=x.FEntityType;
end;

constructor DQE2_Entity.Create(Collection: TCollection;Zone:DQE2_Zone;
  TheType: DQE2_EntityType);
begin
  inherited Create(Collection,Zone);
  FEntityType:=TheType;
  FScriptState:=0;
  FPassCount:=0;
  FRunning:=false;
end;

destructor DQE2_Entity.Destroy;
begin
  inherited;
end;

procedure DQE2_Entity.Die;
begin
  if FRunning then
    FEntityType.Die(Self);
end;

function DQE2_Entity.DowngradeToObject: DQE2_Object;
begin
  Collection:=nil;
  result:=DQE2_Object.Create(world.Objects,nil);
  FZone.Objects[FInZoneIndex]:=result;
  result.FZone:=FZone;result.FInZoneIndex:=FInZoneIndex;
  result.FName:=FName;
  AssignTo(result);
  FInZoneIndex:=-1;
  Free;
end;

function DQE2_Entity.GetScriptState: TPAXScriptObject;
begin
  result:=TPAXScriptObject(pointer(cardinal(FScriptState)));
end;

procedure DQE2_Entity.LoadState;
begin
  inherited;
  FEntityType:=DQE2_EntityType(FState.ReservedPtr1);
end;

procedure DQE2_Entity.Run;
begin
  if FRunning=false then
    FEntityType.Init(Self);
end;

procedure DQE2_Entity.SaveState;
begin
  inherited;
  FState.ReservedPtr1:=FEntityType;
end;

procedure DQE2_Entity.SetEntityType(const Value: DQE2_EntityType);
begin
  FEntityType := Value;
  Stop;
end;

procedure DQE2_Entity.Step;
begin
  inherited;
  if FRunning and (World.State=wsPlay) then
    FEntityType.Step(Self);
end;

procedure DQE2_Entity.Stop;
begin
  FRunning:=false;
end;

end.
