(*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * http://www.opensource.org/licenses/cddl1.php.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * http://www.opensource.org/licenses/cddl1.php.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2009 Andreas Schneider
 *)
unit UfrmMain;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, Menus,
  ComCtrls, OpenGLContext, GL, GLu, UGameResources, ULandscape, ExtCtrls,
  StdCtrls, Spin, UEnums, laz.VirtualTrees, VirtualList, Buttons, math, UMulBlock,
  UWorldItem, LCLIntf, UOverlayUI, UStatics, UEnhancedMemoryStream, ActnList,
  XMLPropStorage, LazHelpHTML, fgl, ImagingClasses, dateutils, UPlatformTypes,
  UMap, UPacket, UGLFont, DOM, XMLRead, XMLWrite, strutils, ULightManager,
  UndoRedoCmd, ImagingTypes, ImagingCanvases, types, Registry,
  IniFiles, LMessages;

const
  FVLightSrcImageCount = 15; // Количество иконок виртуальных источников
  FUndoListLength = 64; // Количество элементов в списке отмены

type
  TAccessChangedListener = procedure(AAccessLevel: TAccessLevel) of object;
  TSelectionListener = procedure(AWorldItem: TWorldItem) of object;
  TScreenBufferState = (sbsValid, sbsIndexed, sbsFiltered);
  TScreenBufferStates = set of TScreenBufferState;

  TBlockInfoList = specialize TFPGList<PBlockInfo>;

  TGhostTile = class(TStaticItem)
  public
    CenterX, CenterY: Word; // Точки привязки к курсору (центральный тайл для объектов размером больше 1 тайла)
  end;
  TPacketList = specialize TFPGObjectList<TPacket>;
  PPacketList = ^TPacketList;
  TAccessChangedListeners = specialize TFPGList<TAccessChangedListener>;
  TSelectionListeners = specialize TFPGList<TSelectionListener>;

  TTileHintInfo = record
    Column: Byte;
    Obj: String;
    Name: String;
    Flags: String;
    ObjRect: TRect;
    NameRect: TRect;
    FlagsRect: TRect;
  end;

  TGroupTile = record
    ID: LongWord;
  end;

  TEntryTile = record
    ID: LongWord;
    Hue: Word;
    X,Y: Smallint;
    Z: ShortInt;
  end;

  TBrushTile = record
    ID: Word;
    Chance: Float;
    Mask: Byte;
    Brush1: ^TGroupBrush;
    Brush2: ^TGroupBrush;
  end;
  PBrushTile = ^TBrushTile;

  TGroupEntry = record
    ID: Word;
    Name: string;
    Image: TSingleImage;
    Count: Word;     // Число тайлов в объекте
    ETile: ^TEntryTile;
  end;
  PGroupEntry = ^TGroupEntry;

  TGroupBrushEdge = record
    ID: Word;
    CountDR: Word;
    BTileDR: ^PBrushTile;
    CountDL: Word;
    BTileDL: ^PBrushTile;
    CountUR: Word;
    BTileUR: ^PBrushTile;
    CountUL: Word;
    BTileUL: ^PBrushTile;
    CountLL: Word;
    BTileLL: ^PBrushTile;
    CountUU: Word;
    BTileUU: ^PBrushTile;
  end;
  PGroupBrushEdge = ^TGroupBrushEdge;

  TGroupBrush = record
    ID: Word;
    Name: string;
    Image: TSingleImage;
    Count: Word;     // Число тайлов в объекте
    BTile: ^PBrushTile;

    ECount: Word;    // Число тайлов перехода (в <Edge />)
    EdgeId: ^PWord;  // �ндификаторы кистей переходов (ссылка на кисть в <Edge />)
    BEdges: ^PGroupBrushEdge; // Тайлы переходов
  end;
  PGroupBrush = ^TGroupBrush;

  TEntryList = record
    Entry: ^PGroupEntry;
    Count: Word;
  end;

  TBrushList = record
    Brush: ^PGroupBrush;
    Count: Word;
    Tiles: array[0..$3FFF] of TBrushTile;
  end;

  TGroupNode = record
    Name : string;
    Color: TColor;
    Bold : Boolean;
    Ital : Boolean;
    Items: LongWord; // число элементов (тайлы, объекты, кисти и тд) в группе (включая подгруппы)
    Nodes: Word;
    ID   : LongWord;
    Links: Word;             // число элементов в GLink
    lids : ^LongWord;
    GLink: ^PVirtualNode;    // сылки на группы
    Count: LongWord;         // число элементов в GTile
    GTile: ^TGroupTile;
    Entries: LongWord;       // число элементов в Entry
    Entry: ^PGroupEntry;     // сылки на объекты из TilesEntry.xml
    Brushes: LongWord;       // число элементов в Brush
    Brush: ^PGroupBrush;     // сылки на объекты из TilesBrush.xml
  end;
  PGroupNode = ^TGroupNode;

  // SurfaceInf.xml types start
  TSurfTile = record
    Tile: ^LongWord; // ID тайлов лендов и итемов
    Hash: ^LongWord; // Surface's тэги для них
    Count: Word;
  end;
  PSurfTile = ^TSurfTile;

  TSurfGrad = record
    Grad: ^TSurfTile; // списки тайлов в Surface'ах, рассортированные по Category+Type
    Hash: ^LongWord;  // SurfaceCategory + ItemType
    Count: Word;
  end;
  PSurfGrad = ^TSurfGrad;

  TSurfInfo = record
    Name  : string;
    TileID: LongWord;
    TileHash:  LongWord; // Кеш Имени (Name)
    GradHash: ^LongWord; // Кэши типов тайлов в объекте
    GradCount: Word;     // Чмсло GradHash
  end;
  PSurfInfo = ^TSurfInfo;

  TSurfGroup = record
    Name : string;       // Имя категории
    Info : ^TSurfInfo;   // Список поверхностей в категории
    Count: Word;         // Длинна списка поверхностей
  end;
  PSurfGroup = ^TSurfGroup;

  TSurfsList = record
    Group: ^TSurfGroup;
    GroupCount:  Word;

    Grads: ^TSurfGrad;   // Контейнер для хранения памяти
    GradsCount:  Word;

    Tiles: ^PSurfGrad;
    TilesCount:  Word;
  end;
  // SurfaceInf.xml types end

  TLightTile = record
    image: Byte;
    color: Byte;
  end;
  PLightTile = ^TLightTile;

  { TfrmMain }

  TfrmMain = class(TForm)
    acSelect: TAction;
    acDraw: TAction;
    acMove: TAction;
    acElevate: TAction;
    acDelete: TAction;
    acHue: TAction;
    acBoundaries: TAction;
    acFilter: TAction;
    acFlat: TAction;
    acNoDraw: TAction;
    acLightlevel: TAction;
    acTerrain: TAction;
    acStatics: TAction;
    acSelection: TAction;
    acSurfElevate: TAction;
    acSurfStretch: TAction;
    acSurfSmooth: TAction;
    acFill: TAction;
    acRedo: TAction;
    acWalkable: TAction;
    acUndo: TAction;
    acVirtualLayer: TAction;
    ActionList1: TActionList;
    ApplicationProperties1: TApplicationProperties;
    btnAddLocation: TSpeedButton;
    btnAddRandom: TSpeedButton;
    btnClearLocations: TSpeedButton;
    btnClearRandom: TSpeedButton;
    btnDeleteLocation: TSpeedButton;
    btnDeleteRandom: TSpeedButton;
    btnGoTo: TButton;
    btnRandomPresetDelete: TSpeedButton;
    btnRandomPresetSave: TSpeedButton;
    cbRandomPreset: TComboBox;
    cbStatics: TCheckBox;
    cbTerrain: TCheckBox;
    edChat: TEdit;
    edFilter: TEdit;
    edSearchID: TEdit;
    edX: TSpinEdit;
    edY: TSpinEdit;
    gbRandom: TGroupBox;
    gbGoTo: TGroupBox;
    ImageList1: TImageList;
    lblTileInfoOLabel: TLabel;
    lblTileInfoIDLabel: TLabel;
    lblTileInfoHLabel: TLabel;
    lblTileInfoIDValue: TLabel;
    lblTileInfoCLabel: TLabel;
    lblTileInfoWLabel: TLabel;
    lblTileInfoYLabel: TLabel;
    lblTileInfoXValue: TLabel;
    lblTileInfoXLabel: TLabel;
    lblTileInfoZValue: TLabel;
    lblTileInfoHueValue: TLabel;
    lblTileInfoHueLabel: TLabel;
    lblChatHeaderCaption: TLabel;
    lblFilter: TLabel;
    lblTileInfoZLabel: TLabel;
    lblTileInfoYValue: TLabel;
    lblTileInfoHValue: TLabel;
    lblTileInfoWValue: TLabel;
    lblX: TLabel;
    lblY: TLabel;
    MainMenu1: TMainMenu;
    MenuItem1: TMenuItem;
    mnuMiscTileListLarge: TMenuItem;
    mnuSeparator9: TMenuItem;
    mnuSeparator8: TMenuItem;
    mnuTileListDrawInfo: TMenuItem;
    mnuMiscTileListTable: TMenuItem;
    mnuMiscTileListSmall: TMenuItem;
    mnuSeparator10: TMenuItem;
    mnuMiscTileListCentre: TMenuItem;
    mnuMiscTileListDrawInfo: TMenuItem;
    mnuMiscTileListMidle: TMenuItem;
    mnuMiscTileListClip: TMenuItem;
    mnuMiscTileListStretch: TMenuItem;
    mnuMiscTileList: TMenuItem;
    mnuEng2Com: TMenuItem;
    mnuTileListMidle: TMenuItem;
    mnuTileListTable: TMenuItem;
    mnuTileListSmall: TMenuItem;
    mnuTileListLarge: TMenuItem;
    mnuTileListStretch: TMenuItem;
    mnuTileListClip: TMenuItem;
    mnuTileListCentre: TMenuItem;
    mnuSeparator7: TMenuItem;
    mnuTileList: TMenuItem;
    mnuSeparator6: TMenuItem;
    mnuSetLanguage: TMenuItem;
    mnuAutoShowFilterWindow: TMenuItem;
    mnuSelection: TMenuItem;
    mnuSurfElevate: TMenuItem;
    mnuSurfStretch: TMenuItem;
    mnuSurfSmooth: TMenuItem;
    mnuFill: TMenuItem;
    mnuShowNoDrawTiles: TMenuItem;
    mnuEngCom: TMenuItem;
    mnuRusCom: TMenuItem;
    mnuSupport: TMenuItem;
    mnuSeparator5: TMenuItem;
    mnuDocs: TMenuItem;
    mnuShowBlocks: TMenuItem;
    mnuGrabBoundaries: TMenuItem;
    mnuGrabBoundMinZ: TMenuItem;
    mnuGrabBoundMaxZ: TMenuItem;
    mnuGrabBoundMinX: TMenuItem;
    mnuGrabBoundMaxX: TMenuItem;
    mnuGrabBoundMinY: TMenuItem;
    mnuGrabBoundMaxY: TMenuItem;
    mnuZoom300: TMenuItem;
    mnuZoom400: TMenuItem;
    mnuZoom033: TMenuItem;
    mnuZoom025: TMenuItem;
    mnuZoom150: TMenuItem;
    mnuZoom200: TMenuItem;
    mnuZoom075: TMenuItem;
    mnuZoom050: TMenuItem;
    mnuZoom100: TMenuItem;
    mnuShowBridges: TMenuItem;
    mnuWindowedMode: TMenuItem;
    mnuMakeScreenShot: TMenuItem;
    mnuShowWater: TMenuItem;
    mnuShowSurfaces: TMenuItem;
    mnuShowRoofs: TMenuItem;
    mnuShowFoliage: TMenuItem;
    mnuAutoHideRandomList: TMenuItem;
    mnuAutoHideGroupList: TMenuItem;
    mnuSeparator4: TMenuItem;
    mnuReloadGroups: TMenuItem;
    mnuGrabFilterTileID: TMenuItem;
    mnuGrabFilterHue: TMenuItem;
    mnuGrabVirtualLayerZ: TMenuItem;
    mnuShowGrid: TMenuItem;
    mnuShowWalls: TMenuItem;
    mnuShowLightSource: TMenuItem;
    mnuWhiteBackground: TMenuItem;
    mnuSecurityQuestion: TMenuItem;
    mnuShowAnimations: TMenuItem;
    mnuSettings: TMenuItem;
    mnuFlatShowHeight: TMenuItem;
    mnuGrabHue: TMenuItem;
    mnuGrabTileID: TMenuItem;
    mnuRegionControl: TMenuItem;
    mnuVirtualLayer: TMenuItem;
    mnuLargeScaleCommands: TMenuItem;
    mnuSetHue: TMenuItem;
    mnuGoToClient: TMenuItem;
    mnuAbout: TMenuItem;
    mnuHelp: TMenuItem;
    mnuSeparator3: TMenuItem;
    mnuBoundaries: TMenuItem;
    mnuSelect: TMenuItem;
    mnuDraw: TMenuItem;
    mnuMove: TMenuItem;
    mnuElevate: TMenuItem;
    mnuDelete: TMenuItem;
    mnuAddToRandom: TMenuItem;
    mnuFlush: TMenuItem;
    mnuShutdown: TMenuItem;
    mnuSeparator2: TMenuItem;
    mnuAccountControl: TMenuItem;
    mnuAdministration: TMenuItem;
    mnuSeparator1: TMenuItem;
    mnuExit: TMenuItem;
    mnuDisconnect: TMenuItem;
    mnuCentrED: TMenuItem;
    oglGameWindow: TOpenGLControl;
    pbRadar: TPaintBox;
    pcLeft: TPageControl;
    pmZoomSettings: TPopupMenu;
    pmViewTerrainSettings: TPopupMenu;
    pmGrabTileInfo: TPopupMenu;
    pmNoDrawSettings: TPopupMenu;
    pmViewStaticSettings: TPopupMenu;
    pmTileList: TPopupMenu;
    pmTools: TPopupMenu;
    pmClients: TPopupMenu;
    pnlChat: TPanel;
    pnlChatHeader: TPanel;
    pmFlatViewSettings: TPopupMenu;
    spChat: TSplitter;
    spGroupList1: TSplitter;
    spTileList: TSplitter;
    spGroupList: TSplitter;
    tbFill: TToolButton;
    tbSurfSmooth: TToolButton;
    tbSurfStretch: TToolButton;
    tbSurfElevate: TToolButton;
    tbSelection: TToolButton;
    tbFilter: TToolButton;
    tbFlat: TToolButton;
    tbSeparator6: TToolButton;
    tbSeparator7: TToolButton;
    tbSeparator8: TToolButton;
    tbSeparator9: TToolButton;
    tbRedo: TToolButton;
    tbZoom: TToolButton;
    tbNoDraw: TToolButton;
    tbSeparator2: TToolButton;
    tbUndo: TToolButton;
    tbLightlevel: TToolButton;
    tbWalkable: TToolButton;
    tmSelectNode: TTimer;
    tmSettingsClose: TTimer;
    tsNavigation: TTabSheet;
    tbSetHue: TToolButton;
    tmGrabTileInfo: TTimer;
    tmMovement: TTimer;
    tbSeparator5: TToolButton;
    tbRadarMap: TToolButton;
    tbVirtualLayer: TToolButton;
    tsObjects: TTabSheet;
    tbMain: TToolBar;
    tbDisconnect: TToolButton;
    tbSeparator1: TToolButton;
    tbSelect: TToolButton;
    tbDrawTile: TToolButton;
    tbMoveTile: TToolButton;
    tbElevateTile: TToolButton;
    tbDeleteTile: TToolButton;
    tbSeparator3: TToolButton;
    tbBoundaries: TToolButton;
    tbSeparator4: TToolButton;
    tbTerrain: TToolButton;
    tbStatics: TToolButton;
    tsTiles: TTabSheet;
    tvGroups: TLazVirtualStringTree;
    vdtTiles: TVirtualList;
    vdtRandom: TLazVirtualDrawTree;
    vdlRandom: TVirtualList;
    vstChat: TLazVirtualStringTree;
    vstLocations: TLazVirtualStringTree;
    vstClients: TLazVirtualStringTree;
    XMLPropStorage1: TXMLPropStorage;
    procedure acBoundariesExecute(Sender: TObject);
    procedure acDeleteExecute(Sender: TObject);
    procedure acDrawExecute(Sender: TObject);
    procedure acElevateExecute(Sender: TObject);
    procedure acFillExecute(Sender: TObject);
    procedure acFilterExecute(Sender: TObject);
    procedure acFlatExecute(Sender: TObject);
    procedure acHueExecute(Sender: TObject);
    procedure acLightlevelExecute(Sender: TObject);
    procedure acMoveExecute(Sender: TObject);
    procedure acNoDrawExecute(Sender: TObject);
    procedure acSelectExecute(Sender: TObject);
    procedure acSelectionExecute(Sender: TObject);
    procedure acStaticsExecute(Sender: TObject);
    procedure acSurfElevateExecute(Sender: TObject);
    procedure acSurfSmoothExecute(Sender: TObject);
    procedure acSurfStretchExecute(Sender: TObject);
    procedure acTerrainExecute(Sender: TObject);
    procedure acUndoExecute(Sender: TObject);
    procedure acVirtualLayerExecute(Sender: TObject);
    procedure acWalkableExecute(Sender: TObject);
    procedure ApplicationProperties1Idle(Sender: TObject; var Done: Boolean);
    procedure ApplicationProperties1ShowHint(var HintStr: string;
      var CanShow: Boolean; var HintInfo: THintInfo);
    procedure btnAddLocationClick(Sender: TObject);
    procedure btnAddRandomClick(Sender: TObject);
    procedure btnClearLocationsClick(Sender: TObject);
    procedure btnClearRandomClick(Sender: TObject);
    procedure btnDeleteLocationClick(Sender: TObject);
    procedure btnDeleteRandomClick(Sender: TObject);
    procedure btnGoToClick(Sender: TObject);
    procedure btnRandomPresetDeleteClick(Sender: TObject);
    procedure btnRandomPresetSaveClick(Sender: TObject);
    procedure cbRandomPresetChange(Sender: TObject);
    procedure cbStaticsChange(Sender: TObject);
    procedure cbTerrainChange(Sender: TObject);
    procedure edChatKeyPress(Sender: TObject; var Key: char);
    procedure edFilterEditingDone(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure edSearchIDExit(Sender: TObject);
    procedure edSearchIDKeyPress(Sender: TObject; var Key: char);
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormShow(Sender: TObject);
    procedure FormWindowStateChange(Sender: TObject);
    procedure lblChatHeaderCaptionClick(Sender: TObject);
    procedure lblChatHeaderCaptionMouseEnter(Sender: TObject);
    procedure lblChatHeaderCaptionMouseLeave(Sender: TObject);
    procedure mnuAboutClick(Sender: TObject);
    procedure mnuAccountControlClick(Sender: TObject);
    procedure mnuAutoHideGroupListClick(Sender: TObject);
    procedure mnuAutoHideRandomListClick(Sender: TObject);
    procedure mnuDisconnectClick(Sender: TObject);
    procedure mnuDocsClick(Sender: TObject);
    procedure mnuEng2ComClick(Sender: TObject);
    procedure mnuEngComClick(Sender: TObject);
    procedure mnuExitClick(Sender: TObject);
    procedure mnuFlatShowHeightClick(Sender: TObject);
    procedure mnuFlushClick(Sender: TObject);
    procedure mnuGoToClientClick(Sender: TObject);
    procedure GrabBoundaries(Sender: TObject);
    procedure mnuGrabFilterHueClick(Sender: TObject);
    procedure mnuGrabFilterTileIDClick(Sender: TObject);
    procedure mnuGrabHueClick(Sender: TObject);
    procedure mnuGrabTileIDClick(Sender: TObject);
    procedure mnuGrabVirtualLayerZClick(Sender: TObject);
    procedure mnuLargeScaleCommandsClick(Sender: TObject);
    procedure mnuMakeScreenShotClick(Sender: TObject);
    procedure mnuRegionControlClick(Sender: TObject);
    procedure mnuReloadGroupsClick(Sender: TObject);
    procedure mnuRusComClick(Sender: TObject);
    procedure mnuSetLanguageClick(Sender: TObject);
    procedure mnuShowAnimationsClick(Sender: TObject);
    procedure mnuShowBlocksClick(Sender: TObject);
    procedure mnuShowGridClick(Sender: TObject);
    procedure mnuShowLightSourceClick(Sender: TObject);
    procedure mnuShowNoDrawTilesClick(Sender: TObject);
    procedure mnuShowStaticsOptionClick(Sender: TObject);
    procedure mnuShutdownClick(Sender: TObject);
    procedure mnuTileListDrawClick(Sender: TObject);
    procedure mnuTileListViewClick(Sender: TObject);
    procedure mnuWhiteBackgroundClick(Sender: TObject);
    procedure mnuWindowedModeClick(Sender: TObject);
    procedure mnuZoomClick(Sender: TObject);
    procedure oglGameWindowDblClick(Sender: TObject);
    procedure oglGameWindowKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure oglGameWindowMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure oglGameWindowMouseEnter(Sender: TObject);
    procedure oglGameWindowMouseLeave(Sender: TObject);
    procedure oglGameWindowMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure oglGameWindowMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure oglGameWindowMouseWheel(Sender: TObject; Shift: TShiftState;
      WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
    procedure oglGameWindowPaint(Sender: TObject);
    procedure oglGameWindowResize(Sender: TObject);
    procedure pbRadarMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure pbRadarPaint(Sender: TObject);
    procedure pcLeftResize(Sender: TObject);
    procedure pmGrabTileInfoPopup(Sender: TObject);
    procedure DropedownMenusClose(Sender: TObject);
    procedure spGroupListMoved(Sender: TObject);
    procedure spTileListMoved(Sender: TObject);
    procedure tbFilterMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure tbFilterMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure tbRadarMapClick(Sender: TObject);
    procedure tmGrabTileInfoTimer(Sender: TObject);
    procedure tmMovementTimer(Sender: TObject);
    procedure tmSelectNodeTimer(Sender: TObject);
    procedure tmSettingsCloseTimer(Sender: TObject);
    procedure tvGroupFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
    procedure tvGroupsChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
    procedure tvGroupsDrawText(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
      Node: PVirtualNode; Column: TColumnIndex; const CellText: String;
      const CellRect: TRect; var DefaultDraw: Boolean);
    procedure tvGroupsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState
      );
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure tvSelectGroupsChanged(Sender: TObject);
    procedure vdtRandomClick(Sender: TObject);
    procedure vdtRandomDragDrop(Sender: TBaseVirtualTree; Source: TObject;
      DataObject: IDataObject; Formats: TFormatArray; Shift: TShiftState;
      Pt: TPoint; var Effect: Integer; Mode: TDropMode);
    procedure vdtRandomDragOver(Sender: TBaseVirtualTree; Source: TObject;
      Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode;
      var Effect: Integer; var Accept: Boolean);
    procedure vdtRandomLoadNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Stream: TStream);
    procedure vdtRandomSaveNode(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Stream: TStream);
    procedure vdtRandomUpdating(Sender: TBaseVirtualTree; State: TVTUpdateState);
    procedure vdtTilesChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
    procedure vdtTilesClick(Sender: TObject);
    procedure vdtTilesDragAllowed(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; var Allowed: Boolean);
    procedure vdtTilesDrawHint(Sender: TBaseVirtualTree; HintCanvas: TCanvas;
      Node: PVirtualNode; const R: TRect; Column: TColumnIndex);
    procedure vdtTilesDrawNode(Sender: TBaseVirtualTree;
      const PaintInfo: TVTPaintInfo);
    procedure vdtTilesEnter(Sender: TObject);
    procedure vdtTilesGetHintSize(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; var R: TRect);
    procedure vdtTilesKeyPress(Sender: TObject; var Key: char);
    procedure vdtTilesScroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
    procedure vstChatClick(Sender: TObject);
    procedure vstChatFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
    procedure vstChatGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
    procedure vstChatPaintText(Sender: TBaseVirtualTree;
      const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
      TextType: TVSTTextType);
    procedure vstClientsGetImageIndex(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
      var Ghosted: Boolean; var ImageIndex: Integer);
    procedure vstClientsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
    procedure vstLocationsDblClick(Sender: TObject);
    procedure vstLocationsFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode
      );
    procedure vstLocationsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
    procedure vstLocationsLoadNode(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Stream: TStream);
    procedure vstLocationsNewText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; const NewText: String);
    procedure vstLocationsSaveNode(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Stream: TStream);
    procedure XMLPropStorage1RestoreProperties(Sender: TObject);
    procedure XMLPropStorage1SavingProperties(Sender: TObject);
  protected
    { Members }
    FAppDir: String;      // .\
    FLocalDir: String;    // .\..\LocalData\
    FConfigDir: String;   // {$User}\AppData\Local\CentrED-plus\                      - зависит от реестра
    FProfileDir: String;  // {$User}\AppData\Local\CentrED-plus\Profiles\{$Profile}\  - зависит от реестра
    FX: Integer;
    FY: Integer;
    FDrawDistance: Integer;
    FLowOffsetX: Integer;
    FLowOffsetY: Integer;
    FHighOffsetX: Integer;
    FHighOffsetY: Integer;
    FRangeX: Integer;
    FRangeY: Integer;
    FLandscape: TLandscape;
    FTextureManager: TLandTextureManager;
    FScreenBuffer: TScreenBuffer;
    FScreenBufferState: TScreenBufferStates;
    FCurrentTile: TWorldItem;
    FSelectedTile: TWorldItem;
    FVirtualTiles: TWorldItemList;
    FVLayerImage: TSingleImage;
    FVLayerMaterial: TMaterial;
    FVLightSrcImage: array[1..FVLightSrcImageCount] of TSingleImage;
    FVLightSrcMaterial: ^TMaterial;
    FOverlayUI: TOverlayUI;
    FLocationsFile: string;
    FRandomPresetsFile: string;
    FRandomPresetsDoc: TXMLDocument;
    FLastDraw: TDateTime;
    FAccessChangedListeners: TAccessChangedListeners;
    FRepaintNeeded: Boolean;
    FSelection: TRect;
    FUndoList: PPacketList;
    FUndoListArray: array[1..FUndoListLength] of TPacketList;
    FUndoListFirstIndex: Word;
    FundoListLastIndex: Word;
    FEntryList: TEntryList;
    FBrushList: TBrushList;
    FSurfsList: TSurfsList;

    FGroupsSelectionUndoRedoCommandGroup: TUndoRedoCommandGroup;
    FGroupsSelectionUndoRedoManager: TUndoRedoManager;
    FTilesSelectionUndoRedoCommandGroup: TUndoRedoCommandGroup;
    FTilesSelectionUndoRedoManager: TUndoRedoManager;

    FGLFont: TGLFont;
    FSelectionListeners: TSelectionListeners;
    FTileHint: TTileHintInfo;
    FLightManager: TLightManager;
    FVisibleTiles: TBits;
    FLightSourceTiles: PLightTile;
    { Methods }
    function  GetNextUndoList: PPacketList;
    function  LoadListError(condition: Boolean; filename, message : string): Boolean;
    procedure LoadVisibleTiles(AFileName: String);
    procedure LoadLightSourceTiles(AFileName: String);
    procedure LoadEntryTilesList;
    procedure LoadBrushTilesList;
    procedure LoadSurfsTilesList;
    procedure BuildGroupList;
    procedure BuildTileList;
    procedure FreeGroupLists;
    function  ConfirmAction: Boolean;
    function  FindRandomPreset(AName: String): TDOMElement;
    procedure ForceUpdateCurrentTile;
    procedure GetDrawOffset(AX, AY: Integer; out DrawX, DrawY: Integer); inline;
    function  GetInternalTileID(ATile: TWorldItem): LongWord;
    function  GetSelectedRect: TRect;
    procedure InitRender;
    procedure InitSize;
    procedure LoadLocations;
    procedure LoadRandomPresets;
    procedure MoveBy(AOffsetX, AOffsetY: Integer); inline;
    procedure PrepareMapCell(AMapCell: TMapCell);
    procedure PrepareScreenBlock(ABlockInfo: PBlockInfo);
    procedure ProcessToolState;
    procedure ProcessAccessLevel;
    procedure RebuildScreenBuffer;
    procedure Render;
    procedure SaveLocations;
    procedure SaveRandomPresets;
    procedure SetCurrentTile(const AValue: TWorldItem);
    procedure SetDarkLights; inline;
    procedure SetNormalLights; inline;
    procedure SetSelectedTile(const AValue: TWorldItem);
    procedure SetX(const AValue: Integer);
    procedure SetY(const AValue: Integer);
    procedure UpdateCurrentTile;
    procedure UpdateCurrentTile(AX, AY: Integer);
    procedure UpdateFilter;
    procedure UpdateSelection;
    procedure WriteChatMessage(ASender, AMessage: string);
    { Events }
    procedure OnClientHandlingPacket(ABuffer: TEnhancedMemoryStream);
    procedure OnLandscapeChanged;
    procedure OnMapChanged(AMapCell: TMapCell);
    procedure OnNewBlock(ABlock: TBlock);
    procedure OnStaticDeleted(AStaticItem: TStaticItem);
    procedure OnStaticElevated(AStaticItem: TStaticItem);
    procedure OnStaticHued(AStaticItem: TStaticItem);
    procedure OnStaticInserted(AStaticItem: TStaticItem);
    procedure OnTileRemoved(ATile: TMulBlock);
  public
    { Fields }
    property X: Integer read FX write SetX;
    property Y: Integer read FY write SetY;
    property Landscape: TLandscape read FLandscape;
    property CurrentTile: TWorldItem read FCurrentTile write SetCurrentTile;
    property SelectedTile: TWorldItem read FSelectedTile write SetSelectedTile;
    property LightManager: TLightManager read FLightManager;
    property AppDir: string read FAppDir;
    property LocalDir: string read FLocalDir;
    property ConfigDir: string read FConfigDir;
    property ProfileDir: string read FProfileDir;

    { Methods }
    procedure InvalidateFilter;
    procedure InvalidateScreenBuffer;
    procedure RegisterAccessChangedListener(AListener: TAccessChangedListener);
    procedure RegisterSelectionListener(AListener: TSelectionListener);
    procedure SetPos(AX, AY: Word);
    procedure SwitchToSelection;
    procedure UnregisterAccessChangedListener(AListener: TAccessChangedListener);
    procedure UnregisterSelectionListener(AListener: TSelectionListener);
  public
    {Localization}
    lbFormTitleAccount: string;
    lbFormTitleProfile: string;
    lbBottomCursorVLayer1: string;
    lbBottomCursorVLayer2: string;
    lbBottomCursorItemID: string;
    lbBottomCursorLandID: string;
    lbBottomCursorPosX: string;
    lbBottomCursorPosY: string;
    lbBottomCursorPosZ: string;
    lbBottomCursorItemHue: string;
    lbToolbarUndo: string;

    lbDlgWindowedModeSwitchCaption: string;
    lbDlgWindowedModeSwitch: string;
    lbScreenShotMsg: string;
    lbUserLoginedMsg: string;
    lbUserLogoutedMsg: string;
    lbDlgGetDcErrCaption: string;
    lbDlgGetDcErr: string;
    lbDlgFreeDcErrCaption: string;
    lbDlgFreeDcErr: string;
    lbDlgCnangedAccessCaption: string;
    lbDlgCnangedAccess: string;
    lbDlgBlockedAccessCaption: string;
    lbDlgBlockedAccess: string;

    lbDlgSaveRandPrsCaption: string;
    lbDlgSaveRandPrs: string;
    lbDlgSearchIdErrCaption: string;
    lbDlgSearchIdErr: string;
    lbDlgNotFoundErrCaption: string;
    lbDlgNotFoundErr: string;

    lbDlgDelConfCaption: string;
    lbDlgDelConf: string;
    lbDlgNewQuerryCaption: string;
    lbDlgNewQuerry: string;

  end; 

var
  frmMain: TfrmMain;

implementation

uses
  UdmNetwork, UArt, UTexture, UHue, UTiledata, UAdminHandling, UPackets,
  UfrmAccountControl, UGraphicHelper, ImagingComponents, UfrmDrawSettings,
  UfrmBoundaries, UfrmElevateSettings, UfrmConfirmation, UfrmMoveSettings,
  UfrmAbout, UPacketHandlers, UfrmHueSettings, UfrmRadar, UfrmLargeScaleCommand,
  UfrmLogin, UResourceManager, UfrmVirtualLayer, UfrmFilter, UfrmRegionControl,
  Logging, LConvEncoding, LCLType, UfrmLightlevel, vinfo, Imaging, Language,
  UfrmEditAccount, UfrmFillSettings, UfrmSelectionSettings, UfrmInitialize,
  UfrmSurfElevateSettings, UfrmSurfStretchSettings, UfrmSurfSmoothSettings, Crc32Hash;

{$I version.inc}

type
  TGLArrayf4 = array[0..3] of GLfloat;
  PTileInfo = ^TTileInfo;
  TTileInfo = record
    ID: LongWord;
    ptr: Pointer;
  end;
  PChatInfo = ^TChatInfo;
  TChatInfo = record
    Time: TDateTime;
    Sender: string;
    Msg: string;
  end;
  PLocationInfo = ^TLocationInfo;
  TLocationInfo = record
    X: Word;
    Y: Word;
    Name: string;
  end;
  PClientInfo = ^TClientInfo;
  TClientInfo = record
    Name: string;
    AccessLevel: TAccessLevel;
    LogonDateTime : TDateTime;
    //Time: string;
    //Map: Byte;
    //X: Word;
    //Y: Word;
  end;

const
  CScreenBufferValid = [sbsValid, sbsIndexed, sbsFiltered];

function IsInRect(const AX, AY: Integer; const ARect: TRect): Boolean; inline;
begin
  Result := (AX >= ARect.Left) and
            (AX <= ARect.Right) and
            (AY >= ARect.Top) and
            (AY <= ARect.Bottom);
end;

{ TfrmMain }

procedure TfrmMain.mnuExitClick(Sender: TObject);
begin
  Close;
end;

procedure TfrmMain.mnuFlatShowHeightClick(Sender: TObject);
begin
  tbFlat.Down := acFlat.Checked;
  RebuildScreenBuffer;
end;

procedure TfrmMain.mnuShowNoDrawTilesClick(Sender: TObject);
begin
  tbNoDraw.Down := acNoDraw.Checked;
  RebuildScreenBuffer;
  FRepaintNeeded := True;
end;

procedure TfrmMain.mnuShowLightSourceClick(Sender: TObject);
begin
  tbNoDraw.Down := acNoDraw.Checked;
  RebuildScreenBuffer;
  FRepaintNeeded := True;
end;

procedure TfrmMain.mnuShowGridClick(Sender: TObject);
begin
  tbTerrain.Down := acTerrain.Checked;
  mnuShowBlocks.Checked := False;
  RebuildScreenBuffer;
  FRepaintNeeded := True;
end;

procedure TfrmMain.mnuShowBlocksClick(Sender: TObject);
begin
  tbTerrain.Down := acTerrain.Checked;
  mnuShowGrid.Checked := False;
  RebuildScreenBuffer;
  FRepaintNeeded := True;
end;

procedure TfrmMain.mnuShowStaticsOptionClick(Sender: TObject);
begin
  tbStatics.Down := acStatics.Checked;
  RebuildScreenBuffer;
end;

procedure TfrmMain.mnuFlushClick(Sender: TObject);
begin
  dmNetwork.Send(TFlushServerPacket.Create);
end;

procedure TfrmMain.mnuGoToClientClick(Sender: TObject);
var
  mpos: TPoint;
  node: PVirtualNode;
  clientInfo: PClientInfo;
begin
  mpos := vstClients.ScreenToClient(Mouse.CursorPos);
  node := vstClients.GetNodeAt(mpos.X, mpos.Y);// .GetFirstSelected;
  if node <> nil then
  begin
    clientInfo := vstClients.GetNodeData(node);
    dmNetwork.Send(TGotoClientPosPacket.Create(clientInfo^.Name));
  end;

end;

procedure TfrmMain.mnuGrabTileIDClick(Sender: TObject);
var
  internalTileID: LongWord;
  item: PVirtualItem;
  tileInfo: PTileInfo;
  treeNode: PVirtualNode;

  function TileInNode(Node: PVirtualNode; TileID: LongWord) : Boolean;
  var
    nodeData: ^TGroupNode;
    i: Integer;
  begin
    Result := False;
    nodeData := tvGroups.GetNodeData(Node);
    for i := 0 to nodeData^.Count - 1 do
    begin
      if nodeData^.GTile[i].ID = TileID then
      begin
        Result := True;
        break;
      end;
    end;
  end;

begin
  Logger.Send([lcClient, lcDebug], 'TfrmMain.mnuGrabTileIDClick', TRUE);
  if CurrentTile <> nil then
  begin
    internalTileID := GetInternalTileID(CurrentTile);

    // Выбираем группы
    if (not cbStatics.Checked) and (not cbTerrain.Checked) then
    begin
      treeNode := tvGroups.GetFirst();
      while treeNode <> nil do
      begin
        if TileInNode(treeNode, internalTileID) then
        begin
          tvGroups.Selected[treeNode] := True;
          tvGroups.FocusedNode := treeNode;
          if toMultiSelect in tvGroups.TreeOptions.SelectionOptions
            then break;
        end;
          treeNode := tvGroups.GetNext(treeNode);
      end;
    end;

    Logger.Send([lcClient, lcDebug], 'TfrmMain.mnuGrabTileIDClick', internalTileID);
    // Выбираем тайл
    item := vdtTiles.GetFirst;
    while item <> nil do
    begin
      tileInfo := vdtTiles.GetNodeData(item);
      if tileInfo^.ID = internalTileID then
      begin
        vdtTiles.ClearSelection;
        vdtTiles.Selected[item] := True;
        vdtTiles.FocusedNode := item;
        Break;
      end;
      item := vdtTiles.GetNext(item);
    end;
  end;
  Logger.Send([lcClient, lcDebug], 'TfrmMain.mnuGrabTileIDClick', FALSE);
end;

procedure TfrmMain.mnuGrabHueClick(Sender: TObject);
begin
  if CurrentTile is TStaticItem then
  begin
    frmHueSettings.lbHue.ItemIndex := TStaticItem(CurrentTile).Hue;
    frmFilter.JumpToHue(TStaticItem(CurrentTile).Hue);
  end;
end;

procedure TfrmMain.mnuGrabFilterTileIDClick(Sender: TObject);
begin
  if CurrentTile is TStaticItem then
  begin
    frmFilter.AddTile(GetInternalTileID(CurrentTile));
    frmFilter.cbTileFilter.Checked := True;
    frmMain.InvalidateFilter;
  end;
end;

procedure TfrmMain.mnuGrabFilterHueClick(Sender: TObject);
begin
  if CurrentTile is TStaticItem then
  begin
    frmFilter.AddHue(TStaticItem(CurrentTile).Hue);
    frmFilter.cbHueFilter.Checked := True;
    frmMain.InvalidateFilter;
  end;
end;

procedure TfrmMain.mnuGrabVirtualLayerZClick(Sender: TObject);
begin
  frmVirtualLayer.seZ.Value := CurrentTile.Z;
  frmVirtualLayer.seZChange(frmVirtualLayer.seZ);
  if not frmVirtualLayer.cbShowLayer.Checked then
  begin
    frmVirtualLayer.cbShowLayer.Checked := True;
    frmMain.InvalidateScreenBuffer;
  end;
  //cursorNeedsUpdate := True;
  //Handled := True;
end;

procedure TfrmMain.GrabBoundaries(Sender: TObject);
begin
  if Sender = mnuGrabBoundMinZ then begin
    frmBoundaries.seMinZ.Value := CurrentTile.Z; frmBoundaries.seMinZChange(nil); end
  else if Sender = mnuGrabBoundMaxZ then begin
    frmBoundaries.seMaxZ.Value := CurrentTile.Z; frmBoundaries.seMaxZChange(nil); end
  else if Sender = mnuGrabBoundMinX then begin
    frmBoundaries.seMinX.Value := CurrentTile.X; frmBoundaries.seMinXChange(nil); end
  else if Sender = mnuGrabBoundMaxX then begin
    frmBoundaries.seMaxX.Value := CurrentTile.X; frmBoundaries.seMaxXChange(nil); end
  else if Sender = mnuGrabBoundMinY then begin
    frmBoundaries.seMinY.Value := CurrentTile.Y; frmBoundaries.seMinYChange(nil); end
  else if Sender = mnuGrabBoundMaxY then begin
    frmBoundaries.seMaxY.Value := CurrentTile.Y; frmBoundaries.seMaxYChange(nil); end;
end;

procedure TfrmMain.mnuLargeScaleCommandsClick(Sender: TObject);
begin
  frmLargeScaleCommand.Show;
end;

procedure TfrmMain.mnuRegionControlClick(Sender: TObject);
begin
  frmRegionControl.Show;
end;

procedure TfrmMain.mnuShowAnimationsClick(Sender: TObject);
begin
  FTextureManager.UseAnims := mnuShowAnimations.Checked;
  RebuildScreenBuffer;
end;

procedure TfrmMain.mnuShutdownClick(Sender: TObject);
begin
  dmNetwork.Send(TQuitServerPacket.Create(''));
end;



procedure TfrmMain.mnuWhiteBackgroundClick(Sender: TObject);
begin
  FRepaintNeeded := True;
end;

procedure TfrmMain.mnuWindowedModeClick(Sender: TObject);
var btn: Integer;
begin
  btn := MessageDlg(lbDlgWindowedModeSwitchCaption, lbDlgWindowedModeSwitch,
                                       mtConfirmation, mbYesNoCancel, 0);
    // btn=2=mbCancel; btn=6=mbYes; btn=7=mbNo;
    // Почему так хз, по идее btn=ord(mbCancel) или TMsgDlgBtn(btn)=mbCancel,
    // но этой чертовой среде ничего нравитсо ("Pascale must die")
  if btn = 2 then mnuWindowedMode.Checked := not mnuWindowedMode.Checked else begin
    XMLPropStorage1.Save;
    if btn = 6 then begin
      XMLPropStorage1.Restore;
      dmNetwork.Disconnect;
    end;
  end;
end;

procedure TfrmMain.mnuZoomClick(Sender: TObject);
var
  zoom : Single;
  cache: Integer;
begin

  if Sender = tbZoom then begin
    if tbZoom.Down then mnuZoomClick(mnuZoom100)
    else case tbZoom.Tag of
       250: mnuZoomClick(mnuZoom025);
       333: mnuZoomClick(mnuZoom033);
       500: mnuZoomClick(mnuZoom050);
       750: mnuZoomClick(mnuZoom075);
      1000: mnuZoomClick(mnuZoom100);
      1500: mnuZoomClick(mnuZoom150);
      2000: mnuZoomClick(mnuZoom200);
      3000: mnuZoomClick(mnuZoom300);
      4000: mnuZoomClick(mnuZoom400);
    end;
    exit;
  end;

  if (Sender as TMenuItem).Checked then begin
    if mnuZoom100 <> Sender then
      mnuZoomClick(mnuZoom100);
    exit;
  end;

  (Sender as TMenuItem).Checked := True;
  if mnuZoom025 <> Sender then mnuZoom025.Checked:= False else begin zoom:=0.250; cache:=4096; end;
  if mnuZoom033 <> Sender then mnuZoom033.Checked:= False else begin zoom:=0.333; cache:=2048; end;
  if mnuZoom050 <> Sender then mnuZoom050.Checked:= False else begin zoom:=0.500; cache:=1024; end;
  if mnuZoom075 <> Sender then mnuZoom075.Checked:= False else begin zoom:=0.750; cache:= 512; end;
  if mnuZoom100 <> Sender then mnuZoom100.Checked:= False else begin zoom:=1.000; cache:= 256; end;
  if mnuZoom150 <> Sender then mnuZoom150.Checked:= False else begin zoom:=1.500; cache:= 128; end;
  if mnuZoom200 <> Sender then mnuZoom200.Checked:= False else begin zoom:=2.000; cache:=  64; end;
  if mnuZoom300 <> Sender then mnuZoom300.Checked:= False else begin zoom:=3.000; cache:=  32; end;
  if mnuZoom400 <> Sender then mnuZoom400.Checked:= False else begin zoom:=4.000; cache:=  16; end;

  cache := 2 * Trunc(Sqrt(oglGameWindow.Width * oglGameWindow.Width +
           oglGamewindow.Height * oglGamewindow.Height) / (44 * zoom));
  cache := cache*cache;

  //cache := 4096;
  FLandscape.ResizeBlockCache(cache);

  if zoom < 1.0 then tbZoom.ImageIndex:= 37 else
  if zoom > 1.0 then tbZoom.ImageIndex:= 36 else
  tbZoom.ImageIndex:= 35;
  tbZoom.Down := zoom <> 1.0;
  if zoom <> 1.0 then
    tbZoom.Tag := Trunc(zoom * 1000.0);

  FRepaintNeeded := True;
  RebuildScreenBuffer;
end;

procedure TfrmMain.oglGameWindowDblClick(Sender: TObject);
begin
  if (acSelect.Checked) and (CurrentTile <> nil) then
    btnAddRandomClick(Sender);
end;

procedure TfrmMain.oglGameWindowKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var step, zoomfactor: Integer;
begin
  if tbZoom.Down then zoomfactor := tbZoom.Tag else zoomfactor := 1000;
  if Shift = [ssShift]
    then step := 1
    else if Shift = []
       then step := 8000 div zoomfactor
       else exit;

  case Key of
    VK_W, VK_NUMPAD8, VK_UP:    MoveBy(-step, -step);
    VK_S, VK_NUMPAD2, VK_DOWN:  MoveBy(+step, +step);
    VK_A, VK_NUMPAD4, VK_LEFT:  MoveBy(-step, +step);
    VK_D, VK_NUMPAD6, VK_RIGHT: MoveBy(+step, -step);
    VK_Q, VK_NUMPAD7:           MoveBy(-step, 0);
    VK_E, VK_NUMPAD9:           MoveBy(0, -step);
    VK_Y, VK_NUMPAD1:           MoveBy(0, +step);
    VK_G, VK_NUMPAD3:           MoveBy(+step, 0);
    VK_SPACE:                   if frmFilter.Visible then begin
                                   frmFilter.Locked := True;
                                   frmFilter.Hide;
                                   frmFilter.Locked := False;
                                end else frmFilter.Show;
  end;
end;

procedure TfrmMain.oglGameWindowMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  Logger.EnterMethod([lcClient, lcDebug], 'MouseDown');
  try
    if Button = mbRight then
      pmTools.PopUp(Mouse.CursorPos.X, Mouse.CursorPos.Y);

    if Button = mbMiddle then
      tmGrabTileInfoTimer(Sender);

    if Button <> mbLeft then
      Exit;

    UpdateCurrentTile(X, Y);
    if FOverlayUI.ActiveArrow > -1 then
      tmMovement.Enabled := True;

    SelectedTile := CurrentTile;
    if CurrentTile = nil then Exit;

    if acSelect.Checked then                      //***** Selection Mode *****//
      tmGrabTileInfo.Enabled := True;

    FRepaintNeeded := True;
  finally
    Logger.ExitMethod([lcClient, lcDebug], 'MouseDown');
  end;
end;

procedure TfrmMain.oglGameWindowMouseEnter(Sender: TObject);
begin
  if Active then
    oglGameWindow.SetFocus;

  FOverlayUI.Visible := True;

  if (frmFilter.Visible and mnuAutoShowFilterWindow.Checked) then
  begin
    frmFilter.Locked := True;
    frmFilter.Hide;
    frmFilter.Locked := False;
  end;

  FRepaintNeeded := True;
end;

procedure TfrmMain.oglGameWindowMouseLeave(Sender: TObject);
begin
  if not (frmConfirmation.Visible or
          (frmMoveSettings.Visible and (fsModal in frmMoveSettings.FormState))
         ) then //during confirmation the mouse would leave ...
  begin
    CurrentTile := nil;
    FOverlayUI.Visible := False;
  end;

  FRepaintNeeded := True;
end;

procedure TfrmMain.oglGameWindowMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
  lastTile: TWorldItem;
  offsetX, offsetY: Integer;
begin
  //Logger.EnterMethod([lcClient, lcDebug], 'MouseMove');
  lastTile := CurrentTile;
  
  if ssMiddle in Shift then
  begin
    UpdateCurrentTile(X, Y);
    if (lastTile <> nil) and (CurrentTile <> nil) and (lastTile <> CurrentTile) then
    begin
      offsetX := lastTile.X - CurrentTile.X;
      offsetY := lastTile.Y - CurrentTile.Y;
      if InRange(offsetX, -8, 8) and InRange(offsetY, -8, 8) then
        SetPos(FX - offsetX, FY - offsetY);
    end;
  end;

  UpdateCurrentTile(X, Y);

  FRepaintNeeded := True;
  //Logger.ExitMethod([lcClient, lcDebug], 'MouseMove');
end;

procedure TfrmMain.oglGameWindowMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  map: TMapCell;
  i: Integer;
  z: ShortInt;
  blockInfo: PBlockInfo;
  targetRect: TRect;
  offsetX, offsetY: Integer;
  item: TWorldItem;
  tileX, tileY, newX, newY: Word;
  targetBlocks: TBlockInfoList;  //а в чем разница с targetTiles: TWorldItemList; ?
  targetTile: TWorldItem;
begin
  Logger.EnterMethod([lcClient, lcDebug], 'MouseUp');
  if Button <> mbLeft then
  begin
    Logger.ExitMethod([lcClient, lcDebug], 'MouseUp');
    Exit;
  end;

  UpdateCurrentTile(X, Y);
  tmMovement.Enabled := False;
  if CurrentTile = nil then
  begin
    SelectedTile := nil;
    Logger.ExitMethod([lcClient, lcDebug], 'MouseUp');
    Exit;
  end;

  targetTile := CurrentTile;
  
  if acSelect.Checked then
  begin
    if tmGrabTileInfo.Enabled then
    begin
      tmGrabTileInfo.Enabled := False;
      mnuGrabTileIDClick(nil);
    end;

    for i := FSelectionListeners.Count - 1 downto 0 do
      FSelectionListeners[i](CurrentTile);
  end;

  if (not acSelect.Checked) and (targetTile <> nil) and (SelectedTile <> nil) then
  begin
    targetRect := GetSelectedRect;
    FUndoList := GetNextUndoList;

    if (SelectedTile = targetTile) or ConfirmAction then
    begin
      if acDraw.Checked then                        //***** Drawing Mode *****//
      begin
        for tileX := FSelection.Left-1 to FSelection.Right do
          for tileY := FSelection.Top-1 to FSelection.Bottom do
          begin
            map := FLandscape.MapCell[tileX, tileY];
            if map.IsGhost then
            begin
              FUndoList^.Add(TDrawMapPacket.Create(tileX, tileY, map.RawZ, map.RawTileID));
              dmNetwork.Send(TDrawMapPacket.Create(tileX, tileY, map.Z, map.TileID));
            end;
          end;

        Logger.Send([lcClient, lcDebug], 'Virtual tiles', FVirtualTiles.Count);
        for i := 0 to FVirtualTiles.Count - 1 do
        begin
          item := FVirtualTiles[i];
          if item is TGhostTile then
          begin
            dmNetwork.Send(TInsertStaticPacket.Create(item.X, item.Y, item.Z, item.TileID, TGhostTile(item).Hue));
            FUndoList^.Add(TDeleteStaticPacket.Create(TGhostTile(item)));
          end;
        end;
      end else if (SelectedTile <> targetTile) or targetTile.CanBeEdited then
      begin
        if (not acMove.Checked) or (SelectedTile <> targetTile) or
           (not frmMoveSettings.cbAsk.Checked) or ConfirmAction then
        begin
          targetBlocks := TBlockInfoList.Create;
          if SelectedTile = targetTile then
          begin
            blockInfo := nil;
            while FScreenBuffer.Iterate(blockInfo) do
            begin
              if blockInfo^.Item = targetTile then
              begin
                targetBlocks.Add(blockInfo);
                Break;
              end;
            end;
          end else
          begin
            blockInfo := nil;
            while FScreenBuffer.Iterate(blockInfo) do
            begin
              if (blockInfo^.State = ssNormal) and
                blockInfo^.Item.CanBeEdited and
                IsInRect(blockInfo^.Item.X, blockInfo^.Item.Y, targetRect) then
              begin
                targetBlocks.Add(blockInfo);
              end;
            end;
          end;

          if acMove.Checked then                       //***** Move item *****//
          begin
            offsetX := frmMoveSettings.GetOffsetX;
            offsetY := frmMoveSettings.GetOffsetY;

            if (frmMoveSettings.cbLand.Checked) then begin
              if offsetX >= 0 then newX := $7FFF else newX := 0;
              if offsetY >= 0 then newY := $7FFF else newY := 0;
              for i := 0 to targetBlocks.Count - 1 do if (targetBlocks.Items[i]^.Item is TMapCell) then begin
                if offsetX >= 0 then newX := min(newX, targetBlocks.Items[i]^.Item.X) else newX := max(newX, targetBlocks.Items[i]^.Item.X - 1);
                if offsetY >= 0 then newY := min(newY, targetBlocks.Items[i]^.Item.Y) else newY := max(newY, targetBlocks.Items[i]^.Item.Y - 1);
              end;

              tileX := 0;
              if offsetY > 0 then for i := newY to newY + offsetY do inc(tileX, FLandscape.MapCell[newX,i].RawZ+128) else
              if offsetY < 0 then for i := newY + offsetY to newY do inc(tileX, FLandscape.MapCell[newX,i].RawZ+128);
              if offsetX > 0 then for i := newX to newX + offsetX do inc(tileX, FLandscape.MapCell[i,newY].RawZ+128) else
              if offsetX < 0 then for i := newX + offsetX to newX do inc(tileX, FLandscape.MapCell[i,newY].RawZ+128);
              if (offsetX <> 0) and (offsetY <> 0)
                 then tileY := tileX div (abs(offsetX) + abs(offsetY) + 2)
                 else tileY := tileX div (abs(offsetX) + abs(offsetY) + 1);
              z := max(-128, min(tileY - 128, +127));
            end;

            for i := 0 to targetBlocks.Count - 1 do
            begin
              if frmMoveSettings.cbLand.Checked and (((offsetY > 0) or (offsetX > 0)) and not ((offsetY > 0) and (offsetX < 0)))
                then tileY := abs(i - targetBlocks.Count + 1) else tileY := i;
              item := targetBlocks.Items[tileY]^.Item;

              if (frmMoveSettings.cbItem.Checked) and (item is TStaticItem) then begin
                newX := EnsureRange(item.X + offsetX, 0, FLandscape.CellWidth - 1);
                newY := EnsureRange(item.Y + offsetY, 0, FLandscape.CellHeight - 1);
                FUndoList^.Add(TMoveStaticPacket.Create(newX, newY, item.Z, item.TileID, TStaticItem(item).Hue, item.X, item.Y));
                dmNetwork.Send(TMoveStaticPacket.Create(TStaticItem(item), newX, newY));
              end;
              if (frmMoveSettings.cbLand.Checked) and (item is TMapCell) then begin
                newX := EnsureRange(item.X + offsetX, 0, FLandscape.CellWidth - 1);
                newY := EnsureRange(item.Y + offsetY, 0, FLandscape.CellHeight - 1);
                map := FLandscape.MapCell[newX, newY];

                // Это не очень хорошо, для оптимизации следует ввести специальный пакет TMoveMapPacket
                FUndoList^.Add(TDrawMapPacket.Create(item.X, item.Y, item.Z, item.TileID));
                FUndoList^.Add(TDrawMapPacket.Create(newX, newY, map.RawZ, map.TileID));
                dmNetwork.Send(TDrawMapPacket.Create(item.X, item.Y, z, $0001));
                dmNetwork.Send(TDrawMapPacket.Create(newX, newY, item.Z, item.TileID));

              end;
            end;
          end else if acElevate.Checked then        //***** Elevate item *****//
          begin
            for i := 0 to targetBlocks.Count - 1 do
            begin
              item := targetBlocks.Items[i]^.Item;

              z := frmElevateSettings.seZ.Value;
              if frmElevateSettings.rbRaise.Checked then
                z := EnsureRange(item.Z + z, -128, 127)
              else if frmElevateSettings.rbLower.Checked then
                z := EnsureRange(item.Z - z, -128, 127);

              if item is TMapCell then
              begin
                if frmElevateSettings.cbRandomHeight.Checked then
                  Inc(z, Random(frmElevateSettings.seRandomHeight.Value));
                FUndoList^.Add(TDrawMapPacket.Create(item.X, item.Y, item.Z,
                  item.TileID));
                dmNetwork.Send(TDrawMapPacket.Create(item.X, item.Y, z,
                  item.TileID));
              end else
              begin
                FUndoList^.Add(TElevateStaticPacket.Create(item.X, item.Y,
                  z, item.TileID, TStaticItem(item).Hue, item.Z));
                dmNetwork.Send(TElevateStaticPacket.Create(TStaticItem(item), z));
              end;
            end;
          end else if acDelete.Checked then          //***** Delete item *****//
          begin
            Logger.Send([lcClient, lcDebug], 'targetBlocks.Count', targetBlocks.Count);
            for i := 0 to targetBlocks.Count - 1 do
            begin
              item := targetBlocks.Items[i]^.Item;
              if item is TStaticItem then
              begin
                FUndoList^.Add(TInsertStaticPacket.Create(item.X, item.Y,
                  item.Z, item.TileID, TStaticItem(item).Hue));
                dmNetwork.Send(TDeleteStaticPacket.Create(TStaticItem(item)));
              end;
            end;
          end else if acHue.Checked then                //***** Hue item *****//
          begin
            for i := 0 to targetBlocks.Count - 1 do
            begin
              blockInfo := targetBlocks.Items[i];
              item := blockInfo^.Item;

              if blockInfo^.HueOverride and (item is TStaticItem) then
              begin
                if TStaticItem(item).Hue <> blockInfo^.Hue then
                begin
                  FUndoList^.Add(THueStaticPacket.Create(item.X, item.Y, item.Z,
                    item.TileID, blockInfo^.Hue, TStaticItem(item).Hue));
                dmNetwork.Send(THueStaticPacket.Create(TStaticItem(item),
                    blockInfo^.Hue));
                end;
              end;
            end;
            blockInfo := nil;
          end;

          targetBlocks.Free;
        end;
      end;
    end;
  end;
  acUndo.Enabled := FUndoList^.Count > 0;
  SelectedTile := nil;
  FRepaintNeeded := True;
  Logger.ExitMethod([lcClient, lcDebug], 'MouseUp');
end;

procedure TfrmMain.oglGameWindowMouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
var
  cursorNeedsUpdate: Boolean;
  newZ: ShortInt;
begin
  //We want single steps ...
  WheelDelta := WheelDelta div WHEEL_DELTA;

  if ((Word(GetKeyState(VK_X)) and $8000)<>0) then begin
    if (Shift = [ssCtrl]) then begin
      frmBoundaries.seMinX.Value := frmBoundaries.seMinX.Value + min(max(WheelDelta,
          frmBoundaries.seMinX.MinValue-frmBoundaries.seMinX.Value),
          frmBoundaries.seMinX.MaxValue-frmBoundaries.seMinX.Value);
      frmBoundaries.seMinXChange(nil); exit; end
    else if (Shift = [ssShift]) then begin
      frmBoundaries.seMaxX.Value := frmBoundaries.seMaxX.Value + min(max(WheelDelta,
          frmBoundaries.seMaxX.MinValue-frmBoundaries.seMaxX.Value),
          frmBoundaries.seMaxX.MaxValue-frmBoundaries.seMaxX.Value);
      frmBoundaries.seMaxXChange(nil); exit; end;
  end else if ((Word(GetKeyState(VK_C)) and $8000)<>0) then begin
    if (Shift = [ssCtrl]) then begin
      frmBoundaries.seMinY.Value := frmBoundaries.seMinY.Value + min(max(WheelDelta,
          frmBoundaries.seMinY.MinValue-frmBoundaries.seMinY.Value),
          frmBoundaries.seMinY.MaxValue-frmBoundaries.seMinY.Value);
      frmBoundaries.seMinYChange(nil); exit; end
    else if (Shift = [ssShift]) then begin
      frmBoundaries.seMaxY.Value := frmBoundaries.seMaxY.Value + min(max(WheelDelta,
          frmBoundaries.seMaxY.MinValue-frmBoundaries.seMaxY.Value),
          frmBoundaries.seMaxY.MaxValue-frmBoundaries.seMaxY.Value);
      frmBoundaries.seMaxYChange(nil); exit; end;
  end else begin
    if (Shift = [ssCtrl]) then begin
      frmBoundaries.seMinZ.Value := frmBoundaries.seMinZ.Value + min(max(WheelDelta,
          frmBoundaries.seMinZ.MinValue-frmBoundaries.seMinZ.Value),
          frmBoundaries.seMinZ.MaxValue-frmBoundaries.seMinZ.Value);
      frmBoundaries.seMinZChange(nil); exit; end
    else if (Shift = [ssShift]) then begin
      frmBoundaries.seMaxZ.Value := frmBoundaries.seMaxZ.Value + min(max(WheelDelta,
          frmBoundaries.seMaxZ.MinValue-frmBoundaries.seMaxZ.Value),
          frmBoundaries.seMaxZ.MaxValue-frmBoundaries.seMaxZ.Value);
      frmBoundaries.seMaxZChange(nil); exit; end;
  end;


  if CurrentTile = nil then
    Exit;

  cursorNeedsUpdate := False;
  if (CurrentTile is TVirtualTile) or ((ssCtrl in Shift) and
     (frmVirtualLayer.cbShowLayer.Checked)) then
  begin
    frmVirtualLayer.seZ.Value := EnsureRange(frmVirtualLayer.seZ.Value +
      WheelDelta, -128, 127);
    frmVirtualLayer.seZChange(frmVirtualLayer.seZ);
    cursorNeedsUpdate := True;
    Handled := True;
  end else if not (ssCtrl in Shift) then
  begin
    FUndoList := GetNextUndoList;
    newZ := EnsureRange(CurrentTile.Z + WheelDelta, -128, 127);
    if CurrentTile is TStaticItem then
    begin
      FUndoList^.Add(TElevateStaticPacket.Create(CurrentTile.X, CurrentTile.Y,
        newZ, CurrentTile.TileID, TStaticItem(CurrentTile).Hue,
        CurrentTile.Z));
      dmNetwork.Send(TElevateStaticPacket.Create(TStaticItem(CurrentTile),
        newZ));
      cursorNeedsUpdate := True;
      Handled := True;
    end else if CurrentTile is TMapCell then
    begin
      FUndoList^.Add(TDrawMapPacket.Create(CurrentTile.X, CurrentTile.Y,
        CurrentTile.Z, CurrentTile.TileID));
      dmNetwork.Send(TDrawMapPacket.Create(CurrentTile.X, CurrentTile.Y,
        newZ, CurrentTile.TileID));
      Handled := True;
    end;
    acUndo.Enabled := FUndoList^.Count > 0;
  end;
  
  if cursorNeedsUpdate then
  begin
    SetCursorPos(Mouse.CursorPos.X, Mouse.CursorPos.Y - 4 * WheelDelta);
    UpdateCurrentTile(MousePos.X, MousePos.Y - 4 * WheelDelta);
  end;

  FRepaintNeeded := True;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
var
  i : Integer;
  ARegistry: TRegistry;
begin
  Logger.EnterMethod([lcLandscape, lcDebug], 'TfrmMain.FormCreate(Sender: TObject)');

  // Подменяем TLazVirtualDrawTree на его перегруженный аналог TVirtualList
  vdtTiles := TVirtualList.Create(vdtTiles);
  vdtRandom := TVirtualList.Create(vdtRandom);
  vdlRandom := TVirtualList(vdtRandom); // Лазареусу пчмуто не нравиться если поменять тип у vdtRandom

  oglGameWindow.Cursor := +01;
  pcLeft.ActivePageIndex  := tsNavigation.PageIndex;

  LanguageTranslate(Self);
  Application.TaskBarBehavior := tbSingleButton;

  {
  BorderStyle := bsNone;
  FormStyle   := fsStayOnTop;
  WindowState := wsMaximized;
  Left:=0; Top:=0;
  Width:=1920;
  Height:=1200; }
  //oglGameWindow.Visible:= false;
  //oglGameWindowResize(Sender);

  FConfigDir := GetAppConfigDir(False);

  (*ARegistry := TRegistry.Create();
  ARegistry.RootKey := HKEY_LOCAL_MACHINE;
  ARegistry.OpenKey('\SOFTWARE\Quintessence\UO CentrED+', False);

  FAppDir := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName));
  FLocalDir := FAppDir + '..' + PathDelim + 'LocalData' + PathDelim;
  if ARegistry.ReadBool('UseConfigDir')
    then FConfigDir := GetAppConfigDir(False)
    else FConfigDir := FLocalDir + 'UsersData' + PathDelim;
  ARegistry.Free;*) //TODO cross platform

  if (sprofile <> '')
    then FProfileDir := FConfigDir + 'Profiles' + PathDelim + UTF8ToCP1251(sprofile) + PathDelim
    else FProfileDir := '';
  ForceDirectories(FConfigDir);

  if (FProfileDir <> '')
    then XMLPropStorage1.FileName := FProfileDir + 'Config.xml'
    else XMLPropStorage1.FileName := FConfigDir + 'Config.xml';
  XMLPropStorage1.Active := True;

  FLandscape := ResMan.Landscape;
  FLandscape.OnChange := @OnLandscapeChanged;
  FLandscape.OnMapChanged := @OnMapChanged;
  FLandscape.OnNewBlock := @OnNewBlock;
  FLandscape.OnStaticDeleted := @OnStaticDeleted;
  FLandscape.OnStaticElevated := @OnStaticElevated;
  FLandscape.OnStaticHued := @OnStaticHued;
  FLandscape.OnStaticInserted := @OnStaticInserted;

  Logger.Send([lcClient, lcInfo], 'LoadNoDrawMap()...');
  if FileExists(FProfileDir + 'VirtualTiles.xml') then
    FLandscape.LoadNoDrawMap(FProfileDir + 'VirtualTiles.xml')
  else begin
    if FileExists(FLocalDir + 'VirtualTiles.xml') then
      FLandscape.LoadNoDrawMap(FLocalDir + 'VirtualTiles.xml');
    if FileExists(FConfigDir + 'VirtualTiles.xml') then
      FLandscape.LoadNoDrawMap(FConfigDir + 'VirtualTiles.xml');
    if FileExists(ResMan.GetFile('VirtualTiles.xml')) then
      FLandscape.LoadNoDrawMap(ResMan.GetFile('VirtualTiles.xml'));
  end;

  FTextureManager := TLandTextureManager.Create;
  FScreenBuffer := TScreenBuffer.Create;
  FScreenBufferState := [];
  X := 0;
  Y := 0;
  edX.MaxValue := FLandscape.CellWidth;
  edY.MaxValue := FLandscape.CellHeight;
  FOverlayUI := TOverlayUI.Create;
  FLightManager := TLightManager.Create(@GetDrawOffset);
  
  ProcessAccessLevel;

  Logger.Send([lcClient, lcInfo], 'LoadLightSourceTiles()...');
  if FileExists(FProfileDir + 'VirtualTiles.xml') then
    LoadLightSourceTiles (FProfileDir + 'VirtualTiles.xml')
  else begin
    if FileExists(FLocalDir + 'VirtualTiles.xml') then
      LoadLightSourceTiles (FLocalDir + 'VirtualTiles.xml');
    if FileExists(FConfigDir + 'VirtualTiles.xml') then
      LoadLightSourceTiles (FConfigDir + 'VirtualTiles.xml');
    if FileExists(ResMan.GetFile('VirtualTiles.xml')) then
      LoadLightSourceTiles (ResMan.GetFile('VirtualTiles.xml'));
  end;

  Logger.Send([lcClient, lcInfo], 'LoadVisibleTiles()...');
  if FileExists(FProfileDir + 'VirtualTiles.xml') then
    LoadVisibleTiles(FProfileDir + 'VirtualTiles.xml')
  else begin
    if FileExists(FLocalDir + 'VirtualTiles.xml') then
      LoadVisibleTiles(FLocalDir + 'VirtualTiles.xml');
    if FileExists(FConfigDir + 'VirtualTiles.xml') then
      LoadVisibleTiles(FConfigDir + 'VirtualTiles.xml');
    if FileExists(ResMan.GetFile('VirtualTiles.xml')) then
      LoadVisibleTiles(ResMan.GetFile('VirtualTiles.xml'));
  end;

  Logger.Send([lcClient, lcInfo], 'LoadColorLight()...');
  if FileExists(FProfileDir + 'ColorLight.xml') then
    FLightManager.LoadConfig(FProfileDir + 'ColorLight.xml')
  else begin
    if FileExists(FLocalDir + 'ColorLight.xml') then
      FLightManager.LoadConfig(FLocalDir + 'ColorLight.xml');
    if FileExists(FConfigDir + 'ColorLight.xml') then
      FLightManager.LoadConfig(FConfigDir + 'ColorLight.xml');
    if FileExists(ResMan.GetFile('ColorLight.xml')) then
      FLightManager.LoadConfig(ResMan.GetFile('ColorLight.xml'));
  end;

  FTilesSelectionUndoRedoCommandGroup  := TUndoRedoCommandGroup.Create;
  FGroupsSelectionUndoRedoCommandGroup := TUndoRedoCommandGroup.Create;
  FTilesSelectionUndoRedoManager  := TUndoRedoManager.Create;
  FGroupsSelectionUndoRedoManager := TUndoRedoManager.Create;
  tvGroups.NodeDataSize := SizeOf(TGroupNode);
  vdtTiles.NodeDataSize := SizeOf(TTileInfo);
  vdlRandom.NodeDataSize := SizeOf(TTileInfo);
  //mnuTileListViewClick(nil);
  LoadEntryTilesList;
  LoadBrushTilesList;
  LoadSurfsTilesList;
  BuildGroupList;
  BuildTileList;
  Randomize;
  
  vstChat.NodeDataSize := SizeOf(TChatInfo);
  pnlChatHeader.AnchorSide[akBottom].Control := pcLeft;
  pnlChatHeader.AnchorSide[akBottom].Side := asrBottom;

  if FProfileDir <> ''
    then FLocationsFile := FProfileDir + 'Locations.xml'
    else FLocationsFile := FConfigDir + 'Locations.xml';
  vstLocations.NodeDataSize := SizeOf(TLocationInfo);
  LoadLocations;

  vstClients.NodeDataSize := SizeOf(TClientInfo);

  Logger.Send([lcClient, lcInfo], 'RegisterPacketHandler()...');
  RegisterPacketHandler($0C, TPacketHandler.Create(0, @OnClientHandlingPacket));

  FVLayerImage := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/VirtualLayer.tga'));
  FVLightSrcImage[ 1] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24nn.tga'));
  FVLightSrcImage[ 2] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24bw.tga'));
  FVLightSrcImage[ 3] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24tw.tga'));
  FVLightSrcImage[ 4] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24bo.tga'));
  FVLightSrcImage[ 5] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24to.tga'));
  FVLightSrcImage[ 6] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24by.tga'));
  FVLightSrcImage[ 7] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24ty.tga'));
  FVLightSrcImage[ 8] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24ny.tga'));
  FVLightSrcImage[ 9] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24bl.tga'));
  FVLightSrcImage[10] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24bb.tga'));
  FVLightSrcImage[11] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24tb.tga'));
  FVLightSrcImage[12] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24bg.tga'));
  FVLightSrcImage[13] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24tg.tga'));
  FVLightSrcImage[14] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24br.tga'));
  FVLightSrcImage[15] := TSingleImage.CreateFromStream(ResourceManager.GetResource('Overlay/LightBulb_24bp.tga'));

  FGLFont := TGLFont.Create;
  FGLFont.LoadImage(ResourceManager.GetResource('GLFont/DejaVu.png'));
  FGLFont.LoadFontInfo(ResourceManager.GetResource('GLFont/DejaVu.fnt'));

  Logger.Send([lcClient, lcInfo], 'TWorldItemList.Create(True)...');
  FVirtualTiles := TWorldItemList.Create(True);

  for i:=1 to FUndoListLength do
    FUndoListArray[i] := TPacketList.Create(True);
  FUndoList := @FUndoListArray[1];
  FUndoListFirstIndex := 0;
  FundoListLastIndex  := 0;

  if FProfileDir <> ''
    then FRandomPresetsFile := FProfileDir + 'RandomPresets.xml'
    else FRandomPresetsFile := FConfigDir + 'RandomPresets.xml';
  LoadRandomPresets;

  Logger.Send([lcClient, lcInfo], 'Завершение загрузки...');
  DoubleBuffered := True;
  //pnlBottom.DoubleBuffered := True;

  FAccessChangedListeners := TAccessChangedListeners.Create;
  FSelectionListeners := TSelectionListeners.Create;
  
  FLastDraw := Now;

  Logger.ExitMethod([lcLandscape, lcDebug], 'TfrmMain.FormCreate(Sender: TObject)');
end;

procedure TfrmMain.btnGoToClick(Sender: TObject);
begin
  SetPos(edX.Value, edY.Value);
end;

procedure TfrmMain.btnRandomPresetDeleteClick(Sender: TObject);
var
  preset: TDOMElement;
begin
  if cbRandomPreset.ItemIndex > -1 then
  begin
    preset := TDOMElement(cbRandomPreset.Items.Objects[cbRandomPreset.ItemIndex]);
    FRandomPresetsDoc.DocumentElement.RemoveChild(preset);
    cbRandomPreset.Items.Delete(cbRandomPreset.ItemIndex);
    cbRandomPreset.ItemIndex := -1;
  end;
end;

procedure TfrmMain.btnRandomPresetSaveClick(Sender: TObject);
var
  presetName: string;
  i: Integer;
  presetElement, tileElement: TDOMElement;
  children: TDOMNodeList;
  tileNode: PVirtualItem;
  tileInfo: PTileInfo;
begin
  presetName := cbRandomPreset.Text;
  if InputQuery(lbDlgSaveRandPrsCaption, lbDlgSaveRandPrs, presetName) then
  begin
    presetElement := FindRandomPreset(presetName);
    if presetElement = nil then
    begin
      presetElement := FRandomPresetsDoc.CreateElement('PresetElement');
      presetElement.AttribStrings['Name'] := UTF8ToCP1251(presetName);
      FRandomPresetsDoc.DocumentElement.AppendChild(presetElement);
      cbRandomPreset.Items.AddObject(presetName, presetElement);
    end else
    begin
      children := presetElement.ChildNodes;
      for i := children.Count - 1 downto 0 do
        presetElement.RemoveChild(children[i]);
    end;

    tileNode := vdlRandom.GetFirst;
    while tileNode <> nil do
    begin
      tileInfo := vdlRandom.GetNodeData(tileNode);
      tileElement := FRandomPresetsDoc.CreateElement('TileElement');
      tileElement.AttribStrings['ID'] := IntToStr(tileInfo^.ID);
      presetElement.AppendChild(tileElement);
      tileNode := vdlRandom.GetNext(tileNode);
    end;

    cbRandomPreset.ItemIndex := cbRandomPreset.Items.IndexOfObject(presetElement);

    SaveRandomPresets;
  end;
end;

procedure TfrmMain.cbRandomPresetChange(Sender: TObject);
var
  presetElement, tileElement: TDOMElement;
  tiles: TDOMNodeList;
  tileNode: PVirtualItem;
  tileInfo: PTileInfo;
  i, id: Integer;
begin
  if cbRandomPreset.ItemIndex > -1 then
  begin
    vdlRandom.Clear;
    presetElement := TDOMElement(cbRandomPreset.Items.Objects[cbRandomPreset.ItemIndex]);
    tiles := presetElement.ChildNodes;
    for i := 0 to tiles.Count - 1 do
    begin
      tileElement := TDOMElement(tiles[i]);
      if (tileElement.NodeName = 'TileElement') and
         TryStrToInt(tileElement.AttribStrings['ID'], id) and
         (id < FLandscape.MaxStaticID + $4000) then
      begin
        tileNode := vdlRandom.AddItem(nil);
        tileInfo := vdlRandom.GetNodeData(tileNode);
        tileInfo^.ID := id;
      end;
    end;
  end;
end;

procedure TfrmMain.btnAddRandomClick(Sender: TObject);
var
  selected: PVirtualItem;
  node: PVirtualItem;
  sourceTileInfo, targetTileInfo: PTileInfo;
begin
  vdlRandom.BeginUpdate;
  selected := vdtTiles.GetFirstSelected;
  while selected <> nil do
  begin
    sourceTileInfo := vdtTiles.GetNodeData(selected);
    node := vdlRandom.AddItem(nil);
    targetTileInfo := vdlRandom.GetNodeData(node);
    targetTileInfo^.ID  := sourceTileInfo^.ID;
    targetTileInfo^.ptr := sourceTileInfo^.ptr;
    selected := vdtTiles.GetNextSelected(selected);
  end;
  vdlRandom.EndUpdate;
end;

procedure TfrmMain.btnClearLocationsClick(Sender: TObject);
begin
  if MessageDlg(lbDlgDelConfCaption, lbDlgDelConf,
    mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    vstLocations.Clear;
  end;
end;

procedure TfrmMain.ApplicationProperties1Idle(Sender: TObject; var Done: Boolean);
begin
  if (FScreenBufferState <> CScreenBufferValid) or
     ((FRepaintNeeded or mnuShowAnimations.Checked) and
      (MilliSecondsBetween(Now, FLastDraw) > 50)) then
  begin
    //Logger.Send([lcClient, lcDebug], 'Repainting Game Window');
    oglGameWindow.Repaint;
    FLastDraw := Now;
    FRepaintNeeded := False;
  end;
  Sleep(1);
  Done := False;
end;

procedure TfrmMain.ApplicationProperties1ShowHint(var HintStr: string;
  var CanShow: Boolean; var HintInfo: THintInfo);
begin
  //that check is a bit dirty, but serves its purpose
  //(i.e. to set the timeout for the tile info hints)
  if HintStr = '-' then
    HintInfo.HideTimeout := Application.HintHidePause +
      Application.HintHidePausePerChar * (Length(FTileHint.Name) +
      Length(FTileHint.Obj) + Length(FTileHint.Flags));
end;

procedure TfrmMain.btnAddLocationClick(Sender: TObject);
var
  locationName: string;
  locationInfo: PLocationInfo;
begin
  locationName := '';
  if InputQuery(lbDlgNewQuerryCaption, lbDlgNewQuerry,
    locationName) then
  begin
    locationInfo := vstLocations.GetNodeData(vstLocations.AddChild(nil));
    locationInfo^.X := X;
    locationInfo^.Y := Y;
    locationInfo^.Name := locationName;
  end;
end;

procedure TfrmMain.acSelectExecute(Sender: TObject);
begin
  acSelect.Checked := True;
  tbSelect.Down := True;
  mnuSelect.Checked := True;
  ProcessToolState;
end;

procedure TfrmMain.acSelectionExecute(Sender: TObject);
begin
  acSelection.Checked := True;
  tbSelection.Down := True;
  mnuSelection.Checked := True;
  frmSelectionSettings.Show;
  ProcessToolState;
end;

function TfrmMain.GetNextUndoList: PPacketList;
var
  i: Integer;
begin
  Inc(FUndoListLastIndex, 1);
  if FUndoListLastIndex > FUndoListLength
     then FUndoListLastIndex := 1;

  if (FUndoListFirstIndex = FUndoListLastIndex) or (FUndoListFirstIndex = 0)
    then Inc(FUndoListFirstIndex, 1);
  if FUndoListFirstIndex > FUndoListLength
    then FUndoListFirstIndex := 1;

  if FUndoListFirstIndex <= FUndoListLastIndex
    then i := FUndoListLastIndex - FUndoListFirstIndex + 1
    else i := FUndoListLastIndex + FUndoListLength - FUndoListFirstIndex + 1;
  tbUndo.Hint := lbToolbarUndo + ' (' + IntToStr(i) + ').';

  FUndoListArray[FundoListLastIndex].Clear;
  GetNextUndoList := @FUndoListArray[FundoListLastIndex];
end;

procedure TfrmMain.acUndoExecute(Sender: TObject);
var
  i: Integer;
begin
  for i := FUndoList^.Count - 1 downto 0 do
  begin
    dmNetwork.Send(FUndoList^[i]);
    FUndoList^[i] := nil;
  end;
  FUndoList^.Clear;

  if FUndoListLastIndex = FUndoListFirstIndex then
    begin
      FUndoListFirstIndex:= 0;
      FUndoListLastIndex := 0;
      tbUndo.Hint := lbToolbarUndo + ' (0).';
      acUndo.Enabled := False;
    end
  else
    begin
      Inc(FUndoListLastIndex, -1);
      if FUndoListLastIndex = 0
        then FUndoListLastIndex := FUndoListLength;
      FUndoList := @FUndoListArray[FundoListLastIndex];
      if FUndoListFirstIndex <= FUndoListLastIndex
        then i := FUndoListLastIndex - FUndoListFirstIndex + 1
        else i := FUndoListLastIndex + FUndoListLength - FUndoListFirstIndex + 1;
      tbUndo.Hint := lbToolbarUndo + ' (' + IntToStr(i) + ').';
    end;
end;

procedure TfrmMain.acVirtualLayerExecute(Sender: TObject);
begin
  frmVirtualLayer.Show;
end;

procedure TfrmMain.acTerrainExecute(Sender: TObject);
begin
  acTerrain.Checked := not acTerrain.Checked;
  //tbTerrain.Down := acTerrain.Checked;
  RebuildScreenBuffer;
end;

procedure TfrmMain.acStaticsExecute(Sender: TObject);
begin
  acStatics.Checked := not acStatics.Checked;
  RebuildScreenBuffer;
end;

procedure TfrmMain.acWalkableExecute(Sender: TObject);
begin
  InvalidateFilter;
  FRepaintNeeded := True;
end;

procedure TfrmMain.acDrawExecute(Sender: TObject);
begin
  acDraw.Checked := True;
  tbDrawTile.Down := True;
  mnuDraw.Checked := True;
  frmDrawSettings.Show;
  ProcessToolState;
end;

procedure TfrmMain.acFillExecute(Sender: TObject);
begin
  acFill.Checked := True;
  tbFill.Down := True;
  mnuFill.Checked := True;
  frmFillSettings.Show;
  ProcessToolState;
end;

procedure TfrmMain.acDeleteExecute(Sender: TObject);
begin
  acDelete.Checked := True;
  tbDeleteTile.Down := True;
  mnuDelete.Checked := True;
  ProcessToolState;
end;

procedure TfrmMain.acBoundariesExecute(Sender: TObject);
begin
  frmBoundaries.Show;
end;

procedure TfrmMain.acElevateExecute(Sender: TObject);
begin
  acElevate.Checked := True;
  tbElevateTile.Down := True;
  mnuElevate.Checked := True;
  ProcessToolState;
  frmElevateSettings.Show;
end;



procedure TfrmMain.acSurfElevateExecute(Sender: TObject);
begin
  acSurfElevate.Checked := True;
  tbSurfElevate.Down := True;
  mnuSurfElevate.Checked := True;
  frmSurfElevateSettings.Show;
  ProcessToolState;
end;

procedure TfrmMain.acSurfStretchExecute(Sender: TObject);
begin
  acSurfStretch.Checked := True;
  tbSurfStretch.Down := True;
  mnuSurfStretch.Checked := True;
  frmSurfStretchSettings.Show;
  ProcessToolState;
end;

procedure TfrmMain.acSurfSmoothExecute(Sender: TObject);
begin
  acSurfSmooth.Checked := True;
  tbSurfSmooth.Down := True;
  mnuSurfSmooth.Checked := True;
  frmSurfSmoothSettings.Show;
  ProcessToolState;
end;

procedure TfrmMain.acFilterExecute(Sender: TObject);
begin
  if acFilter.Checked then
  begin
    frmFilter.Show;
    frmFilter.Locked := False;
    if (tbFilter.Down) then begin
      frmFilter.tFormClose.Interval := 1500;
      frmFilter.tFormClose.Tag := PtrInt(True);
      frmFilter.tFormClose.Enabled := True;
    end;
  end else
    frmFilter.Hide;
  InvalidateFilter;
end;

procedure TfrmMain.acFlatExecute(Sender: TObject);
begin
  acFlat.Checked := not acFlat.Checked;
  RebuildScreenBuffer;
end;

procedure TfrmMain.acHueExecute(Sender: TObject);
begin
  acHue.Checked := True;
  tbSetHue.Down := True;
  mnuSetHue.Checked := True;
  ProcessToolState;
  frmHueSettings.Show;
end;

procedure TfrmMain.acLightlevelExecute(Sender: TObject);
begin
  frmLightlevel.Show;
end;

procedure TfrmMain.acMoveExecute(Sender: TObject);
begin
  acMove.Checked := True;
  tbMoveTile.Down := True;
  mnuMove.Checked := True;
  ProcessToolState;
  frmMoveSettings.Show;
end;

procedure TfrmMain.acNoDrawExecute(Sender: TObject);
begin
  acNoDraw.Checked := not acNoDraw.Checked;
  RebuildScreenBuffer;
end;

procedure TfrmMain.btnClearRandomClick(Sender: TObject);
begin
  vdlRandom.BeginUpdate;
  vdlRandom.Clear;
  vdlRandom.EndUpdate;
end;

procedure TfrmMain.btnDeleteLocationClick(Sender: TObject);
begin
  vstLocations.DeleteSelectedNodes;
end;

procedure TfrmMain.btnDeleteRandomClick(Sender: TObject);
begin
  vdlRandom.BeginUpdate;
  vdlRandom.DeleteSelectedNodes;
  vdlRandom.EndUpdate;
end;

procedure TfrmMain.cbStaticsChange(Sender: TObject);
begin
  //tvGroups.Enabled := not (cbStatics.Checked or cbTerrain.Checked);
  if mnuAutoHideGroupList.Checked then begin
    if (((cbStatics.Checked) or (cbTerrain.Checked)) = spGroupList.Enabled)
      then mnuAutoHideGroupListClick(Sender);
  end else if (cbStatics.Checked or cbTerrain.Checked)
    then tvGroups.ClearSelection;

  //if (not cbStatics.Checked) and (not cbTerrain.Checked) then
  //  cbTerrain.Checked := True;

  //if tvGroups.Selected <> nil then
  //  tvGroups.Selected := nil
  //else
    BuildTileList;
end;

procedure TfrmMain.cbTerrainChange(Sender: TObject);
begin
  if mnuAutoHideGroupList.Checked then
    if (((cbStatics.Checked) or (cbTerrain.Checked)) = spGroupList.Enabled)
      then mnuAutoHideGroupListClick(Sender);
  //if (not cbTerrain.Checked) and (not cbStatics.Checked) then
  //  cbStatics.Checked := True;

  //if tvGroups.Selected <> nil then
  //  tvGroups.Selected := nil
  //else
    BuildTileList;
end;

procedure TfrmMain.tvSelectGroupsChanged(Sender: TObject);
begin
  if (not cbTerrain.Checked) and (not cbStatics.Checked) then
     BuildTileList;
 // if Sender = cbStatics then
   // MessageDlg('Ошибка', 'Тайл с указанным ID не был найден.' +
     //   LineEnding + 'Проверте конфликты настроек фильтров.', mtError, [mbOK], 0);
  if cbTerrain.Checked then cbTerrain.Checked := False;
  if cbStatics.Checked then cbStatics.Checked := False;
end;

procedure TfrmMain.mnuReloadGroupsClick(Sender: TObject);
var
  fPath : string;
begin
  FreeGroupLists;
  LoadEntryTilesList;
  LoadBrushTilesList;
  LoadSurfsTilesList;
  BuildGroupList;

  if FileExists(FProfileDir + 'ColorLight.xml')
    then fPath := (FProfileDir + 'ColorLight.xml')
    else if FileExists(FLocalDir + 'ColorLight.xml')
      then fPath := (FLocalDir + 'ColorLight.xml')
      else if FileExists(FConfigDir + 'ColorLight.xml')
        then fPath := (FConfigDir + 'ColorLight.xml')
        else if FileExists(ResMan.GetFile('ColorLight.xml'))
          then fPath := (ResMan.GetFile('ColorLight.xml'))
          else Exit;
  FLightManager.LoadConfig(fPath);

  if FileExists(FProfileDir + 'VirtualTiles.xml')
    then fPath := (FProfileDir + 'VirtualTiles.xml')
    else if FileExists(FLocalDir + 'VirtualTiles.xml')
      then fPath := (FLocalDir + 'VirtualTiles.xml')
      else if FileExists(FConfigDir + 'VirtualTiles.xml')
        then fPath := (FConfigDir + 'VirtualTiles.xml')
        else if FileExists(ResMan.GetFile('VirtualTiles.xml'))
          then fPath := (ResMan.GetFile('VirtualTiles.xml'))
          else Exit;
  LoadLightSourceTiles(fPath);
  LoadVisibleTiles(fPath);
end;

procedure TfrmMain.mnuSetLanguageClick(Sender: TObject);
var
  settings: TIniFile;
begin
  LanguageSet(Integer(TMenuItem(Sender).Tag));
  ReloadLanguageTranslation();
  frmMain.Menu := nil; // Перерисовываем главное меню
  frmMain.Menu := MainMenu1;
  ProcessAccessLevel;  // Обновляем заголовок формы

  // Запоминаем выбранный язык
  settings := TIniFile.Create(FConfigDir + 'LoginSettings.ini');
  settings.WriteString('Profile', 'Lang', TMenuItem(Sender).Caption);
  settings.Free;
end;

//procedure ssrSaveHandle(handle: HWND; filepath: PChar);  external  'SSRender';
  //function GetDesktopWindow(): HWND; external  'SSRender';
//function GetDesktopWindow:HWND; external 'user32' name 'GetDesktopWindow';
procedure TfrmMain.mnuMakeScreenShotClick(Sender: TObject);
var
  Bitmap: TBitmap;
  Image: TJPEGImage;
  ScreenWND: HWND;
  ScreenDC: HDC;
  DateTime: TDateTime;
  FolderPath: string;
  Refocus: Boolean;
  srcRect, destRect: TRect;
  oglPoint: TPoint;
begin
  Bitmap:= TBitmap.Create;
  Image := TJPEGImage.Create;
  // We remove the focus from the window (to remove the arrows)
  Refocus := oglGameWindow.Focused;
  if Refocus then begin
    oglGameWindowMouseLeave(Sender);
    oglGameWindow.Paint;
  end;
  // Obtaining device context
  oglGameWindow.HandleNeeded;
  //TODO : ScreenWND := GetDesktopWindow();// oglGameWindow.Handle; //Handle;//0;//
  ScreenDC := GetDC(ScreenWND);   //GetDeviceContext(ScreenWND);
  if ScreenDC<>0 then try
    // We get the "buffer" screen
    Bitmap.LoadFromDevice(ScreenDC);
    // The size of the stored image
    destRect.Left  :=0;
    destRect.Top   :=0;
    destRect.Right :=oglGameWindow.Width;
    destRect.Bottom:=oglGameWindow.Height;
    Image.SetSize(destRect.Right, destRect.Bottom);
    // Keep the area on the screen
    oglPoint.x := 0;  oglPoint.y := 0;
    oglPoint := oglGameWindow.ClientToScreen(oglPoint);
    srcRect.Left   := oglPoint.x;
    srcRect.Top    := oglPoint.y;
    srcRect.Right  := srcRect.Left + destRect.Right;
    srcRect.Bottom := srcRect.Top  + destRect.Bottom;
    // Copy the area from the screen to the stored image
    Image.Canvas.CopyRect(destRect, Bitmap.Canvas, srcRect);
    // Save the file
    DateTime := Now;
    FolderPath := FAppDir + '../ScreenShots/' + FormatDateTime('YYYY-MM-DD', DateTime) + '/';
    ForceDirectories(FolderPath);
    Image.SaveToFile(FolderPath + FormatDateTime('HHNN-SSZZZ', DateTime) + '.jpg');
    WriteChatMessage('System', Format('%s "./../ScreenShots/%s/%s.jpg"',
    [lbScreenShotMsg, FormatDateTime('YYYY-MM-DD', DateTime), FormatDateTime('HHNN-SSZZZ', DateTime)]));
  finally
    if ReleaseDC(ScreenWND, ScreenDC)<>1 then
      MessageDlg(lbDlgFreeDcErrCaption, lbDlgFreeDcErr, mtError, [mbOK], 0);
    Bitmap.Free;
    Image.Free;
  end else
    MessageDlg(lbDlgGetDcErrCaption, lbDlgGetDcErr, mtError, [mbOK], 0);
  // Return back to the focus window (if necessary)
  if Refocus then begin
    oglGameWindowMouseEnter(Sender);
    oglGameWindow.Paint;
  end;

  //DateTime := Now;
  //FolderPath := FAppDir + 'ScreenShots/' + FormatDateTime('YYYY-MM-DD', DateTime) + '/';
  //ForceDirectories(FolderPath);
  //MessageDlg('Ошибка', Format('Handle: %d File: "%s"', [ScreenWND, FolderPath + FormatDateTime('HHNN-SSZZZ', DateTime) + '.bmp']), mtError, [mbOK], 0);

  //ssrSaveHandle(ScreenWND, PChar(FolderPath + FormatDateTime('HHNN-SSZZZ', DateTime) + '.bmp'));
  //oglGameWindowResize(Sender);
end;

procedure TfrmMain.edChatKeyPress(Sender: TObject; var Key: char);
begin
  if Key = #13 then
  begin
    Key := #0;
    if edChat.Text <> '' then
    begin
      dmNetwork.Send(TChatMessagePacket.Create(edChat.Text));
      edChat.Text := '';
    end;
  end;
end;

procedure TfrmMain.edFilterEditingDone(Sender: TObject);
var
  chrtemp: char;
  strtemp: string;
  inttemp: Integer;
begin
  strtemp := edFilter.Text;
  if (Length(strtemp) > 2) and (strtemp[1] = '0') and ((strtemp[2] = 'x') or (strtemp[2] = 'X'))
    then begin Delete(strtemp, 1, 1); strtemp[1] := '$'; end;
  if (TryStrToInt(strtemp, inttemp)) then begin
    edSearchID.Text := strtemp;
    chrtemp := #13;
    Self.edSearchIDKeyPress(nil, chrtemp);
    edSearchID.Text := '';
    edFilter.Text := '';
    Exit;
  end;

  BuildTileList;
end;

procedure TfrmMain.FormActivate(Sender: TObject);
begin
  Logger.EnterMethod([lcLandscape, lcDebug], 'TfrmMain.FormActivate(Sender: TObject)');
  if oglGameWindow.MouseEntered then
    oglGameWindowMouseEnter(Sender);
  Logger.ExitMethod([lcLandscape, lcDebug], 'TfrmMain.FormActivate(Sender: TObject)');
end;

procedure TfrmMain.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  frmDrawSettings.rbTileList.Checked := True;
  frmDrawSettings.rbRandom.Checked := False;
  dmNetwork.CheckClose(Self);
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
var
  i: Integer;
begin
  CurrentTile := nil;
  SelectedTile := nil;

  SaveLocations;
  SaveRandomPresets;

  FreeAndNil(FTextureManager);
  FreeAndNil(FScreenBuffer);
  FreeAndNil(FOverlayUI);
  FreeAndNil(FLightManager);
  FreeAndNil(FVLayerImage);
  FreeAndNil(FVLayerMaterial);
  for i:=1 to FVLightSrcImageCount do
      FreeAndNil(FVLightSrcImage[i]);
  if FVLightSrcMaterial <> nil then begin
      for i := 0 to FVLightSrcImageCount - 1 do
          FreeAndNil(FVLightSrcMaterial[i]);
      freemem(FVLightSrcMaterial);
      FVLightSrcMaterial := nil;
  end;
  FreeAndNil(FVirtualTiles);
  for i:=1 to FUndoListLength do
    FreeAndNil(FUndoListArray[i]);
  FreeAndNil(FGLFont);
  FreeAndNil(FRandomPresetsDoc);
  FreeAndNil(FAccessChangedListeners);
  FreeAndNil(FSelectionListeners);

  FreeAndNil(FTilesSelectionUndoRedoManager);
  FreeAndNil(FGroupsSelectionUndoRedoManager);
  FreeAndNil(FTilesSelectionUndoRedoCommandGroup);
  FreeAndNil(FGroupsSelectionUndoRedoCommandGroup);

  RegisterPacketHandler($0C, nil);
end;

procedure TfrmMain.edSearchIDExit(Sender: TObject);
begin
  edSearchID.Visible := False;
  edSearchID.Text := '';
end;

procedure TfrmMain.edSearchIDKeyPress(Sender: TObject; var Key: char);
var
  enteredText: String;
  tileID: Integer;
  tileType: Char;
  item: PVirtualItem;
  tileInfo: PTileInfo;
begin
  if Key = #13 then
  begin
    Key := #0;
    enteredText := edSearchID.Text;
    tileType := #0;
    if Length(enteredText) > 1 then
      tileType := enteredText[Length(enteredText)];

    if not (tileType in ['S', 'T']) then
    begin
      if cbTerrain.Checked then
        tileType := 'T'
      else
        tileType := 'S';
    end else
      Delete(enteredText, Length(enteredText), 1);
    
    tileID := 0;
    if not TryStrToInt(enteredText, tileID) then
    begin
      MessageDlg(lbDlgSearchIdErrCaption, lbDlgSearchIdErr, mtError, [mbOK], 0);
      vdtTiles.SetFocus;
      Exit;
    end;
    
    if tileType = 'S' then
      Inc(tileID, $4000);
      
    item := vdtTiles.GetFirst;
    while item <> nil do
    begin
      tileInfo := vdtTiles.GetNodeData(item);
      if tileInfo^.ID = tileID then
      begin
        vdtTiles.ClearSelection;
        vdtTiles.Selected[item] := True;
        vdtTiles.FocusedNode := item;
        Break;
      end;
      item := vdtTiles.GetNext(item);
    end;
    
    if item = nil then
    begin
      MessageDlg(lbDlgNotFoundErrCaption, lbDlgNotFoundErr, mtError, [mbOK], 0);
      vdtTiles.SetFocus;
      Exit;
    end;
    edSearchID.Visible := False;
  end else if Key = #27 then
  begin
    edSearchID.Visible := False;
    Key := #0;
  end else if not (Key in ['$', '0'..'9', 'a'..'f', 'A'..'F', 's', 'S', 't', 'T', #8])
    then Key := #0;
end;

procedure TfrmMain.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var UndoRedoManager: TUndoRedoManager;
begin
       if Sender=tvGroups then UndoRedoManager:=FGroupsSelectionUndoRedoManager
  else if Sender=vdtTiles then UndoRedoManager:=FTilesSelectionUndoRedoManager;

  if Button = mbExtra1 then UndoRedoManager.Undo;
  if Button = mbExtra2 then UndoRedoManager.Redo;
end;

procedure TfrmMain.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var UndoRedoManager: TUndoRedoManager;
begin
       if Sender=tvGroups then UndoRedoManager:=FGroupsSelectionUndoRedoManager
  else if Sender=vdtTiles then UndoRedoManager:=FTilesSelectionUndoRedoManager;

  if Key = VK_OEM_PLUS   then UndoRedoManager.Redo;
  if Key = VK_OEM_MINUS  then UndoRedoManager.Undo;
  if Key = VK_SPACE      then if frmFilter.Visible then begin
                           frmFilter.Locked := True;
                           frmFilter.Hide;
                           frmFilter.Locked := False;
                         end else frmFilter.Show;
end;

procedure TfrmMain.FormShow(Sender: TObject);
begin
  //XMLPropStorage1.Restore;
  XMLPropStorage1RestoreProperties(nil);
  mnuAutoHideGroupListClick(nil);
  mnuAutoHideRandomListClick(nil);
end;

procedure TfrmMain.FormWindowStateChange(Sender: TObject);
//var
//  ws: TWindowState;
//  h: HWND;
begin
  // �диотизм тот еще, но иначе приложение не коректно сворачивается
  // Logger.Send([lcClient, lcDebug], 'Main Window State Chancged');
  if frmMain.WindowState = wsMinimized then begin
    if frmRadarMap.Visible          then frmRadarMap.WindowState          := wsMinimized;
    //if frmRegionControl.Visible     then frmRegionControl.WindowState     := wsMinimized;
    //if frmLargeScaleCommand.Visible then frmLargeScaleCommand.WindowState := wsMinimized;
    //if frmAccountControl.Visible    then frmAccountControl.WindowState    := wsMinimized;
    //if frmAbout.Visible             then frmAbout.WindowState             := wsMinimized;
    Application.Minimize;
  end; // else Application.Restore;

  {h:=frmMain.Handle;
  if frmMain.WindowState = wsMinimized then begin
    ShowWindow(h,SW_HIDE);
    SetWindowLong(h, GWL_EXSTYLE, GetWindowLong(h, GWL_EXSTYLE) and not WS_EX_OVERLAPPEDWINDOW or WS_EX_STATICEDGE);
    ShowWindow(h,SW_SHOWMINIMIZED);
  end else begin
    //ShowInTaskBar := stDefault;
    ShowWindow(h,SW_HIDE);
    SetWindowLong(h, GWL_EXSTYLE, GetWindowLong(h, GWL_EXSTYLE) and not WS_EX_STATICEDGE or WS_EX_OVERLAPPEDWINDOW);
    //ShowInTaskBar := stDefault;
    ShowWindow(h,SW_RESTORE);
  end;}

end;

procedure TfrmMain.lblChatHeaderCaptionClick(Sender: TObject);
begin
  if pnlChat.Visible then
  begin
    pnlChat.Visible := False;
    spChat.Visible := False;
    pnlChatHeader.AnchorSide[akBottom].Control := pcLeft;
    pnlChatHeader.AnchorSide[akBottom].Side := asrBottom;
  end else
  begin
    spChat.Visible := True;
    pnlChat.Visible := True;
    spChat.Top := pnlChat.Top - spChat.Height;
    pnlChatHeader.AnchorSide[akBottom].Control := spChat;
    pnlChatHeader.AnchorSide[akBottom].Side := asrTop;
    
    lblChatHeaderCaption.Font.Bold := False;
    lblChatHeaderCaption.Font.Italic := False;
    lblChatHeaderCaption.Font.Color := clWindowText;
    
    edChat.SetFocus;
  end;
end;

procedure TfrmMain.lblChatHeaderCaptionMouseEnter(Sender: TObject);
begin
  lblChatHeaderCaption.Font.Underline := True;
end;

procedure TfrmMain.lblChatHeaderCaptionMouseLeave(Sender: TObject);
begin
  lblChatHeaderCaption.Font.Underline := False;
end;

procedure TfrmMain.mnuAboutClick(Sender: TObject);
begin
  frmAbout.ShowModal;
end;

procedure TfrmMain.mnuAccountControlClick(Sender: TObject);
begin
  frmAccountControl.Show;
  frmAccountControl.BringToFront;
end;

procedure TfrmMain.mnuTileListViewClick(Sender: TObject);
var
  ViewMode: Word;
  MainTile: Boolean;
  AddHeight: Boolean;

  function UpdateProps(VTList: TVirtualList; ViewMode: Word; AddHeight: Boolean; Update: Boolean): Boolean;
  var
    ColumIdx: Word;
  begin
    Result := False;
    if ((not Update) and (Word(VTList.Tag) = ViewMode))
      then Exit;

    for ColumIdx := 0 to 11 do begin
      VTList.Header.Columns[ColumIdx].Tag := PtrInt(-1);
      VTList.Header.Columns[ColumIdx].Options := VTList.Header.Columns[ColumIdx].Options  - [coVisible];
    end;

    case ViewMode of
      4: ColumIdx := 44;
      3: ColumIdx := 80;
      2: ColumIdx := 96;
      1: ColumIdx := 44;
    end;
    if (AddHeight)
       then inc(ColumIdx, 14);
    VTList.DefaultNodeHeight  := ColumIdx;
    VTList.Tag  := PtrInt(ViewMode);
    if (ViewMode = 1) then begin
      VTList.Header.Options  := VTList.Header.Options  + [hoVisible];
      VTList.TreeOptions.SelectionOptions  := VTList.TreeOptions.SelectionOptions  + [toFullRowSelect];
      VTList.TreeOptions.MiscOptions  := VTList.TreeOptions.MiscOptions  + [toFullRowDrag];
      VTList.Header.Columns[3].Options  := VTList.Header.Columns[3].Options  - [coVisible];
      VTList.Header.Columns[13].Options  := VTList.Header.Columns[13].Options  - [coVisible];
    end else begin
      VTList.Header.Options  := VTList.Header.Options  - [hoVisible];
      VTList.TreeOptions.SelectionOptions  := VTList.TreeOptions.SelectionOptions  - [toFullRowSelect];
      VTList.TreeOptions.MiscOptions  := VTList.TreeOptions.MiscOptions  - [toFullRowDrag];
      VTList.Header.Columns[3].Options  := VTList.Header.Columns[3].Options  + [coVisible];
      VTList.Header.Columns[13].Options  := VTList.Header.Columns[13].Options  + [coVisible];
    end;

    case ViewMode of
      4: begin ColumIdx :=  4; ViewMode :=  7; end;
      3: begin ColumIdx :=  8; ViewMode := 10; end;
      2: begin ColumIdx := 11; ViewMode := 12; end;
      1: begin ColumIdx :=  0; ViewMode :=  2; end;
    end;
    for ColumIdx := ColumIdx to ViewMode do begin
      if (ViewMode <> 2)
        then VTList.Header.Columns[ColumIdx].Tag := PtrInt(Word(VTList.Tag) + ColumIdx - ViewMode - 1)
        else VTList.Header.Columns[ColumIdx].Tag := PtrInt(0);
      VTList.Header.Columns[ColumIdx].Options  := VTList.Header.Columns[ColumIdx].Options  + [coVisible];
    end;

    VTList.BeginUpdate;
    VTList.UpdateTileColumn(Word(VTList.Tag), Update);
    VTList.EndUpdate;
    Result := True;
  end;

begin
  if (Sender <> nil) then begin
    mnuTileListDrawInfo.Enabled := (Sender <> mnuTileListTable);
    mnuMiscTileListDrawInfo.Enabled := (Sender <> mnuMiscTileListTable);
  end;
  MainTile := (Sender = mnuTileListTable) or (Sender = mnuTileListSmall)
           or (Sender = mnuTileListMidle) or (Sender = mnuTileListLarge);
  if ((Sender = mnuTileListTable) or (Sender = mnuMiscTileListTable)) then ViewMode := 1 else
  if ((Sender = mnuTileListSmall) or (Sender = mnuMiscTileListSmall)) then ViewMode := 4 else
  if ((Sender = mnuTileListMidle) or (Sender = mnuMiscTileListMidle)) then ViewMode := 3 else
  if ((Sender = mnuTileListLarge) or (Sender = mnuMiscTileListLarge)) then ViewMode := 2;

  if ((MainTile) or (Sender = nil)) then begin
    if ((Sender = nil) and (mnuTileListTable.Checked)) then ViewMode := 1 else
    if ((Sender = nil) and (mnuTileListSmall.Checked)) then ViewMode := 4 else
    if ((Sender = nil) and (mnuTileListMidle.Checked)) then ViewMode := 3 else
    if ((Sender = nil) and (mnuTileListLarge.Checked)) then ViewMode := 2;
    AddHeight := mnuTileListDrawInfo.Enabled and mnuTileListDrawInfo.Checked;
    UpdateProps(vdtTiles, ViewMode, AddHeight, Sender = nil);
  end;
  if ((not MainTile) or (Sender = nil)) then begin
    if ((Sender = nil) and (mnuMiscTileListTable.Checked)) then ViewMode := 1 else
    if ((Sender = nil) and (mnuMiscTileListSmall.Checked)) then ViewMode := 4 else
    if ((Sender = nil) and (mnuMiscTileListMidle.Checked)) then ViewMode := 3 else
    if ((Sender = nil) and (mnuMiscTileListLarge.Checked)) then ViewMode := 2;
    AddHeight := mnuMiscTileListDrawInfo.Enabled and mnuMiscTileListDrawInfo.Checked;
    UpdateProps(vdlRandom, ViewMode, AddHeight, Sender = nil);
    UpdateProps(frmFilter.vdtFilter, ViewMode, AddHeight, Sender = nil);
    UpdateProps(frmLargeScaleCommand.vdlTerrainTiles, ViewMode, AddHeight, Sender = nil);
    UpdateProps(frmLargeScaleCommand.vdlInsertStaticsTiles, ViewMode, AddHeight, Sender = nil);
    UpdateProps(frmLargeScaleCommand.vdlDeleteStaticsTiles, ViewMode, AddHeight, Sender = nil);
  end;

//     vdtTiles.Clear;
//     BuildTileList;
end;

procedure TfrmMain.mnuTileListDrawClick(Sender: TObject);
var
  dheight: Integer;

  procedure UpdateHeight(TVList: TVirtualList; height: integer);
  var
    TVTree: TLazVirtualDrawTree;
    node: PVirtualNode;
  begin
    TVTree := TLazVirtualDrawTree(TVList);
    TVList.BeginUpdate;
    TVList.DefaultNodeHeight := TVList.DefaultNodeHeight + height;
    node := TVTree.GetFirst();
    while (node <> nil) do begin
      node^.NodeHeight := node^.NodeHeight + height;
      node := TVTree.GetNext(node);
    end;
    TVList.EndUpdate;
  end;

begin
  if (Sender = mnuTileListDrawInfo) then begin
    if (mnuTileListDrawInfo.Checked)
      then UpdateHeight(vdtTiles, +14)
      else UpdateHeight(vdtTiles, -14);

  end else if (Sender = mnuMiscTileListDrawInfo) then begin
    if (mnuMiscTileListDrawInfo.Checked)
      then dheight := +14
      else dheight := -14;
    UpdateHeight(vdlRandom, dheight);
    UpdateHeight(frmFilter.vdtFilter, dheight);
    UpdateHeight(frmLargeScaleCommand.vdlTerrainTiles, dheight);
    UpdateHeight(frmLargeScaleCommand.vdlInsertStaticsTiles, dheight);
    UpdateHeight(frmLargeScaleCommand.vdlDeleteStaticsTiles, dheight);
  end;
  // Код является частью отрисовки OnDrawNode => ее надо принудительно вызвать
  vdtTiles.Repaint;
  vdlRandom.Repaint;
  frmFilter.vdtFilter.Repaint;
end;

procedure TfrmMain.mnuAutoHideGroupListClick(Sender: TObject);
begin
  if mnuAutoHideGroupList.Checked then
  begin
    spGroupList.Enabled := (not cbTerrain.Checked) and (not cbStatics.Checked);
    tvGroups.Visible := spGroupList.Enabled;
    if not spGroupList.Enabled
      then mnuAutoHideGroupList.Tag := spGroupList.Top;
      //else mnuAutoHideGroupList.Tag := 0;
  end else
  begin
    if mnuAutoHideGroupList.Tag > 0
      then spGroupList.Top := mnuAutoHideGroupList.Tag;
    mnuAutoHideGroupList.Tag := 0;
    spGroupList.Enabled := True;
    tvGroups.Visible := True;
  end;

  if spGroupList.Enabled then
  begin
    spGroupList.Cursor := crVSplit;
    //if spGroupList.Tag > 0 then
    //  spGroupList.Height := spGroupList.Tag;
    spGroupList.Height := 5;
  end else
  begin
    spGroupList.Cursor := crDefault;
    //spGroupList.Tag := spGroupList.Height;
    spGroupList.Height := 1;
  end;

  spGroupListMoved(Sender);
end;

procedure TfrmMain.mnuAutoHideRandomListClick(Sender: TObject);
begin
  if mnuAutoHideRandomList.Checked then
  begin
    if (not frmDrawSettings.rbRandom.Checked) and (Sender <> nil)
      then mnuAutoHideRandomList.Tag := spTileList.Top;
      //else mnuAutoHideRandomList.Tag := 0;
    spTileList.Enabled := frmDrawSettings.rbRandom.Checked;
    //gbRandom.Visible := frmDrawSettings.rbRandom.Checked;
    cbRandomPreset.Visible := frmDrawSettings.rbRandom.Checked;
    btnRandomPresetSave.Visible := frmDrawSettings.rbRandom.Checked;
    btnRandomPresetDelete.Visible := frmDrawSettings.rbRandom.Checked;
    vdlRandom.Visible := frmDrawSettings.rbRandom.Checked;
  end else
  begin
    if mnuAutoHideRandomList.Tag > 0
      then spTileList.Top := mnuAutoHideRandomList.Tag;
    mnuAutoHideRandomList.Tag := 0;
    spTileList.Enabled := True;
    //gbRandom.Visible := True;
    cbRandomPreset.Visible := True;
    btnRandomPresetSave.Visible := True;
    btnRandomPresetDelete.Visible := True;
    vdlRandom.Visible := True;
  end;

  if spTileList.Enabled then
  begin
    spTileList.Cursor := crVSplit;
    //if spTileList.Tag > 0 then
    //  spTileList.Height := spTileList.Tag;
    spTileList.Height := 5;
  end else
  begin
    spTileList.Cursor := crDefault;
    //spTileList.Tag := spTileList.Height;
    spTileList.Height := 1;
  end;

  spTileListMoved(Sender);
end;

procedure TfrmMain.spGroupListMoved(Sender: TObject);
var
  anchor: integer;
begin
  if mnuAutoHideGroupList.Checked then
    if (spGroupList.Enabled) and (mnuAutoHideGroupList.Tag > 0) then
      begin
        spGroupList.Top := mnuAutoHideGroupList.Tag;
        mnuAutoHideGroupList.Tag := 0;
      end
    else if not spGroupList.Enabled then
      begin
        spGroupList.Top := tvGroups.Top + 4;
        Exit;
      end;

  if spGroupList.Enabled then
    if spGroupList.Top <= tvGroups.Top then
      spGroupList.Top := tvGroups.Top
    else begin
      anchor := spTileList.Top - spGroupList.Height - vdtTiles.Constraints.MinHeight;
      if spGroupList.Top > anchor then spGroupList.Top := (anchor - 1);
    end;
end;

procedure TfrmMain.spTileListMoved(Sender: TObject);
var
  anchor: integer;
begin
  if mnuAutoHideRandomList.Checked then
    if (spTileList.Enabled) and (mnuAutoHideRandomList.Tag > 0) then
      begin
        spTileList.Top := mnuAutoHideRandomList.Tag;
        mnuAutoHideRandomList.Tag := 0;
      end
    else if not spTileList.Enabled then
      begin
        spTileList.Top := gbRandom.Top + gbRandom.Height - spTileList.Tag + 3;
        Exit;
      end;

  if spTileList.Enabled then
    if spTileList.Top + spTileList.Height >= gbRandom.Top + gbRandom.Height then
      spTileList.Top := gbRandom.Top + gbRandom.Height - spTileList.Height
    else begin
      anchor := spGroupList.Top + spGroupList.Height + vdtTiles.Constraints.MinHeight;
      if spTileList.Top < anchor then spTileList.Top := (anchor + 1);
    end;
end;

procedure TfrmMain.tbFilterMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  If Button = mbRight then
    tbFilter.Tag := PtrInt(GetTickCount);
end;

procedure TfrmMain.tbFilterMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if acFilter.Checked and (not frmFilter.Visible) and ((GetTickCount - DWORD(tbFilter.Tag)) < 1000) then begin
    tbFilter.Tag := PtrInt(False);
    frmFilter.Show;
  end;
end;

procedure TfrmMain.pcLeftResize(Sender: TObject);
begin
  spGroupListMoved(Sender);
  spTileListMoved(Sender);
end;

procedure TfrmMain.mnuDisconnectClick(Sender: TObject);
begin
  dmNetwork.Disconnect;
end;

procedure TfrmMain.mnuDocsClick(Sender: TObject);
begin
  //TODO ShellExecute(Handle, 'open', PChar('http://dev.uoquint.ru/projects/centred/wiki'), nil, nil, 1 {SW_SHOWNORMAL});
end;

procedure TfrmMain.mnuRusComClick(Sender: TObject);
begin
  //TODO ShellExecute(Handle, 'open', PChar('http://forum.uokit.com/index.php?showforum=207'), nil, nil, 1 {SW_SHOWNORMAL});
end;

procedure TfrmMain.mnuEng2ComClick(Sender: TObject);
begin
  //TODO ShellExecute(Handle, 'open', PChar('http://craftuo.com/threads/centred.888'), nil, nil, 1 {SW_SHOWNORMAL});
end;

procedure TfrmMain.mnuEngComClick(Sender: TObject);
begin
  //TODO ShellExecute(Handle, 'open', PChar('http://board.uoquint.ru/'), nil, nil, 1 {SW_SHOWNORMAL});
end;

procedure TfrmMain.oglGameWindowPaint(Sender: TObject);
begin
  if mnuWhiteBackground.Checked then
    glClearColor(1, 1, 1, 1)
  else
    glClearColor(0, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT);

  InitRender;
  InitSize;

  glDisable(GL_DEPTH_TEST);
  Render;

  oglGameWindow.SwapBuffers;
end;

procedure TfrmMain.oglGameWindowResize(Sender: TObject);
begin
  InvalidateScreenBuffer;
end;

procedure TfrmMain.pbRadarMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var posx, posy: Integer;
begin
  posx := self.X+4 * Trunc((Y+X - (pbRadar.Height+pbRadar.Width)div 2)*cos(45));
  posy := self.Y+4 * Trunc((Y-X - (pbRadar.Height-pbRadar.Width)div 2)*cos(45));
  //if (posx < 0) then posx := 0 else if (posx >= 8*Landscape.Width)  then posx := 8*Landscape.Width-1;
  //if (posy < 0) then posy := 0 else if (posy >= 8*Landscape.Height) then posy := 8*Landscape.Height-1;
  if (posx>=0)and(posy>=0)and(posx<8*Landscape.Width)and(posy<8*Landscape.Height)
    then SetPos(posx, posy);
end;

procedure TfrmMain.pbRadarPaint(Sender: TObject);
var
  posX, posY, scrW, scrH: Integer;
  zoom: Single;
  image: TSingleImage;
begin
  posX := (X div 8) - (pbRadar.Width  div 4);
  posY := (Y div 8) - (pbRadar.Height div 4);

  // NOTE: Не очень-то хорошо каждый раз создавать Image...
  image := TSingleImage.CreateFromParams(pbRadar.Width+84, pbRadar.Height+84, ifA8R8G8B8);
  StretchRect(frmRadarMap.Radar.ImageDataPointer^, posX, posY, pbRadar.Width div 2, pbRadar.Height div 2,
           image.ImageDataPointer^, 0, 0, image.Width, image.Height, rfBicubic);
  RotateImage(image.ImageDataPointer^, -45.0);

  DisplayImage(pbRadar.Canvas, (pbRadar.Width-image.Width) div 2, (pbRadar.Height-image.Height) div 2, image);
  image.Free;

  posX := pbRadar.Width  div 2;
  posY := pbRadar.Width  div 2;
  if tbZoom.Down then zoom := tbZoom.Tag / 1000.0 else zoom := 1.0;
  scrW := Trunc(0.25 * oglGameWindow.Width / 44.0 / zoom);
  scrH := Trunc(0.25 * oglGameWindow.Height/ 44.0 / zoom);

  pbRadar.Canvas.Pen.Color := clBlack;
  pbRadar.Canvas.Pen.Style := psSolid;
  pbRadar.Canvas.Line(posX-scrW, posY-scrH, posX+scrW, posY-scrH);
  pbRadar.Canvas.Line(posX+scrW, posY-scrH, posX+scrW, posY+scrH);
  pbRadar.Canvas.Line(posX+scrW, posY+scrH, posX-scrW, posY+scrH);
  pbRadar.Canvas.Line(posX-scrW, posY+scrH, posX-scrW, posY-scrH);

  pbRadar.Canvas.Pen.Color := clSilver;
  pbRadar.Canvas.Pen.Style := psSolid;
  pbRadar.Canvas.Line(posX-scrW-1, posY-scrH-1, posX+scrW+1, posY-scrH-1);
  pbRadar.Canvas.Line(posX+scrW+1, posY-scrH-1, posX+scrW+1, posY+scrH+1);
  pbRadar.Canvas.Line(posX+scrW+1, posY+scrH+1, posX-scrW-1, posY+scrH+1);
  pbRadar.Canvas.Line(posX-scrW-1, posY+scrH+1, posX-scrW-1, posY-scrH-1);
end;

procedure TfrmMain.pmGrabTileInfoPopup(Sender: TObject);
var
  isStatic: Boolean;
begin
  isStatic := CurrentTile is TStaticItem;
  mnuGrabHue.Enabled := isStatic;
  mnuGrabFilterTileID.Enabled := isStatic;
  mnuGrabFilterHue.Enabled := isStatic;
end;

procedure TfrmMain.tbRadarMapClick(Sender: TObject);
begin
  frmRadarMap.Show;
  frmRadarMap.BringToFront;
end;

procedure TfrmMain.tmGrabTileInfoTimer(Sender: TObject);
begin
  tmGrabTileInfo.Enabled := False;
  if CurrentTile <> nil then
    pmGrabTileInfo.PopUp(Mouse.CursorPos.X, Mouse.CursorPos.Y);
    
  SelectedTile := nil;
end;

procedure TfrmMain.tmMovementTimer(Sender: TObject);
begin
  case FOverlayUI.ActiveArrow of
    0: MoveBy(-1,  0);
    1: MoveBy(-1, -1);
    2: MoveBy( 0, -1);
    3: MoveBy(+1, -1);
    4: MoveBy(+1,  0);
    5: MoveBy(+1, +1);
    6: MoveBy( 0, +1);
    7: MoveBy(-1, +1);
  end;
end;

procedure TfrmMain.tmSettingsCloseTimer(Sender: TObject);
begin
  tmSettingsClose.Enabled := False;
  tbTerrain.Down := acTerrain.Checked;
  tbStatics.Down := acStatics.Checked;
  tbNoDraw.Down  := acNoDraw.Checked;
  tbFlat.Down    := acFlat.Checked;
end;

procedure TfrmMain.FormMouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer);
begin
  if Sender is TWinControl then
    if not (TWinControl(Sender)).Focused
      then (TWinControl(Sender)).SetFocus;
end;

procedure TfrmMain.tvGroupsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var
  nodeData : PGroupNode;
begin
  nodeData := tvGroups.GetNodeData(Node);
  CellText := nodeData^.Name;
end;

procedure TfrmMain.tvGroupsDrawText(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  const CellText: String; const CellRect: TRect; var DefaultDraw: Boolean);
var
  nodeData : PGroupNode;
  TextWidth: Integer;
begin
  nodeData := tvGroups.GetNodeData(Node);

  TargetCanvas.Font.Color := nodeData^.Color;
  TargetCanvas.Font.Bold  := nodeData^.Bold;
  TargetCanvas.Font.Italic:= nodeData^.Ital;
  TargetCanvas.TextOut(CellRect.Left, CellRect.Top+2, CellText);

  TextWidth := TargetCanvas.TextWidth(CellText);
  TargetCanvas.Font.Color := TColor($00BBBBBB);
  TargetCanvas.Font.Bold  := False;
  TargetCanvas.Font.Italic:= False;
  TargetCanvas.TextOut(CellRect.Left + TextWidth + 6, CellRect.Top + 2,
                       Format('(%u)', [nodeData^.Items]));

  DefaultDraw := False;
end;

procedure TfrmMain.tmSelectNodeTimer(Sender: TObject);
begin
  tmSelectNode.Enabled := False;
  if (FGroupsSelectionUndoRedoCommandGroup.UndoRedoCommands.Count > 0) then begin
    if not FGroupsSelectionUndoRedoManager.Enabled then begin
      tmSelectNode.Enabled := True;
      Exit;
    end;
    FGroupsSelectionUndoRedoManager.ExecCommand(FGroupsSelectionUndoRedoCommandGroup);
    FGroupsSelectionUndoRedoCommandGroup := TUndoRedoCommandGroup.Create;
  end;
  //if (FTilesSelectionUndoRedoCommandGroup.UndoRedoCommands.Count > 0) then
    begin
    if not FTilesSelectionUndoRedoManager.Enabled then begin
      tmSelectNode.Enabled := True;
      Exit;
    end;
    FTilesSelectionUndoRedoManager.ExecCommand(FTilesSelectionUndoRedoCommandGroup);
    FTilesSelectionUndoRedoCommandGroup := TUndoRedoCommandGroup.Create;
  end;
end;

procedure TfrmMain.vdtTilesChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
begin
  if (Node <> nil) and (FTilesSelectionUndoRedoManager.Enabled) then begin
    FTilesSelectionUndoRedoCommandGroup.Add(
      TUndoRedoSelectVirtualNodeCommand.Create(
        FTilesSelectionUndoRedoManager, Sender, Node)
    );
    if not FTilesSelectionUndoRedoManager.Enabled
      then vdtTiles.FocusedNode := PVirtualItem(Node);
    if not tmSelectNode.Enabled
      then tmSelectNode.Enabled := True
      else tmSelectNode.Interval:= 250;
  end;
end;

procedure TfrmMain.tvGroupsChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  item: PVirtualNode;
begin
  if (Node <> nil) and (FGroupsSelectionUndoRedoManager.Enabled) then begin
    FGroupsSelectionUndoRedoCommandGroup.Add(
      TUndoRedoSelectVirtualNodeCommand.Create(
        FGroupsSelectionUndoRedoManager, Sender, Node)
    );
    if not tmSelectNode.Enabled
      then tmSelectNode.Enabled := True
      else tmSelectNode.Interval:= 250;

    if ((cbStatics.Checked or cbTerrain.Checked) and Sender.Selected[Node]) then begin
      cbStatics.Checked := False;
      cbTerrain.Checked := False;
      Exit;
    end;
  end;

  BuildTileList;
end;

procedure TfrmMain.tvGroupFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  groupData: PGroupNode;
begin
  groupData := Sender.GetNodeData(Node);
  groupData^.Name := EmptyStr;
  FreeMem(groupData^.GLink);
  FreeMem(groupData^.GTile);
  FreeMem(groupData^.Brush);
  FreeMem(groupData^.Entry);
end;

procedure TfrmMain.DropedownMenusClose(Sender: TObject);
begin
  tmSettingsClose.Enabled := True;
end;

procedure TfrmMain.vdtRandomClick(Sender: TObject);
var
  item: PVirtualItem;
  node: PVirtualItem;
  treeNode: PVirtualNode;
  tileInfo: PTileInfo;
  selectedID: Integer;

  function TileInNode(Node: PVirtualNode; TileID: LongWord) : Boolean;
  var
    nodeData: ^TGroupNode;
    i: Integer;
  begin
    Result := False;
    nodeData := tvGroups.GetNodeData(Node);
    for i := 0 to nodeData^.Count - 1 do
    begin
      if nodeData^.GTile[i].ID = TileID then
      begin
        Result := True;
        break;
      end;
    end;
  end;

begin
  if vdlRandom.SelectedCount = 1 then
  begin
    node := vdlRandom.GetFirstSelected;
    if node <> nil then
    begin
      tileInfo := vdlRandom.GetNodeData(node);
      selectedID := tileInfo^.ID;

      // Выбираем группы
      if (not cbStatics.Checked) and (not cbTerrain.Checked) then
      begin
        treeNode := tvGroups.GetFirst();
        while treeNode <> nil do
        begin
          if TileInNode(treeNode, selectedID) then
          begin
            tvGroups.Selected[treeNode] := True;
            tvGroups.FocusedNode := treeNode;
            if toMultiSelect in tvGroups.TreeOptions.SelectionOptions
              then Break;
          end;
            treeNode := tvGroups.GetNext(treeNode);
        end;
      end;

      // Выбираем тайл
      item := vdtTiles.GetFirst;
      while item <> nil do
      begin
        tileInfo := vdtTiles.GetNodeData(item);
        if tileInfo^.ID = selectedID then
        begin
          vdtTiles.ClearSelection;
          vdtTiles.Selected[item] := True;
          vdtTiles.FocusedNode := item;
          item := nil;
        end else
          item := vdtTiles.GetNext(item);
      end;

    end;
  end;
end;

procedure TfrmMain.vdtRandomDragDrop(Sender: TBaseVirtualTree; Source: TObject;
  DataObject: IDataObject; Formats: TFormatArray; Shift: TShiftState;
  Pt: TPoint; var Effect: Integer; Mode: TDropMode);
begin
  if Source = vdtTiles then
    btnAddRandomClick(Sender);
end;

procedure TfrmMain.vdtRandomDragOver(Sender: TBaseVirtualTree; Source: TObject;
  Shift: TShiftState; State: TDragState; Pt: TPoint; Mode: TDropMode;
  var Effect: Integer; var Accept: Boolean);
begin
  if source = vdtTiles then Accept := True;
end;

procedure TfrmMain.vdtRandomLoadNode(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Stream: TStream);
var
  tileInfo: PTileInfo;
begin
  tileInfo := Sender.GetNodeData(Node);
  Stream.Read(tileInfo^.ID, SizeOf(tileInfo^.ID));
end;

procedure TfrmMain.vdtRandomSaveNode(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Stream: TStream);
var
  tileInfo: PTileInfo;
begin
  tileInfo := Sender.GetNodeData(Node);
  Stream.Write(tileInfo^.ID, SizeOf(tileInfo^.ID));
end;

procedure TfrmMain.vdtRandomUpdating(Sender: TBaseVirtualTree;
  State: TVTUpdateState);
begin
  if acDraw.Checked then
    ProcessToolState;
end;

procedure TfrmMain.vdtTilesClick(Sender: TObject);
begin
  if acDraw.Checked then
    ProcessToolState;
end;

procedure TfrmMain.vdtTilesDragAllowed(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean);
begin
  //vdtTiles.auto
  Allowed := True;
end;

procedure TfrmMain.vdtTilesDrawHint(Sender: TBaseVirtualTree;
  HintCanvas: TCanvas; Node: PVirtualNode; const R: TRect; Column: TColumnIndex
  );
var
  m_name : string;
begin
  vdtTiles.UpdateHintCanvas(HintCanvas);

  HintCanvas.Font.Assign(Sender.Font);
  HintCanvas.Font.Style := [fsBold];
  m_name := CP1251ToUTF8(FTileHint.Name);
  DrawText(HintCanvas.Handle, PChar(m_name), Length(m_name), FTileHint.NameRect, 0);
  HintCanvas.Font.Style := [];
  DrawText(HintCanvas.Handle, PChar(FTileHint.Obj), Length(FTileHint.Obj), FTileHint.ObjRect, 0);
  HintCanvas.Font.Style := [fsItalic];
  DrawText(HintCanvas.Handle, PChar(FTileHint.Flags), Length(FTileHint.Flags),
    FTileHint.FlagsRect, DT_WORDBREAK);
end;

procedure TfrmMain.vdtTilesDrawNode(Sender: TBaseVirtualTree; const PaintInfo: TVTPaintInfo);
var
  tileInfo: PTileInfo;
  textStyle: TTextStyle;
  artEntry: TArt;
  tileData: TTileData;
  id: LongWord;
  rect, CellRect:TRect;
  image: TSingleImage;
  colorOld, colorNew: Word;
  DrawInfo: Boolean;
  oldcolor: DWORD;
  newcolor: DWORD;
  srcCanvas, dstCanvas: TFastARGB32Canvas;
  tmpImage : TSingleImage;
  bit:  Integer;

  procedure DisplayNodeImage(main: Boolean; const Canvas: TCanvas; const CellRect: TRect; Image: TBaseImage; ForceStretch: Boolean = False);
  var
    Resised: Float;
    DstSize: TSize;
    DstRect: TRect;
    //SrcRect: TRect;
  begin
    if (main and mnuTileListStretch.Checked) or (not main and mnuMiscTileListStretch.Checked) or (ForceStretch) then begin

      if ForceStretch or (DstSize.cy <> 44) then begin
        DstSize.cx := CellRect.Right - CellRect.Left;
        DstSize.cy := CellRect.Bottom - CellRect.Top;
        if ForceStretch then begin
          if (DstSize.cx <= 44) then id := 2
          else if (DstSize.cx <= 80) then id := 3
          else id := 4;
          DstSize.cx := DstSize.cx - id;
          DstSize.cy := DstSize.cy - id;
        end;
        Resised := Min(Min(Float(DstSize.cx)/Float(Image.Width), Float(DstSize.cy)/Float(Image.Height)), 1.0);
        DstSize.cx := Trunc(Resised * Float(Image.Width));
        DstSize.cy := Trunc(Resised * Float(Image.Height));
        DstRect.Left := Trunc((CellRect.Left + CellRect.Right - DstSize.cx) / 2);
        DstRect.Right := DstRect.Left + DstSize.cx;
        if not ForceStretch
          then DstRect.Bottom := CellRect.Bottom
          else DstRect.Bottom := (CellRect.Bottom - CellRect.Top + DstSize.cy) div 2;
        DstRect.Top := DstRect.Bottom - DstSize.cy
      end else begin
        DstRect := CellRect;
      end;
      DisplayImage(Canvas, DstRect, Image);
    end else
    if (main and mnuTileListClip.Checked) or (not main and mnuMiscTileListClip.Checked) then begin
      //DstSize.cx := CellRect.Right - CellRect.Left;
      //DstSize.cy := CellRect.Bottom - CellRect.Top;
      //DstRect := CellRect;
      //SrcRect.Top := 0;
      //if (DstSize.cy <= Image.Height) then begin
      //  SrcRect.Bottom := DstSize.cy;
      //end else begin
      //  SrcRect.Bottom := Image.Height;
      //  DstRect.Bottom := DstRect.Top + Image.Height;
      //end;
      //if (DstSize.cx <= Image.Width) then begin
      //  SrcRect.Left := (Image.Width - DstSize.cx) div 2;
      //  SrcRect.Right:= SrcRect.Left + DstSize.cx;
      //end else begin
      //  SrcRect.Left := 0;
      //  SrcRect.Right:= Image.Width;
      //  DstRect.Left := DstRect.Left + (DstSize.cx - Image.Width) div 2;
      //  DstRect.Right:= DstRect.Left + Image.Width;
      //end;
      //Logger.Send([lcClient, lcDebug], 'TfrmMain.vdtTilesDrawNode [%d,%d,%d,%d](%d,%d) - [%d,%d,%d,%d](%d,%d) I(%d,%d) C[%d,%d,%d,%d](%d,%d)',
      //[SrcRect.Top, SrcRect.Left, SrcRect.Bottom, SrcRect.Right, SrcRect.Right - SrcRect.Left, SrcRect.Bottom - SrcRect.Top,
      // DstRect.Top, DstRect.Left, DstRect.Bottom, DstRect.Right, DstRect.Right - DstRect.Left, DstRect.Bottom - DstRect.Top,
      // Image.Width, Image.Height,
      // CellRect.Top, CellRect.Left, CellRect.Bottom, CellRect.Right, CellRect.Right - CellRect.Left, CellRect.Bottom - CellRect.Top]);

      //DisplayImage(Canvas, DstRect, Image, SrcRect);
      DisplayImage(Canvas, Trunc((CellRect.Left + CellRect.Right - Image.Width) / 2), CellRect.Top, Image);
      Canvas.FillRect(CellRect.Left, CellRect.Bottom, CellRect.Right, CellRect.Bottom + 14);
    end else
    if (main and mnuTileListCentre.Checked) or (not main and mnuMiscTileListCentre.Checked) then begin
      // TODO: ....
    end;
  end;

begin
  if (not (Sender is TVirtualList))
    then tileInfo := Sender.GetNodeData(PaintInfo.Node)
    else tileInfo := vdtTiles.GetNodeData(PVirtualItem(PaintInfo.Node));

  //if (sender <> vdtTiles) then begin
  //Logger.Send([lcClient, lcDebug], 'TfrmMain.vdtTilesDrawNode %.8x at cell [%d,%d]', [tileInfo^.ID, PaintInfo.Column, PaintInfo.Node^.Index]);
  //Exit;
  //end;
//  Logger.Send([lcClient, lcDebug], 'TfrmMain.vdtTilesDrawNode %d', [tileInfo^.ID]);

  if (tileInfo^.ID = $FFFFFFFF) then exit;

  textStyle := PaintInfo.Canvas.TextStyle;
  textStyle.Alignment := taCenter;
  textStyle.Layout := tlCenter;
  textStyle.Wordbreak := True;
  PaintInfo.Canvas.Font.Color := Sender.Font.Color;
  if (Sender = vdtTiles) then
    PaintInfo.Canvas.Font.Color := TColor($808080);

  //Logger.Send([lcClient, lcDebug], 'vdtTilesDrawNode %.4x', [tileInfo^.ID]);

  case PaintInfo.Column of
    0:
      begin
        id := tileInfo^.ID;
        if id > $2F000000 then
          PaintInfo.Canvas.TextRect(PaintInfo.CellRect, 0, 0, Format('BL%.3d', [id - $2F000000]), textStyle)
        else if id > $1F000000 then
          PaintInfo.Canvas.TextRect(PaintInfo.CellRect, 0, 0, Format('ET%.3d', [id - $1F000000]), textStyle)
        else begin
        if id > $00003FFF then Dec(id, $00004000);
        //PaintInfo.Canvas.TextRect(PaintInfo.CellRect, 0, 0, '4x', textStyle);
        PaintInfo.Canvas.TextRect(PaintInfo.CellRect, 0, 0, Format('$%.4x', [id]), textStyle);
        end;
      end;
    2:
      begin
        textStyle.Alignment := taLeftJustify;
        if (tileInfo^.ID > $2F000000) then begin          // Кисти
          PaintInfo.Canvas.TextRect(PaintInfo.CellRect, PaintInfo.CellRect.Left + 4,
            PaintInfo.CellRect.Top, PGroupBrush(tileInfo^.ptr)^.Name, textStyle);
        end else if (tileInfo^.ID > $1F000000) then begin // Объекты
          PaintInfo.Canvas.TextRect(PaintInfo.CellRect, PaintInfo.CellRect.Left + 4,
            PaintInfo.CellRect.Top, PGroupEntry(tileInfo^.ptr)^.Name, textStyle);
        end else begin  // Тайлы
          tileData := TTileData(ResMan.Tiledata.Block[tileInfo^.ID]);
          PaintInfo.Canvas.TextRect(PaintInfo.CellRect, PaintInfo.CellRect.Left + 4,
            PaintInfo.CellRect.Top, CP1251ToUTF8(Trim(tileData.TileName)),
            textStyle);
          tileData.Free;
        end;
      end;
    3:  begin Exit; end;
    13: begin Exit; end;
    // 1: - 3: 4: 5: 6: - 7: 8: 9: - 10: 11:
    else
      begin
        CellRect := PaintInfo.CellRect;
        if (PaintInfo.Column = 1)
          then DrawInfo := False
        else if (sender = vdtTiles)
          then DrawInfo := mnuTileListDrawInfo.Enabled and mnuTileListDrawInfo.Checked
          else DrawInfo := mnuMiscTileListDrawInfo.Enabled and mnuMiscTileListDrawInfo.Checked;
        if DrawInfo then begin
          CellRect.Bottom := CellRect.Bottom - 14;
        end;

        if (tileInfo^.ID > $2F000000) then begin         // Кисти
          //image := TSingleImage.CreateFromImage(PGroupBrush(tileInfo^.ptr)^.Image);
          colorOld := $0000; colorNew := RGB2ARGB(PaintInfo.Canvas.Pixels[CellRect.Left,CellRect.Top]);
          PGroupBrush(tileInfo^.ptr)^.Image.ReplaceColor(0,0,PGroupBrush(tileInfo^.ptr)^.Image.Width,PGroupBrush(tileInfo^.ptr)^.Image.Height,
          @colorOld, @colorNew);
          image := TSingleImage.CreateFromImage(PGroupBrush(tileInfo^.ptr)^.Image);

          image.Format := ifDefault;
          DisplayNodeImage((Sender = vdtTiles), PaintInfo.Canvas, CellRect, image, True);
          //DisplayImage(PaintInfo.Canvas, CellRect, image);
          image.Free;
        end else if (tileInfo^.ID > $1F000000) then begin// Объекты
          //colorOld := $0000; colorNew := RGB2ARGB($00FFFFFF);
          //PGroupEntry(tileInfo^.ptr)^.Image.ReplaceColor(0,0,PGroupEntry(tileInfo^.ptr)^.Image.Width,PGroupEntry(tileInfo^.ptr)^.Image.Height,
          //@colorOld, @colorNew);
          image := TSingleImage.CreateFromImage(PGroupEntry(tileInfo^.ptr)^.Image);
          //oldcolor := PDWORD(Image.Bits)^;
          //newcolor := $00FF0000;//PDWORD(Image.)^;
          //image.ReplaceColor(0,0,Image.Width,Image.Height,@oldcolor,@newcolor);
          //image.Format := ifDefault;
          DisplayNodeImage((Sender = vdtTiles), PaintInfo.Canvas, CellRect, image, True);
          image.Free;
        end else if ResMan.Art.Exists(tileInfo^.ID) then // Тайлы
        begin
          artEntry := ResMan.Art.GetArt(tileInfo^.ID,
            RGB2ARGB(PaintInfo.Canvas.Pixels[CellRect.Left, CellRect.Top]), nil, False);
          DisplayNodeImage((Sender = vdtTiles), PaintInfo.Canvas, CellRect, artEntry.Graphic);
          artEntry.Free;
          if tileInfo^.ID > $3FFF then
            if (FLightSourceTiles[tileInfo^.ID - $4000].image > 0) then // Источники света
            begin
              bit := FLightSourceTiles[tileInfo^.ID - $4000].image;
              //rect.Right:=CellRect.Right;
              //rect.Bottom:=CellRect.Bottom;
              //rect.Left:=CellRect.Right - 24;
              //rect.Top:=CellRect.Bottom - 24;
              //DisplayImage(PaintInfo.Canvas, rect, FVLightSrcImage);

              //DisplayNodeImage((Sender = vdtTiles), PaintInfo.Canvas, CellRect, FVLightSrcImage, True);

              rect.Left := 0;
              rect.Top  := 0;
              rect.Right:= FVLightSrcImage[bit].Width;
              rect.Bottom:=FVLightSrcImage[bit].Height;
              tmpImage := TSingleImage.CreateFromParams(rect.Right, rect.Bottom, ifA8R8G8B8);
              srcCanvas := TFastARGB32Canvas.CreateForImage(FVLightSrcImage[bit]);
              dstCanvas := TFastARGB32Canvas.CreateForImage(tmpImage);

              dstCanvas.FillColor32 := PaintInfo.Canvas.Pixels[CellRect.Left+4,CellRect.Top+4];
              dstCanvas.FillRect(rect);
              srcCanvas.DrawAlpha(rect, dstCanvas, 0,0);
              srcCanvas.Free;
              dstCanvas.Free;
              DisplayNodeImage((Sender = vdtTiles), PaintInfo.Canvas, CellRect, tmpImage, True);
              tmpImage.Free;
            end;
        end;

        if DrawInfo then begin // Подписи тайлов
          CellRect.Bottom := PaintInfo.CellRect.Bottom;
          rect := PaintInfo.CellRect;
          rect.Top := rect.Bottom - 14;;
          id := tileInfo^.ID;
          if id > $2F000000 then
            PaintInfo.Canvas.TextRect(rect, 0, 0, Format('BL%.3d', [id - $2F000000]), textStyle)
          else if id > $1F000000 then
            PaintInfo.Canvas.TextRect(rect, 0, 0, Format('ET%.3d', [id - $1F000000]), textStyle)
          else begin
            if id > $00003FFF then Dec(id, $00004000);
            //PaintInfo.Canvas.TextRect(rect, 0, 0, '4x', textStyle);
            PaintInfo.Canvas.TextRect(rect, 0, 0, Format('0x%.4x', [id]), textStyle);
          end;
        end;
      end;

  end;
end;

procedure TfrmMain.vdtTilesEnter(Sender: TObject);
begin
  if acFilter.Checked and mnuAutoShowFilterWindow.Checked and (not frmFilter.Visible) and (not frmFilter.Locked) then
  begin
    frmFilter.Locked := True;
    frmFilter.Show;
    frmMain.SetFocus;
    frmFilter.Locked := False;
  end;
end;

procedure TfrmMain.vdtTilesGetHintSize(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; var R: TRect);
var
  tileInfo: PTileInfo;
  tileData: TTiledata;
  prefix, flags: string;
  id: LongWord;
  cmHint: TCMHintShow;

  procedure UpdateFlags(AFlag: TTileDataFlag; AName: string);
  begin
    if AFlag in tileData.Flags then
    begin
      if flags <> '' then
        flags := flags + ', ' + AName
      else
        flags := AName;
    end;
  end;

begin
  case Word(Sender.Tag) of
    1: id := 0;
    2: id := min(max(0, Column - 10), 2);
    3: id := min(max(0, Column -  7), 3);
    4: id := min(max(0, Column -  3), 4);
  end;
  //Logger.Send([lcClient, lcDebug], 'vdtTilesGetHintSize', Column);
  tileInfo := (TVirtualList(Sender)).GetNodeData(vdtTiles.GetItemAt(Node, Column));// @PTileInfo(Sender.GetNodeData(Node))[id];
  flags := '';
//  if (FTileHint.Column = id) then Exit;
//  FTileHint.Column := id;

  if (tileInfo^.ID > $2F000000) then begin
    FTileHint.Name := UTF8ToCP1251(PGroupBrush(tileInfo^.ptr)^.Name);
    FTileHint.Obj := Format('Brush ID: 0x%.4x (%.5d)', [tileInfo^.ID-$2F000000, tileInfo^.ID-$2F000000]);
    //flags := 'Brush';
  end else if (tileInfo^.ID > $1F000000) then begin
    FTileHint.Name := UTF8ToCP1251(PGroupEntry(tileInfo^.ptr)^.Name);
    FTileHint.Obj := Format('Entry ID: 0x%.4x (%.5d)', [tileInfo^.ID-$1F000000, tileInfo^.ID-$1F000000]);
    //flags := 'Entry';
  end else
  begin
    tileData := ResMan.Tiledata.TileData[tileInfo^.ID];
    if tileInfo^.ID < $4000 then begin
      if TLandTiledata(tileData).TextureID > 0 then
        flags := 'Stretchable';
      FTileHint.Obj := Format('LandT ID: 0x%.4x (%.5d)', [tileInfo^.ID, tileInfo^.ID]);
    end else begin
      FTileHint.Obj := Format('ItemT ID: 0x%.4x (%.5d)', [tileInfo^.ID-$4000, tileInfo^.ID-$4000]);
    end;


    if tdfArticleA in tileData.Flags then
      prefix := 'a '
    else if tdfArticleAn in tileData.Flags then
      prefix := 'an '
    else
      prefix := '';

    FTileHint.Name := AnsiProperCase(Format('%s%s', [prefix, tileData.TileName]), [' ']);

    UpdateFlags(tdfBackground, 'Background');
    UpdateFlags(tdfWeapon, 'Weapon');
    UpdateFlags(tdfTransparent, 'Transparent');
    UpdateFlags(tdfTranslucent, 'Translucent');
    UpdateFlags(tdfWall, 'Wall');
    UpdateFlags(tdfDamaging, 'Damaging');
    UpdateFlags(tdfImpassable, 'Impassable');
    UpdateFlags(tdfWet, 'Wet');
    UpdateFlags(tdfSurface, 'Surface');
    UpdateFlags(tdfBridge, 'Bridge');
    UpdateFlags(tdfGeneric, 'Generic');
    UpdateFlags(tdfWindow, 'Window');
    UpdateFlags(tdfNoShoot, 'NoShoot');
    UpdateFlags(tdfInternal, 'Internal');
    UpdateFlags(tdfFoliage, 'Foliage');
    UpdateFlags(tdfPartialHue, 'PartialHue');
    UpdateFlags(tdfMap, 'Map');
    UpdateFlags(tdfContainer, 'Container');
    UpdateFlags(tdfWearable, 'Wearable');
    UpdateFlags(tdfLightSource, 'Lightsource');
    UpdateFlags(tdfAnimation, 'Animation');
    UpdateFlags(tdfNoDiagonal, 'NoDiagonal');  //HoverOver
    UpdateFlags(tdfArmor, 'Armor');
    UpdateFlags(tdfRoof, 'Roof');
    UpdateFlags(tdfDoor, 'Door');
    UpdateFlags(tdfStairBack, 'StairBack');
    UpdateFlags(tdfStairRight, 'StairRight');
  end;

  FTileHint.NameRect.Left := 5;
  FTileHint.NameRect.Top := 5;
  Sender.Canvas.Font.Style := [fsBold];
  DrawText(Sender.Canvas.Handle, PChar(CP1251ToUTF8(FTileHint.Name)), Length(CP1251ToUTF8(FTileHint.Name)),
    FTileHint.NameRect, DT_CALCRECT);
  FTileHint.ObjRect.Left := 5;
  FTileHint.ObjRect.Top := FTileHint.NameRect.Bottom + 6;
  Sender.Canvas.Font.Style := [];
  DrawText(Sender.Canvas.Handle, PChar(FTileHint.Obj), Length(FTileHint.Obj), FTileHint.ObjRect, DT_CALCRECT);
  FTileHint.Flags := Format('Flags = [%s]', [flags]);
  FTileHint.FlagsRect.Left := 5;
  FTileHint.FlagsRect.Top := FTileHint.ObjRect.Bottom + 2;
  FTileHint.FlagsRect.Right := 145;
  Sender.Canvas.Font.Style := [fsItalic];
  DrawText(Sender.Canvas.Handle, PChar(FTileHint.Flags), Length(FTileHint.Flags),
    FTileHint.FlagsRect, DT_CALCRECT or DT_WORDBREAK);

  R := Rect(0, 0, Max(Max(FTileHint.NameRect.Right, FTileHint.ObjRect.Right), FTileHint.FlagsRect.Right) + 5,
    FTileHint.FlagsRect.Bottom + 5);
end;

procedure TfrmMain.vdtTilesKeyPress(Sender: TObject; var Key: char);
begin
  if Key in ['$', '0'..'9'] then
  begin
    edSearchID.Text := Key;
    edSearchID.Visible := True;
    edSearchID.SetFocus;
    edSearchID.SelStart := 1;
    Key := #0;
  end;
end;

procedure TfrmMain.vdtTilesScroll(Sender: TBaseVirtualTree; DeltaX,
  DeltaY: Integer);
begin
  if Sender.CanFocus and Sender.MouseEntered then
    Sender.SetFocus;
end;

procedure TfrmMain.vstChatClick(Sender: TObject);
begin
  edChat.SetFocus;
end;

procedure TfrmMain.vstChatFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  chatInfo: PChatInfo;
begin
  chatInfo := Sender.GetNodeData(Node);
  chatInfo^.Sender := '';
  chatInfo^.Msg := '';
end;

procedure TfrmMain.vstChatGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var
  chatInfo: PChatInfo;
begin
  chatInfo := Sender.GetNodeData(Node);
  case Column of
    0: CellText := TimeToStr(chatInfo^.Time);
    1: CellText := chatInfo^.Sender;
    2: CellText := chatInfo^.Msg;
  end;
end;

procedure TfrmMain.vstChatPaintText(Sender: TBaseVirtualTree;
  const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  TextType: TVSTTextType);
var
  chatInfo: PChatInfo;
begin
  chatInfo := Sender.GetNodeData(Node);
  if chatInfo^.Sender = 'System' then
  begin
    if Column = 1 then
      TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsItalic, fsBold]
    else
      TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsItalic];
  end;
end;

procedure TfrmMain.vstClientsGetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: String);
var
  clientInfo: PClientInfo;
begin
  clientInfo := Sender.GetNodeData(Node);
  case Column of
    1: CellText := clientInfo^.Name;//Format('%d, %d', [locationInfo^.X, locationInfo^.Y]);
    2: CellText := FormatDateTime('mm\dd - hh:mm:ss', clientInfo^.LogonDateTime);
  end;
end;

procedure TfrmMain.vstClientsGetImageIndex(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
  var Ghosted: Boolean; var ImageIndex: Integer);
var
  clientInfo: PClientInfo;
begin
  clientInfo := Sender.GetNodeData(Node);
  case Column of
    0: frmAccountControl.GetAccountImageIndex(clientInfo^.AccessLevel, ImageIndex);
  end;
end;

procedure TfrmMain.vstLocationsDblClick(Sender: TObject);
var
  node: PVirtualNode;
  locationInfo: PLocationInfo;
begin
  node := vstLocations.GetFirstSelected;
  if node <> nil then
  begin
    locationInfo := vstLocations.GetNodeData(node);
    SetPos(locationInfo^.X, locationInfo^.Y);
  end;
end;

procedure TfrmMain.vstLocationsFreeNode(Sender: TBaseVirtualTree;
  Node: PVirtualNode);
var
  locationInfo: PLocationInfo;
begin
  locationInfo := Sender.GetNodeData(Node);
  locationInfo^.Name := EmptyStr;
end;

procedure TfrmMain.vstLocationsGetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: String);
var
  locationInfo: PLocationInfo;
begin
  locationInfo := Sender.GetNodeData(Node);
  case Column of
    0: CellText := Format('%d, %d', [locationInfo^.X, locationInfo^.Y]);
    1: CellText := locationInfo^.Name;
  end;
end;

procedure TfrmMain.vstLocationsLoadNode(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Stream: TStream);
var
  locationInfo: PLocationInfo;
  stringLength: Integer;
  s: string;
begin
  locationInfo := Sender.GetNodeData(Node);
  Stream.Read(locationInfo^.X, SizeOf(Word));
  Stream.Read(locationInfo^.Y, SizeOf(Word));
  stringLength := 0;
  Stream.Read(stringLength, SizeOf(Integer));
  SetLength(s, stringLength);
  Stream.Read(s[1], stringLength);
  locationInfo^.Name := s;
end;

procedure TfrmMain.vstLocationsNewText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; const NewText: String);
var
  locationInfo: PLocationInfo;
begin
  if Column = 1 then
  begin
    locationInfo := Sender.GetNodeData(Node);
    locationInfo^.Name := NewText;
  end;
end;

procedure TfrmMain.vstLocationsSaveNode(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Stream: TStream);
var
  locationInfo: PLocationInfo;
  stringLength: Integer;
begin
  locationInfo := Sender.GetNodeData(Node);
  Stream.Write(locationInfo^.X, SizeOf(Word));
  Stream.Write(locationInfo^.Y, SizeOf(Word));
  stringLength := Length(locationInfo^.Name);
  Stream.Write(stringLength, SizeOf(Integer));
  Stream.Write(locationInfo^.Name[1], stringLength);
end;

procedure TfrmMain.XMLPropStorage1RestoreProperties(Sender: TObject);
var ws: Integer;
begin
  Logger.EnterMethod([lcLandscape, lcDebug], 'TfrmMain.XMLPropStorage1RestoreProperties(Sender: TObject);');

  if Sender = nil then begin
    FTextureManager.UseAnims := mnuShowAnimations.Checked;
    tbTerrain.Down := acTerrain.Checked;
    frmLightLevel.tbLightlevel.Position:=acLightLevel.Tag;
  end;

  if mnuWindowedMode.Checked then begin
    BorderStyle := bsSizeable;
    FormStyle   := fsNormal;
    {if ((Tag<>0) and (mnuWindowedMode.Tag<>0)) then begin
      ws := (Tag and $3);
      if ws = 1 then WindowState := wsNormal     else
      if ws = 2 then WindowState := wsMinimized  else
      if ws = 3 then WindowState := wsMaximized;
      Left  := ((mnuWindowedMode.Tag  shr 15) and $7FFF);
      Top   := (mnuWindowedMode.Tag and $7FFF);
      Width := ((Tag  shr 16) and $3FFF);
      Height:= ((Tag  shr  2) and $3FFF);
      Tag := 0; mnuWindowedMode.Tag := 0;
    end; }
  end else begin
    {if ((Tag = 0) and (mnuWindowedMode.Tag = 0)) then begin
      if WindowState = wsNormal    then ws := 1  else
      if WindowState = wsMinimized then ws := 2  else
      if WindowState = wsMaximized then ws := 3  else ws := 0;
      Tag := ((Width and $3FFF) shl 16) or ((Height and $3FFF) shl 2) or (ws and $3);
      mnuWindowedMode.Tag := ((Left and $7FFF) shl 15) or ((Top and $7FFF) shl 0);
    end; }
    BorderStyle := bsNone;
    FormStyle   := fsStayOnTop;
    WindowState := wsMaximized;
  end;

  //mnuTileListViewClick(nil);
  //mnuAutoHideGroupListClick(nil);
  //mnuAutoHideRandomListClick(nil);
  Logger.ExitMethod([lcLandscape, lcDebug], 'TfrmMain.XMLPropStorage1RestoreProperties(Sender: TObject);');
end;

procedure TfrmMain.XMLPropStorage1SavingProperties(Sender: TObject);
var ws: Integer;
begin
  Logger.EnterMethod([lcLandscape, lcDebug], 'TfrmMain.XMLPropStorage1SavingProperties(Sender: TObject);');
  //Hide;
  if mnuWindowedMode.Checked then begin
    if ((Tag<>0) and (mnuWindowedMode.Tag<>0)) then begin
      ws := (Tag and $3);
      if ws = 1 then WindowState := wsNormal     else
      if ws = 2 then WindowState := wsMinimized  else
      if ws = 3 then WindowState := wsMaximized;
      Left  := ((mnuWindowedMode.Tag  shr 15) and $7FFF);
      Top   := (mnuWindowedMode.Tag and $7FFF);
      Width := ((Tag  shr 16) and $3FFF);
      Height:= ((Tag  shr  2) and $3FFF);
      Tag := 0; mnuWindowedMode.Tag := 0;
    end;
  end else begin
    if ((Tag = 0) and (mnuWindowedMode.Tag = 0)) then begin
      if WindowState = wsNormal    then ws := 1  else
      if WindowState = wsMinimized then ws := 2  else
      if WindowState = wsMaximized then ws := 3  else ws := 0;
      Tag := ((Width and $3FFF) shl 16) or ((Height and $3FFF) shl 2) or (ws and $3);
      mnuWindowedMode.Tag := ((Left and $7FFF) shl 15) or ((Top and $7FFF) shl 0);
    end;
  end;
  //Show;
  if spTileList.Top > spTileList.Parent.Height then
    spTileList.Top := spTileList.Parent.Height - 200;
  Logger.ExitMethod([lcLandscape, lcDebug], 'TfrmMain.XMLPropStorage1SavingProperties(Sender: TObject);');
end;

procedure TfrmMain.SetX(const AValue: Integer);
begin
  SetPos(AValue, FY);
end;

procedure TfrmMain.SetY(const AValue: Integer);
begin
  SetPos(FX, AValue);
end;

procedure TfrmMain.SetPos(AX, AY: Word);
begin
  if InRange(AX, 0, FLandscape.CellWidth - 1) and InRange(AY, 0,
    FLandscape.CellHeight - 1) then
  begin
    FX := AX;
    edX.Value := FX;
    FY := AY;
    edY.Value := FY;
    dmNetwork.Send(TUpdateClientPosPacket.Create(AX, AY));
    InvalidateScreenBuffer;
    if frmRadarMap <> nil then frmRadarMap.Repaint;
    pbRadar.Repaint;
  end;
end;

procedure TfrmMain.SwitchToSelection;
begin
  acSelect.Checked := True;
  BringToFront;
end;

procedure TfrmMain.RegisterAccessChangedListener(
  AListener: TAccessChangedListener);
begin
  if FAccessChangedListeners.IndexOf(AListener) < 0 then
    FAccessChangedListeners.Add(AListener);
end;

procedure TfrmMain.RegisterSelectionListener(AListener: TSelectionListener);
begin
  if FSelectionListeners.IndexOf(AListener) < 0 then
    FSelectionListeners.Add(AListener);
end;

procedure TfrmMain.UnregisterAccessChangedListener(
  AListener: TAccessChangedListener);
begin
  FAccessChangedListeners.Remove(AListener);
end;

procedure TfrmMain.UnregisterSelectionListener(AListener: TSelectionListener);
begin
  FSelectionListeners.Remove(AListener);
end;

procedure TfrmMain.SetCurrentTile(const AValue: TWorldItem);
var
  sRect: TRect;
  w, h: Integer;
begin
  //Logger.EnterMethod([lcClient, lcDebug], 'SetCurrentTile');
  if AValue = FCurrentTile then
  begin
    //Logger.ExitMethod([lcClient, lcDebug], 'SetCurrentTile');
    Exit;
  end;
  //Logger.Send([lcClient, lcDebug], 'Value', AValue);

  if FCurrentTile <> nil then
    FCurrentTile.OnDestroy.UnregisterEvent(@OnTileRemoved);
  FCurrentTile := AValue;

  if FCurrentTile = nil then
  begin
    lblTileInfoIDLabel.Caption:= '';
    lblTileInfoIDValue.Caption:= '';
    lblTileInfoXLabel.Caption := '';
    lblTileInfoXValue.Caption := '';
    lblTileInfoYLabel.Caption := '';
    lblTileInfoYValue.Caption := '';
    lblTileInfoZLabel.Caption := '';
    lblTileInfoZValue.Caption := '';
    lblTileInfoHueLabel.Caption := '';
    lblTileInfoHueValue.Caption := '';
    lblTileInfoOLabel.Visible := False;
    lblTileInfoWLabel.Visible := False;
    lblTileInfoWValue.Visible := False;
    lblTileInfoHLabel.Visible := False;
    lblTileInfoHValue.Visible := False;
    lblTileInfoCLabel.Visible := False;
  end else
  begin
    FCurrentTile.OnDestroy.RegisterEvent(@OnTileRemoved);
    lblTileInfoIDValue.Caption := Format('0x%.4x', [FCurrentTile.TileID]);
    lblTileInfoIDValue.Font.Bold := True;
    lblTileInfoXLabel.Caption := lbBottomCursorPosX;
    lblTileInfoYLabel.Caption := lbBottomCursorPosY;
    lblTileInfoZLabel.Caption := lbBottomCursorPosZ;
    lblTileInfoXValue.Caption  := Format('%d', [FCurrentTile.X]);
    lblTileInfoYValue.Caption  := Format('%d', [FCurrentTile.Y]);
    lblTileInfoZValue.Caption  := Format('%d', [FCurrentTile.Z]);
    if FCurrentTile is TVirtualTile then begin
      lblTileInfoIDLabel.Caption := lbBottomCursorVLayer1;
      lblTileInfoIDValue.Font.Bold := False;
      lblTileInfoIDValue.Caption := lbBottomCursorVLayer2;
      lblTileInfoHueLabel.Caption:= '';
      lblTileInfoHueValue.Caption:= '';
    end
    else if FCurrentTile is TMapCell then begin
      lblTileInfoIDLabel.Caption := lbBottomCursorLandID;
      lblTileInfoHueLabel.Caption:= '';
      lblTileInfoHueValue.Caption:= '';
    end
    else if FCurrentTile is TStaticItem then begin
      lblTileInfoIDLabel.Caption := lbBottomCursorItemID;
      lblTileInfoHueLabel.Caption:= lbBottomCursorItemHue;
      lblTileInfoHueValue.Caption:= Format('%.3x', [TStaticItem(FCurrentTile).Hue]);
    end;
    sRect := GetSelectedRect;
    w := sRect.Right - sRect.Left + 1;
    h := sRect.Bottom - sRect.Top + 1;
    if ((w > 1) or (h > 1)) then begin
      lblTileInfoOLabel.Visible := True;
      lblTileInfoWLabel.Visible := True;
      lblTileInfoWValue.Visible := True;
      lblTileInfoHLabel.Visible := True;
      lblTileInfoHValue.Visible := True;
      lblTileInfoCLabel.Visible := True;
      lblTileInfoWValue.Caption := Format('%d', [w]);
      lblTileInfoHValue.Caption := Format('%d', [h]);
    end else begin
      lblTileInfoOLabel.Visible := False;
      lblTileInfoWLabel.Visible := False;
      lblTileInfoWValue.Visible := False;
      lblTileInfoHLabel.Visible := False;
      lblTileInfoHValue.Visible := False;
      lblTileInfoCLabel.Visible := False;
    end;
  end;

  UpdateSelection;

  //Logger.Send([lcClient, lcDebug], 'CurrentTile: %.5x (%.6d)', [CurrentTile.TileID, CurrentTile.TileID]);
  //Logger.ExitMethod([lcClient, lcDebug], 'SetCurrentTile');
end;

procedure TfrmMain.SetSelectedTile(const AValue: TWorldItem);
begin
  //Logger.EnterMethod([lcClient, lcDebug], 'SetSelectedTile');
  if AValue = FSelectedTile then
  begin
    Logger.ExitMethod([lcClient, lcDebug], 'SetSelectedTile');
    Exit;
  end;
  //Logger.Send([lcClient, lcDebug], 'Value', AValue);

  if FSelectedTile <> nil then
    FSelectedTile.OnDestroy.UnregisterEvent(@OnTileRemoved);
  FSelectedTile := AValue;
  if FSelectedTile <> nil then
    FSelectedTile.OnDestroy.RegisterEvent(@OnTileRemoved);

  UpdateSelection;
  //Logger.ExitMethod([lcClient, lcDebug], 'SetSelectedTile');
end;

procedure TfrmMain.SetNormalLights;
const
  specular: TGLArrayf4 = (2, 2, 2, 1);
  ambient: TGLArrayf4 = (1, 1, 1, 1);
begin
  glLightfv(GL_LIGHT0, GL_AMBIENT, @specular);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, @ambient);
end;

procedure TfrmMain.SetDarkLights;
const
  specularDark: TGLArrayf4 = (0.5, 0.5, 0.5, 1);
  ambientDark: TGLArrayf4 = (0.25, 0.25, 0.25, 1);
begin
  glLightfv(GL_LIGHT0, GL_AMBIENT, @specularDark);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, @ambientDark);
end;

procedure TfrmMain.InitRender;
const
  lightPosition: TGLArrayf4 = (-1, -1, 0.5, 0);
begin
  glEnable(GL_ALPHA_TEST);
  glAlphaFunc(GL_GREATER, 0.1);
  glEnable(GL_TEXTURE_2D);
  glDisable(GL_DITHER);
  glEnable(GL_BLEND); // Enable alpha blending of textures
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_NORMALIZE);

  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0, GL_POSITION, @lightPosition);
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
end;

procedure TfrmMain.InitSize;
begin
  glViewport(0, 0, oglGameWindow.Width, oglGameWindow.Height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluOrtho2D(0, oglGameWindow.Width, oglGameWindow.Height, 0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;
end;

procedure TfrmMain.LoadLocations;
var
  xmlDoc: TXMLDocument;
  location: TDOMElement;
  locationNode: PVirtualNode;
  locationInfo: PLocationInfo;
  locations: TDOMNodeList;
  i, j: Integer;
begin
  vstLocations.Clear;

  if FileExists(FLocationsFile) then
  begin
    ReadXMLFile(xmlDoc, FLocationsFile);
    if xmlDoc.DocumentElement.NodeName = 'Locations' then
    begin
      locations := xmlDoc.DocumentElement.ChildNodes;
      for i := 0 to locations.Count - 1 do
      begin
        location := TDOMElement(locations[i]);
        if location.NodeName = 'Location' then
        begin
          locationNode := vstLocations.AddChild(nil);
          locationInfo := vstLocations.GetNodeData(locationNode);
          locationInfo^.Name := CP1251ToUTF8(location.AttribStrings['Name']);

          if TryStrToInt(location.AttribStrings['X'], j) then
            locationInfo^.X := j
          else
            locationInfo^.X := 0;

          if TryStrToInt(location.AttribStrings['Y'], j) then
            locationInfo^.Y := j
          else
            locationInfo^.Y := 0;
        end;
      end;
    end;

    xmlDoc.Free;
  end;
end;

procedure TfrmMain.LoadRandomPresets;
var
  presets: TDOMNodeList;
  i: Integer;
begin
  cbRandomPreset.Clear;

  FreeAndNil(FRandomPresetsDoc);
  if FileExists(FRandomPresetsFile) then
  begin
    ReadXMLFile(FRandomPresetsDoc, FRandomPresetsFile);
    presets := FRandomPresetsDoc.DocumentElement.ChildNodes;
    for i := 0 to presets.Count - 1 do
    begin
      if presets[i].NodeName = 'Preset' then
      begin
        cbRandomPreset.Items.AddObject(
          CP1251ToUTF8(TDOMElement(presets[i]).AttribStrings['Name']), presets[i]);
      end;
    end;
  end else
  begin
    FRandomPresetsDoc := TXMLDocument.Create;
    FRandomPresetsDoc.AppendChild(FRandomPresetsDoc.CreateElement('RandomPresets'));
  end;
end;

procedure TfrmMain.MoveBy(AOffsetX, AOffsetY: Integer); inline;
begin
  SetPos(EnsureRange(FX + AOffsetX, 0, FLandscape.CellWidth - 1),
         EnsureRange(FY + AOffsetY, 0, FLandscape.CellHeight - 1));
  UpdateCurrentTile;
end;

procedure TfrmMain.PrepareMapCell(AMapCell: TMapCell);
var
  current, north, east, west,   tile1, tile4, tile3, tile6: PBlockInfo;
  cell: TMapCell;
begin
  current := FScreenBuffer.UpdateSortOrder(AMapCell);
  if current = nil then
    Exit; //off-screen update

  PrepareScreenBlock(current);
  Exclude(FScreenBufferState, sbsIndexed);

  //Find surrounding cells
  current := nil;
  north := nil;
  east := nil;
  west := nil;
  tile1 := nil;
  tile4 := nil;
  tile3 := nil;
  tile6 := nil;
  while ((north = nil) or (east = nil) or (west = nil) or
    (tile1 = nil) or (tile3 = nil) or (tile4 = nil) or (tile6 = nil)) and
    FScreenBuffer.Iterate(current) do
  begin
    if current^.Item is TMapCell then
    begin
      cell := TMapCell(current^.Item);
      if (cell.X = AMapCell.X - 1) and (cell.Y = AMapCell.Y - 1) then
        north := current
      else if (cell.X = AMapCell.X) and (cell.Y = AMapCell.Y - 1) then
        east := current
      else if (cell.X = AMapCell.X - 1) and (cell.Y = AMapCell.Y) then
        west := current
      else if (cell.X = AMapCell.X) and (cell.Y = AMapCell.Y + 1) then
        tile1 := current
      else if (cell.X = AMapCell.X + 1) and (cell.Y = AMapCell.Y) then
        tile3 := current
      else if (cell.X = AMapCell.X - 1) and (cell.Y = AMapCell.Y + 1) then
        tile4 := current
      else if (cell.X = AMapCell.X + 1) and (cell.Y = AMapCell.Y - 1) then
        tile6 := current;
    end;
  end;

  if north <> nil then PrepareScreenBlock(north);
  if east <> nil then PrepareScreenBlock(east);
  if west <> nil then PrepareScreenBlock(west);
  if tile1 <> nil then PrepareScreenBlock(tile1);
  if tile3 <> nil then PrepareScreenBlock(tile3);
  if tile4 <> nil then PrepareScreenBlock(tile4);
  if tile6 <> nil then PrepareScreenBlock(tile6);

end;

procedure TfrmMain.InvalidateFilter;
begin
  Exclude(FScreenBufferState, sbsFiltered);
end;

procedure TfrmMain.InvalidateScreenBuffer;
begin
  Exclude(FScreenBufferState, sbsValid);
end;

procedure TfrmMain.PrepareScreenBlock(ABlockInfo: PBlockInfo);

  procedure GetLandAlt(const AX, AY: Integer; const ADefaultZ,
    ADefaultRaw: SmallInt; out Z, RawZ: SmallInt);
  var
    cell: TMapCell;
  begin
    cell := FLandscape.MapCell[AX, AY];
    if cell <> nil then
    begin
      Z := cell.Z;
      RawZ := cell.RawZ;
    end else
    begin
      Z := ADefaultZ;
      RawZ := ADefaultRaw;
    end;
  end;

var
  item: TWorldItem;
  drawX, drawY: Integer;
  z, west, south, east, tileNorth, tileWest, tileLeft, tileRight: SmallInt;
  rawZ, rawWest, rawSouth, rawEast, rawTileNorth, rawTileWest, rawTileLeft, rawTileRight: SmallInt;
  staticItem: TStaticItem;
  zoom: Single;
begin
  //add normals to map tiles and materials where possible

  item := ABlockInfo^.Item;

  GetDrawOffset(item.X , item.Y, drawX, drawY);

  if acFlat.Checked then
  begin
    z := 0;
    rawZ := 0;
  end else
  begin
    z := item.Z;
    rawZ := item.RawZ;
  end;

  if ABlockInfo^.HighRes <> nil then ABlockInfo^.HighRes.DelRef;
  if ABlockInfo^.LowRes <> nil then ABlockInfo^.LowRes.DelRef;

  ABlockInfo^.HighRes := nil;
  ABlockInfo^.CheckRealQuad := False;
  ABlockInfo^.Text.Free;

  if item is TMapCell then
  begin
    if not acFlat.Checked then
    begin
      GetLandAlt(item.X, item.Y + 1, z, rawZ, west, rawWest);
      GetLandAlt(item.X + 1, item.Y + 1, z, rawZ, south, rawSouth);
      GetLandAlt(item.X + 1, item.Y, z, rawZ, east, rawEast);

      if  (west <> z) or (south <> z) or (east <> z) then
        ABlockInfo^.HighRes := FTextureManager.GetTexMaterial(item.TileID);

      if (rawWest <> rawZ) or (rawSouth <> rawZ) or (rawEast <> rawZ) then
      begin
        ABlockInfo^.RealQuad[0][0] := drawX;
        ABlockInfo^.RealQuad[0][1] := drawY - rawZ * 4;
        ABlockInfo^.RealQuad[1][0] := drawX + 22;
        ABlockInfo^.RealQuad[1][1] := drawY + 22 - rawEast * 4;
        ABlockInfo^.RealQuad[2][0] := drawX;
        ABlockInfo^.RealQuad[2][1] := drawY + 44 - rawSouth * 4;
        ABlockInfo^.RealQuad[3][0] := drawX - 22;
        ABlockInfo^.RealQuad[3][1] := drawY + 22 - rawWest * 4;

        with ABlockInfo^ do
        begin
          with ScreenRect do
          begin
            Left := drawX - 22;
            Right := drawX + 22;
            Top := RealQuad[0][1];
            Bottom := RealQuad[0][1];

            if RealQuad[1][1] < Top then Top := RealQuad[1][1];
            if RealQuad[1][1] > Bottom then Bottom := RealQuad[1][1];

            if RealQuad[2][1] < Top then Top := RealQuad[2][1];
            if RealQuad[2][1] > Bottom then Bottom := RealQuad[2][1];

            if RealQuad[3][1] < Top then Top := RealQuad[3][1];
            if RealQuad[3][1] > Bottom then Bottom := RealQuad[3][1];
          end;
          CheckRealQuad := True;
        end;
      end;
    end else
    begin
      if mnuFlatShowHeight.Checked then
        ABlockInfo^.Text := TGLText.Create(FGLFont, IntToStr(item.Z));
    end;

    if not ABlockInfo^.CheckRealQuad then
      ABlockInfo^.ScreenRect := Bounds(Trunc(drawX - 22),
        Trunc(drawY - rawZ * 4), 44, 44);

    ABlockInfo^.LowRes := FTextureManager.GetArtMaterial(item.TileID);

    if ABlockInfo^.HighRes <> nil then
    begin
      if ABlockInfo^.Normals = nil then
        New(ABlockInfo^.Normals);
      FLandscape.GetNormals(item.X, item.Y, ABlockInfo^.Normals^);
      ABlockInfo^.DrawQuad[0][0] := drawX;
      ABlockInfo^.DrawQuad[0][1] := drawY - z * 4;
      ABlockInfo^.DrawQuad[1][0] := drawX + 22;
      ABlockInfo^.DrawQuad[1][1] := drawY + 22 - east * 4;
      ABlockInfo^.DrawQuad[2][0] := drawX;
      ABlockInfo^.DrawQuad[2][1] := drawY + 44 - south * 4;
      ABlockInfo^.DrawQuad[3][0] := drawX - 22;
      ABlockInfo^.DrawQuad[3][1] := drawY + 22 - west * 4;

      // Подготовка сетки рельефа для текстур
      if mnuShowGrid.Checked or mnuShowBlocks.Checked then
      begin
        ABlockInfo^.LineWidth[0] := 0.9;
        ABlockInfo^.LineDraw[0][0] := ABlockInfo^.DrawQuad[0];
        ABlockInfo^.LineDraw[0][1] := ABlockInfo^.DrawQuad[1];
        ABlockInfo^.LineWidth[1] := 0.9;
        ABlockInfo^.LineDraw[1][0] := ABlockInfo^.DrawQuad[0];
        ABlockInfo^.LineDraw[1][1] := ABlockInfo^.DrawQuad[3];
        ABlockInfo^.LineWidth[2] := 0.8;
        //ABlockInfo^.LineDraw[2][0] := ABlockInfo^.DrawQuad[0];
        //ABlockInfo^.LineDraw[2][1] := ABlockInfo^.DrawQuad[2];
        ABlockInfo^.LineDraw[2][0] := ABlockInfo^.DrawQuad[1];
        ABlockInfo^.LineDraw[2][1] := ABlockInfo^.DrawQuad[3];
      end;
    end else
    begin
      ABlockInfo^.DrawQuad[0][0] := drawX - 22;
      ABlockInfo^.DrawQuad[0][1] := drawY - z * 4;
      ABlockInfo^.DrawQuad[1][0] := drawX - 22 + ABlockInfo^.LowRes.Width;
      ABlockInfo^.DrawQuad[1][1] := drawY - z * 4;
      ABlockInfo^.DrawQuad[2][0] := drawX - 22 + ABlockInfo^.LowRes.Width;
      ABlockInfo^.DrawQuad[2][1] := drawY + ABlockInfo^.LowRes.Height - z * 4;
      ABlockInfo^.DrawQuad[3][0] := drawX - 22;
      ABlockInfo^.DrawQuad[3][1] := drawY + ABlockInfo^.LowRes.Height - z * 4;

      // Подготовка сетки рельефа для лендов
      if mnuShowGrid.Checked or mnuShowBlocks.Checked then
      begin
        GetLandAlt(item.X, item.Y - 1, z, rawZ, tileNorth, rawTileNorth);
        GetLandAlt(item.X - 1, item.Y, z, rawZ, tileWest, rawTileWest);
        GetLandAlt(item.X - 1, item.Y + 1, z, rawZ, tileLeft, rawTileLeft);
        GetLandAlt(item.X + 1, item.Y - 1, z, rawZ, tileRight, rawTileRight);
        if (tileNorth <> z) or (tileRight <> z)
          then ABlockInfo^.LineWidth[0] := 0.9
          else ABlockInfo^.LineWidth[0] := 0.8;
        ABlockInfo^.LineDraw[0][0][0] := drawX;
        ABlockInfo^.LineDraw[0][0][1] := ABlockInfo^.DrawQuad[0][1];
        ABlockInfo^.LineDraw[0][1][0] := ABlockInfo^.DrawQuad[1][0];
        ABlockInfo^.LineDraw[0][1][1] := ABlockInfo^.DrawQuad[1][1] + 22;
        if (tileWest <> z) or (tileLeft <> z)
          then ABlockInfo^.LineWidth[1] := 0.9
          else ABlockInfo^.LineWidth[1] := 0.8;
        ABlockInfo^.LineDraw[1][0][0] := drawX;
        ABlockInfo^.LineDraw[1][0][1] := ABlockInfo^.DrawQuad[0][1];
        ABlockInfo^.LineDraw[1][1][0] := ABlockInfo^.DrawQuad[3][0];
        ABlockInfo^.LineDraw[1][1][1] := ABlockInfo^.DrawQuad[3][1] - 22;
        ABlockInfo^.LineWidth[2] := 0.0;
      end;
    end;
  end else
  // Виртуальный пол
  if item is TVirtualTile then
  begin
    ABlockInfo^.LowRes := FVLayerMaterial;
    ABlockInfo^.LowRes.AddRef;
    ABlockInfo^.ScreenRect := Bounds(Trunc(drawX - 22), Trunc(drawY - z * 4),
      44, 44);
    ABlockInfo^.DrawQuad[0][0] := drawX - 22;
    ABlockInfo^.DrawQuad[0][1] := drawY - z * 4;
    ABlockInfo^.DrawQuad[1][0] := drawX - 22 + ABlockInfo^.LowRes.Width;
    ABlockInfo^.DrawQuad[1][1] := drawY - z * 4;
    ABlockInfo^.DrawQuad[2][0] := drawX - 22 + ABlockInfo^.LowRes.Width;
    ABlockInfo^.DrawQuad[2][1] := drawY + ABlockInfo^.LowRes.Height - z * 4;
    ABlockInfo^.DrawQuad[3][0] := drawX - 22;
    ABlockInfo^.DrawQuad[3][1] := drawY + ABlockInfo^.LowRes.Height - z * 4;

    // Подготовка сетки блоков на виртуальном полу
    if (frmVirtualLayer.cbShowBlocks.Checked) then
    begin
      if (item.Y mod 8) <> 0 then
        ABlockInfo^.LineWidth[0] := 0
      else begin
        ABlockInfo^.LineWidth[0] := 1.6;
        ABlockInfo^.LineDraw[0][0][0] := drawX;
        ABlockInfo^.LineDraw[0][0][1] := ABlockInfo^.DrawQuad[0][1];
        ABlockInfo^.LineDraw[0][1][0] := ABlockInfo^.DrawQuad[1][0];
        ABlockInfo^.LineDraw[0][1][1] := ABlockInfo^.DrawQuad[1][1] + 22;
      end;
      if (item.X mod 8) <> 0 then
        ABlockInfo^.LineWidth[1] := 0
      else begin
        ABlockInfo^.LineWidth[1] := 1.6;
        ABlockInfo^.LineDraw[1][0][0] := drawX;
        ABlockInfo^.LineDraw[1][0][1] := ABlockInfo^.DrawQuad[0][1];
        ABlockInfo^.LineDraw[1][1][0] := ABlockInfo^.DrawQuad[3][0];
        ABlockInfo^.LineDraw[1][1][1] := ABlockInfo^.DrawQuad[3][1] - 22;
      end;

    end;

  end else
  begin
    staticItem := TStaticItem(item);

    // Тайл виртуального источника света
    if acNoDraw.Checked and mnuShowLightSource.Checked and (staticItem.TileID <= FLandscape.MaxStaticID)
       and (FLightSourceTiles[staticItem.TileID].image > 0) then
    begin
      ABlockInfo^.LowRes := FVLightSrcMaterial[FLightSourceTiles[staticItem.TileID].image - 1];
      ABlockInfo^.LowRes.AddRef;
      ABlockInfo^.ScreenRect := Bounds(Trunc(drawX - 12), Trunc(drawY - z * 4), 24, 24);
      ABlockInfo^.DrawQuad[0][0] := drawX - 12;
      ABlockInfo^.DrawQuad[0][1] := drawY + 12 - z * 4;
      ABlockInfo^.DrawQuad[1][0] := drawX - 12 + ABlockInfo^.LowRes.Width;
      ABlockInfo^.DrawQuad[1][1] := drawY + 12 - z * 4;
      ABlockInfo^.DrawQuad[2][0] := drawX - 12 + ABlockInfo^.LowRes.Width;
      ABlockInfo^.DrawQuad[2][1] := drawY + 12 + ABlockInfo^.LowRes.Height - z * 4;
      ABlockInfo^.DrawQuad[3][0] := drawX - 12;
      ABlockInfo^.DrawQuad[3][1] := drawY + 12 + ABlockInfo^.LowRes.Height - z * 4;
    end else
    // Тайлы обычной статики
    begin
      ABlockInfo^.LowRes := FTextureManager.GetStaticMaterial(staticItem);
      ABlockInfo^.HueOverride := False;

      ABlockInfo^.ScreenRect := Bounds(Trunc(drawX - ABlockInfo^.LowRes.RealWidth / 2),
        Trunc(drawY + 44 - ABlockInfo^.LowRes.RealHeight - z * 4),
        ABlockInfo^.LowRes.RealWidth,
        ABlockInfo^.LowRes.RealHeight);

      ABlockInfo^.Translucent := tdfTranslucent in
        ResMan.Tiledata.StaticTiles[staticItem.TileID].Flags;

      south := ABlockInfo^.LowRes.RealHeight;
      east := ABlockInfo^.LowRes.RealWidth div 2;

      ABlockInfo^.DrawQuad[0][0] := drawX - east;
      ABlockInfo^.DrawQuad[0][1] := drawY + 44 - south - z * 4;
      ABlockInfo^.DrawQuad[1][0] := drawX - east + ABlockInfo^.LowRes.Width;
      ABlockInfo^.DrawQuad[1][1] := drawY + 44 - south - z * 4;
      ABlockInfo^.DrawQuad[2][0] := drawX - east + ABlockInfo^.LowRes.Width;
      ABlockInfo^.DrawQuad[2][1] := drawY + 44 - south + ABlockInfo^.LowRes.Height - z * 4;
      ABlockInfo^.DrawQuad[3][0] := drawX - east;
      ABlockInfo^.DrawQuad[3][1] := drawY + 44 - south + ABlockInfo^.LowRes.Height - z * 4;
    end;
  end;


  if tbZoom.Down then begin
    zoom := tbZoom.Tag / 1000.0;

    ABlockInfo^.ScreenRect.Left   := Trunc(zoom * (ABlockInfo^.ScreenRect.Left   -drawX)+drawX);
    ABlockInfo^.ScreenRect.Top    := Trunc(zoom * (ABlockInfo^.ScreenRect.Top    -drawY)+drawY);;
    ABlockInfo^.ScreenRect.Right  := Trunc(zoom * (ABlockInfo^.ScreenRect.Right  -drawX)+drawX);
    ABlockInfo^.ScreenRect.Bottom := Trunc(zoom * (ABlockInfo^.ScreenRect.Bottom -drawY)+drawY);

    //south:= Trunc(zoom*(ABlockInfo^.DrawQuad[2][1] - ABlockInfo^.DrawQuad[0][1]));
    //east := Trunc(zoom*(ABlockInfo^.DrawQuad[2][0] - ABlockInfo^.DrawQuad[0][0]));

    for z:=0 to 3 do begin
      ABlockInfo^.DrawQuad[z][0] := Trunc(zoom * (ABlockInfo^.DrawQuad[z][0] -drawX)+drawX);
      ABlockInfo^.DrawQuad[z][1] := Trunc(zoom * (ABlockInfo^.DrawQuad[z][1] -drawY)+drawY);
    end;
    if item is TMapCell then begin
      inc(ABlockInfo^.DrawQuad[0][1], -1);
      inc(ABlockInfo^.DrawQuad[1][0], +1);
      inc(ABlockInfo^.DrawQuad[2][1], +1);
      inc(ABlockInfo^.DrawQuad[3][0], -1);
    end else if zoom < 1.0 then begin
      inc(ABlockInfo^.DrawQuad[0][0], -1);
      inc(ABlockInfo^.DrawQuad[0][1], -1);
      //inc(ABlockInfo^.DrawQuad[1][0], +1);
      inc(ABlockInfo^.DrawQuad[1][1], -1);
      //inc(ABlockInfo^.DrawQuad[2][0], +1);
      //inc(ABlockInfo^.DrawQuad[2][1], +1);
      inc(ABlockInfo^.DrawQuad[3][0], -1);
      //inc(ABlockInfo^.DrawQuad[3][1], +1);
    end;

    if not acFlat.Checked and (item is TMapCell) and
       ((rawWest <> rawZ) or (rawSouth <> rawZ) or (rawEast <> rawZ)) then
    for z:=0 to 3 do begin
      ABlockInfo^.RealQuad[z][0] := Trunc(zoom * (ABlockInfo^.RealQuad[z][0] -drawX)+drawX);
      ABlockInfo^.RealQuad[z][1] := Trunc(zoom * (ABlockInfo^.RealQuad[z][1] -drawY)+drawY);
    end;

    rawZ := -1;
    if ((mnuShowGrid.Checked or mnuShowBlocks.Checked) and (item is TMapCell))
      then if (ABlockInfo^.HighRes <> nil) then rawZ:=2 else rawZ:= 1
    else if (frmVirtualLayer.cbShowBlocks.Checked and (item is TVirtualTile))
      then if ((item.Y mod 8 = 0) or (item.X mod 8 = 0)) then rawZ := 1;

    for z:=0 to rawZ do begin
      //ABlockInfo^.LineWidth[z] := zoom * ABlockInfo^.LineWidth[z];
      ABlockInfo^.LineDraw[z][0][0] := TGLint(Trunc(zoom *(ABlockInfo^.LineDraw[z][0][0] -drawX)+drawX));
      ABlockInfo^.LineDraw[z][0][1] := TGLint(Trunc(zoom *(ABlockInfo^.LineDraw[z][0][1] -drawY)+drawY));
      ABlockInfo^.LineDraw[z][1][0] := TGLint(Trunc(zoom *(ABlockInfo^.LineDraw[z][1][0] -drawX)+drawX));
      ABlockInfo^.LineDraw[z][1][1] := TGLint(Trunc(zoom *(ABlockInfo^.LineDraw[z][1][1] -drawY)+drawY));
    end;
  end;
end;

procedure TfrmMain.Render;
var
  highlight: Boolean;
  intensity, red, green, blue: GLfloat;
  blockInfo: PBlockInfo;
  item: TWorldItem;
  i : byte;
  zoom: Single;
begin
  if tbZoom.Down then zoom := tbZoom.Tag / 1000.0 else zoom:=1.0;

  if not (sbsValid in FScreenBufferState) then
    RebuildScreenBuffer;

  if not (sbsIndexed in FScreenBufferState) then
  begin
    FScreenBuffer.UpdateShortcuts;
    Include(FScreenBufferState, sbsIndexed);
  end;

  if not (sbsFiltered in FScreenBufferState) then
    UpdateFilter;

  blockInfo := nil;
  while FScreenBuffer.Iterate(blockInfo) do
  begin
    if blockInfo^.State = ssFiltered then
      Continue;

    item := blockInfo^.Item;

    if acSelect.Checked or item.CanBeEdited or (item is TVirtualTile) then
    begin
      intensity := 1.0;
      SetNormalLights;
    end else
    begin
      intensity := 0.5;
      SetDarkLights;
    end;

    case blockInfo^.WalkRestriction of
      wrNone:
        begin
          red := 1;
          green := 1;
          blue := 1;
        end;
      wrCanWalk:
        begin
          red := 0.5;
          green := 1;
          blue := 0.5;
        end;
      wrCannotWalk:
        begin
          red := 1;
          green := 0.5;
          blue := 0.5;
        end;
    end;

    if blockInfo^.Translucent then
      glColor4f(intensity * red, intensity * green, intensity * blue, 0.8)
    else
      glColor4f(intensity * red, intensity * green, intensity * blue, 1.0);

    highlight := blockInfo^.Highlighted and item.CanBeEdited;

    if highlight then
    begin
      glEnable(GL_COLOR_LOGIC_OP);
      glLogicOp(GL_COPY_INVERTED);
    end;

    if blockInfo^.HighRes <> nil then
    begin
      glBindTexture(GL_TEXTURE_2D, blockInfo^.HighRes.Texture);

      if not highlight and (blockInfo^.WalkRestriction = wrNone) then
        glEnable(GL_LIGHTING);

      glBegin(GL_QUADS);
        glNormal3fv(@blockInfo^.Normals^[0]);
        glTexCoord2i(0, 0); glVertex2iv(@blockInfo^.DrawQuad[0]);
        glNormal3fv(@blockInfo^.Normals^[3]);
        glTexCoord2i(0, 1); glVertex2iv(@blockInfo^.DrawQuad[3]);
        glNormal3fv(@blockInfo^.Normals^[2]);
        glTexCoord2i(1, 1); glVertex2iv(@blockInfo^.DrawQuad[2]);
        glNormal3fv(@blockInfo^.Normals^[1]);
        glTexCoord2i(1, 0); glVertex2iv(@blockInfo^.DrawQuad[1]);
      glEnd;

      if not highlight and (blockInfo^.WalkRestriction = wrNone) then
        glDisable(GL_LIGHTING);
    end else
    begin
      glBindTexture(GL_TEXTURE_2D, blockInfo^.LowRes.Texture);
      glBegin(GL_QUADS);
        glTexCoord2i(0, 0); glVertex2iv(@blockInfo^.DrawQuad[0]);
        glTexCoord2i(1, 0); glVertex2iv(@blockInfo^.DrawQuad[1]);
        glTexCoord2i(1, 1); glVertex2iv(@blockInfo^.DrawQuad[2]);
        glTexCoord2i(0, 1); glVertex2iv(@blockInfo^.DrawQuad[3]);
      glEnd;
    end;

    if highlight then
      glDisable(GL_COLOR_LOGIC_OP);

    if (blockInfo^.Text <> nil) then
      blockInfo^.Text.Render(blockInfo^.ScreenRect);

    /////////////////
    // Рендинг сетки рельефа
    if (mnuShowGrid.Checked) and (blockInfo^.Item is TMapCell) then
    begin
      glDisable(GL_TEXTURE_2D);                // не использовать цвет текстуры
      glEnable(GL_LINE_SMOOTH);                // сглаживание линий
      //glDisable(GL_LINE_SMOOTH);

      if (tbFlat.Down) then
      begin
        glColor4f(0.8, 0.8, 0.8, 0.9);         // цвет линий
        glLineWidth(0.1);                      // ширина линий
        glBegin(GL_LINES);
          glVertex2iv(@blockInfo^.LineDraw[0][0]);
          glVertex2iv(@blockInfo^.LineDraw[0][1]);
          glVertex2iv(@blockInfo^.LineDraw[1][0]);
          glVertex2iv(@blockInfo^.LineDraw[1][1]);
        glEnd;
      end else
      begin
        if blockInfo^.LineWidth[0] < 0.85
          then glColor4f(1.0, 1.0, 0.0, 0.5)   // цвет линий
          else glColor4f(1.0, 1.0, 1.0, 1.0);  // цвет линий
        glLineWidth(blockInfo^.LineWidth[0]);  // ширина линий
        glBegin(GL_LINES);
          glVertex2iv(@blockInfo^.LineDraw[0][0]);
          glVertex2iv(@blockInfo^.LineDraw[0][1]);
        glEnd;
        if blockInfo^.LineWidth[1] < 0.85
          then glColor4f(1.0, 1.0, 0.0, 0.5)   // цвет линий
          else glColor4f(1.0, 1.0, 1.0, 1.0);  // цвет линий
        glLineWidth(blockInfo^.LineWidth[1]);  // ширина линий
        glBegin(GL_LINES);
          glVertex2iv(@blockInfo^.LineDraw[1][0]);
          glVertex2iv(@blockInfo^.LineDraw[1][1]);
        glEnd;
        if blockInfo^.LineWidth[2] > 0 then
          begin
            glColor4f(1.3281, 0.2510, 1.0, 0.8); // цвет линий
            glLineWidth(blockInfo^.LineWidth[2]);// ширина линий
            glBegin(GL_LINES);
              glVertex2iv(@blockInfo^.LineDraw[2][0]);
              glVertex2iv(@blockInfo^.LineDraw[2][1]);
            glEnd;
          end;
      end;

      glDisable(GL_LINE_SMOOTH);
      glEnable(GL_TEXTURE_2D);
    end else if (mnuShowBlocks.Checked) and (blockInfo^.Item is TMapCell) then
    begin  // Рендинг сетки блоков
      glDisable(GL_TEXTURE_2D);                // не использовать цвет текстуры
      glEnable(GL_LINE_SMOOTH);                // сглаживание линий

      if (blockInfo^.Item.X mod 8 = 0) then begin
        glLineWidth(2.0);
        glColor4f(1.0, 1.0, 0.0, 0.5);
      end else begin
        glColor4f(0.8, 0.8, 0.8, 0.9);
        glLineWidth(0.1);
      end;
      glBegin(GL_LINES);
        glVertex2iv(@blockInfo^.LineDraw[1][0]);
        glVertex2iv(@blockInfo^.LineDraw[1][1]);
      glEnd;
      if (blockInfo^.Item.Y mod 8 = 0) then begin
        glLineWidth(2.0);
        glColor4f(1.0, 1.0, 0.0, 0.5);
      end else begin
        glColor4f(0.8, 0.8, 0.8, 0.9);
        glLineWidth(0.1);
      end;
      glBegin(GL_LINES);
        glVertex2iv(@blockInfo^.LineDraw[0][0]);
        glVertex2iv(@blockInfo^.LineDraw[0][1]);
      glEnd;

      glDisable(GL_LINE_SMOOTH);
      glEnable(GL_TEXTURE_2D);
    end else if (frmVirtualLayer.cbShowBlocks.Checked) and (blockInfo^.Item is TVirtualTile) then
    begin
      glDisable(GL_TEXTURE_2D);                // не использовать цвет текстуры
      glEnable(GL_LINE_SMOOTH);                // сглаживание линий

      for i := 0 to 1 do if blockInfo^.LineWidth[i] > 0 then begin
        glColor4f(0.0, 0.65, 0.68, 0.50);      // цвет линий
        glLineWidth(blockInfo^.LineWidth[i]);  // ширина линий
        glBegin(GL_LINES);
          glVertex2iv(@blockInfo^.LineDraw[i][0]);
          glVertex2iv(@blockInfo^.LineDraw[i][1]);
        glEnd;
      end;

      glDisable(GL_LINE_SMOOTH);
      glEnable(GL_TEXTURE_2D);
    end;

    ////////////////
  end;

  if (FLightManager.LightLevel > 0) and not acFlat.Checked then
    FLightManager.Draw(oglGameWindow.ClientRect, zoom);
  FOverlayUI.Draw(oglGameWindow);

  FLandscape.ResizeBlockCache(0);
end;

procedure TfrmMain.SaveLocations;
var
  xmlDoc: TXMLDocument;
  location: TDOMElement;
  locationNode: PVirtualNode;
  locationInfo: PLocationInfo;
begin
  xmlDoc := TXMLDocument.Create;
  xmlDoc.AppendChild(xmlDoc.CreateElement('Locations'));

  locationNode := vstLocations.GetFirst;
  while locationNode <> nil do
  begin
    locationInfo := vstLocations.GetNodeData(locationNode);
    location := xmlDoc.CreateElement('Location');
    location.AttribStrings['Name'] := UTF8ToCP1251(locationInfo^.Name);
    location.AttribStrings['X'] := IntToStr(locationInfo^.X);
    location.AttribStrings['Y'] := IntToStr(locationInfo^.Y);
    xmlDoc.DocumentElement.AppendChild(location);

    locationNode := vstLocations.GetNext(locationNode);
  end;

  WriteXMLFile(xmlDoc, FLocationsFile);
  xmlDoc.Free;
end;

procedure TfrmMain.SaveRandomPresets;
begin
  WriteXMLFile(FRandomPresetsDoc, FRandomPresetsFile);
end;

procedure TfrmMain.OnLandscapeChanged;
begin
  InvalidateScreenBuffer;
  oglGameWindow.Repaint;
  UpdateCurrentTile;
end;

procedure TfrmMain.OnMapChanged(AMapCell: TMapCell);
begin
  PrepareMapCell(AMapCell);
  ForceUpdateCurrentTile;
  InvalidateFilter
end;

procedure TfrmMain.OnNewBlock(ABlock: TBlock);
begin
  InvalidateScreenBuffer;
end;

procedure TfrmMain.OnStaticDeleted(AStaticItem: TStaticItem);
begin
  FScreenBuffer.Delete(AStaticItem);
  FRepaintNeeded := True;
  ForceUpdateCurrentTile;
  InvalidateFilter
end;

procedure TfrmMain.OnStaticElevated(AStaticItem: TStaticItem);
var
  blockInfo: PBlockInfo;
begin
  AStaticItem.PrioritySolver := FScreenBuffer.GetSerial;
  blockInfo := FScreenBuffer.UpdateSortOrder(AStaticItem);
  if blockInfo <> nil then
  begin
    PrepareScreenBlock(blockInfo);
    Exclude(FScreenBufferState, sbsIndexed);
    ForceUpdateCurrentTile;
    InvalidateFilter
  end;
end;

procedure TfrmMain.OnStaticHued(AStaticItem: TStaticItem);
var
  blockInfo: PBlockInfo;
begin
  blockInfo := nil;
  while FScreenBuffer.Iterate(blockInfo) do
  begin
    if blockInfo^.Item = AStaticItem then
    begin
      PrepareScreenBlock(blockInfo);
      FRepaintNeeded := True;
      ForceUpdateCurrentTile;
      Break;
    end;
  end;
end;

procedure TfrmMain.OnStaticInserted(AStaticItem: TStaticItem);
begin
  if (AStaticItem.X >= FX + FLowOffsetX) and
     (AStaticItem.X <= FX + FHighOffsetX) and
     (AStaticItem.Y >= FY + FLowOffsetY) and
     (AStaticItem.Y <= FY + FHighOffsetY) then
  begin
    AStaticItem.PrioritySolver := FScreenBuffer.GetSerial;
    PrepareScreenBlock(FScreenBuffer.Insert(AStaticItem));
    FRepaintNeeded := True;
    ForceUpdateCurrentTile;
    InvalidateFilter
  end;
end;

function  TfrmMain.LoadListError(condition: Boolean; filename, message : string): Boolean; inline;
var
  answer: Word;
  l, c: Word;
begin
  result := False;
  if not condition
    then Exit;

  l := Length(filename);
  for c := l downto 1 do
    if (filename[c] = '\') or (filename[c] = '/')
      then break;
  if c > 1 then inc(c);
  answer := MessageDlg(Format(GetParseErText('MessageDlgCaption'), [Copy(filename, c, l-c+1)]),
            Format(GetParseErText('MessageDlgTxtLine1')+sLineBreak+
            GetParseErText('MessageDlgTxtLine2')+sLineBreak+sLineBreak+
            GetParseErText('MessageDlgTxtLine3'),
            [message, filename]), mtError, [mbAbort, mbRetry, mbIgnore], 0);
  if answer = mrAbort
    then begin
      Application.Terminate;
      Halt;
    end;
  result := (answer = mrRetry);
end;

procedure TfrmMain.LoadVisibleTiles(AFileName: String);
var
  XMLDoc:  TXMLDocument;
  iNode, node: TDOMNode;
  s: string;
  i, id: Integer;
begin
  frmInitialize.SetStatusLabel(Format(frmInitialize.SplashLoading, ['VirtualTiles.xml']));
  FVisibleTiles := TBits.Create($4000 + FLandscape.MaxStaticID);
  for i := 0 to FVisibleTiles.Size - 1 do
    FVisibleTiles[i] := True;

  // Читаем xml файл с жесткого диска
  ReadXMLFile(XMLDoc, AFileName);
  if LowerCase(XMLDoc.DocumentElement.NodeName) = 'virtualtiles' then
  begin
    iNode := XMLDoc.DocumentElement.FirstChild;
    while iNode <> nil do
    begin
      if LowerCase(iNode.NodeName) = 'unused' then
      begin
        node := iNode.FirstChild;
        while node <> nil do
        begin
          s := LowerCase(node.NodeName);
          if (s = 'tile') or (s = 'land') or (s = 'item') then
            for i := node.Attributes.Length - 1 downto 0 do
              if LowerCase(node.Attributes[i].NodeName) = 'id' then
                if TryStrToInt(node.Attributes[i].NodeValue, id) then
                begin
                  if s = 'item'
                    then Inc(id, $4000);
                  if (id >= 0) and (id < FVisibleTiles.Size)
                    then FVisibleTiles[id] := False;
                end;
          node := node.NextSibling;
        end;
      end;
      iNode := iNode.NextSibling;
    end;
  end;
end;

procedure TfrmMain.LoadLightSourceTiles(AFileName: String);
var
  XMLDoc:  TXMLDocument;
  iNode, node: TDOMNode;
  s: string;
  i, id, bit: Integer;
begin
  frmInitialize.SetStatusLabel(Format(frmInitialize.SplashLoading, ['VirtualTiles.xml']));
  if (FLightSourceTiles = nil) then begin
    getmem(FLightSourceTiles, (FLandscape.MaxStaticID + 1) * SizeOf(TLightTile));
    for i := 0 to FLandscape.MaxStaticID - 1 do
      FillByte(FLightSourceTiles[i], SizeOf(TLightTile), 0);
  end;

  // Читаем xml файл с жесткого диска
  ReadXMLFile(XMLDoc, AFileName);
  if LowerCase(XMLDoc.DocumentElement.NodeName) = 'virtualtiles' then
  begin
    iNode := XMLDoc.DocumentElement.FirstChild;
    while iNode <> nil do
    begin
      if LowerCase(iNode.NodeName) = 'lightsource' then
      begin
        node := iNode.FirstChild;
        while node <> nil do
        begin
          s := LowerCase(node.NodeName);
          if (s = 'tile') or (s = 'item') then begin
            id  :=-1;
            bit := 1;
            for i := node.Attributes.Length - 1 downto 0 do
            begin
              if LowerCase(node.Attributes[i].NodeName) = 'id' then
                if TryStrToInt(node.Attributes[i].NodeValue, id) then
                begin
                  if s = 'tile'
                    then Inc(id, -$4000);
                  //if (id >= 0) and (id < FLightSourceTiles.Size)
                  //  then FLightSourceTiles[id] := True;
                end;
              if LowerCase(node.Attributes[i].NodeName) = 'icon' then
                if TryStrToInt(node.Attributes[i].NodeValue, bit) then
                  if (bit < 1) or (bit > FVLightSrcImageCount) then
                    bit := 1;
            end;
            if (id >= 0) and (id <= FLandscape.MaxStaticID) then
            begin
               FLightSourceTiles[id].image := bit;
            end;
          end;
          node := node.NextSibling;
        end;
      end;
      iNode := iNode.NextSibling;
    end;
  end;

end;

procedure TfrmMain.LoadSurfsTilesList;
begin
  frmInitialize.SetStatusLabel(Format(frmInitialize.SplashLoading, ['SurfaceInf.xml']));
end;

{

procedure TfrmMain.LoadSurfsTilesList;
var
  fPath, str, attribute : string;
  XMLDoc: TXMLDocument;
  cNode, sNode, eNode : TDOMNode;
  c, s, e, a : Integer;

  tCount: Integer;
  tHash1: ^DWORD;
  tHash2: ^DWORD;
  tIndex: ^LongWord;


value : Integer;
  //e, t, a, value : Integer;

//  cCount :   Word;
  sCount :  ^Word;
  eCount : ^PWord;
begin
  if FileExists(FProfileDir + 'SurfaceInf.xml')
    then fPath := (FProfileDir + 'SurfaceInf.xml')
    else if FileExists(FLocalDir + 'SurfaceInf.xml')
      then fPath := (FLocalDir + 'SurfaceInf.xml')
      else if FileExists(FConfigDir + 'SurfaceInf.xml')
        then fPath := (FConfigDir + 'SurfaceInf.xml')
        else if FileExists(ResMan.GetFile('SurfaceInf.xml'))
          then fPath := (ResMan.GetFile('SurfaceInf.xml'))
          else Exit;





  // Читаем xml файл с жесткого диска
  ReadXMLFile(XMLDoc, fPath);
  if LowerCase(XMLDoc.DocumentElement.NodeName) = 'surfaceinf' then
  begin
    // Подсчет категорий
    FSurfsList.GroupCount := 0;
    cNode := XMLDoc.DocumentElement.FirstChild;
    while cNode <> nil do
    begin
      if LowerCase(cNode.NodeName) = 'category' then
        inc(FSurfsList.GroupCount);
      cNode := cNode.NextSibling;
    end;
    getmem(sCount, FSurfsList.GroupCount * SizeOf( Word));
    getmem(eCount, FSurfsList.GroupCount * SizeOf(PWord));

    // Подсчет объектов
    cNode := XMLDoc.DocumentElement.FirstChild;
    while cNode <> nil do
    begin
      if LowerCase(cNode.NodeName) = 'category' then
      begin
        sNode := cNode.FirstChild;
        while sNode <> nil do
        begin



          sNode := sNode.NextSibling;
        end;
      end;

      cNode := cNode.NextSibling;
    end;
    ////////////////////////////////////////////////////////////////////////////


    // Создание списка
    FSurfsList.TilesCount := $4000 + FLandscape.MaxStaticID;
    getmem(FSurfsList.Tiles, FSurfsList.TilesCount * SizeOf(PSurfGrad));
    getmem(FSurfsList.Grads, FSurfsList.GradsCount * SizeOf(TSurfGrad));
    getmem(FSurfsList.Group, FSurfsList.GroupCount * SizeOf(TSurfGroup));

    tCount := 0;

    c := 0; // -------------- 1
    cNode := XMLDoc.DocumentElement.FirstChild;
    while cNode <> nil do
    begin
      if LowerCase(cNode.NodeName) = 'category' then begin
        FSurfsList.Group[c].Name := 'NoName';
        for a := cNode.Attributes.Length - 1 downto 0 do begin
          if LowerCase(cNode.Attributes[a].NodeName) = 'name' then
            FSurfsList.Group[c].Name := CP1251ToUTF8(cNode.Attributes[a].NodeValue);
        end;
        // ?????? FSurfsList.Group[c].Count
        getmem(FSurfsList.Group[c].Info, FSurfsList.Group[c].Count * SizeOf(TSurfGroup));

        s := 0; // -------------- 2
        sNode := cNode.FirstChild;
        while sNode <> nil do
        begin
          if LowerCase(sNode.NodeName) = 'surafece' then begin
            FSurfsList.Group[c].Info[s].Name := 'NoName';
            FSurfsList.Group[c].Info[s].TileID := $4001;
            for a := sNode.Attributes.Length - 1 downto 0 do begin
              if LowerCase(sNode.Attributes[a].NodeName) = 'name' then
                FSurfsList.Group[c].Info[s].Name := CP1251ToUTF8(sNode.Attributes[a].NodeValue);
              if LowerCase(sNode.Attributes[a].NodeName) = 'tileid' then
                if TryStrToInt(sNode.Attributes[a].NodeValue, value) then
                  FSurfsList.Group[c].Info[s].TileID := value;
            end;
            CalcStringCRC32(LowerCase(FSurfsList.Group[c].Info[s].Name),
                            FSurfsList.Group[c].Info[s].TileHash);
            // ?????? FSurfsList.Group[c].Info[s].GradCount
            getmem(FSurfsList.Group[c].Info[s].GradHash, FSurfsList.Group[c].Info[s].GradCount * SizeOf(LongWord));

            e := 0; // -------------- 3
            eNode := sNode.FirstChild;
            while eNode <> nil do
            begin
              str := LowerCase(eNode.NodeName);
              if (str = 'item') or (str = 'land') or (str = 'tile') then begin
                str := 'NoType';
                for a := eNode.Attributes.Length - 1 downto 0 do begin
                  if LowerCase(sNode.Attributes[a].NodeName) = 'type' then
                    str := CP1251ToUTF8(eNode.Attributes[a].NodeValue);
                end;
                CalcStringCRC32(LowerCase(FSurfsList.Group[c].Name + '|' + str),
                                FSurfsList.Group[c].Info[s].GradHash[e]);
                inc(tCount);
              end else if str = 'brush' then begin
                // TODO

              end;
              inc(e);
              eNode := eNode.NextSibling;
            end;    // -------------- 3

          end;
          inc(s);
          sNode := sNode.NextSibling;
        end;    // -------------- 2

      end;
      inc(c);
      cNode := cNode.NextSibling;
    end;    // -------------- 1

    ////////////////////////////////////////////////////////////////////////////

    // Построение списка TSurfGrad
    getmem(tHash, tCount * SizeOf(DWORD));
    tCount := 0;
    for c := 0 to FSurfsList.GroupCount do
      for s := 0 to FSurfsList.Group[c].Count do
        for e := 0 to FSurfsList.Group[c].Info[s].GradCount do
        begin
          for a := tCount-1 downto -1 do
            if a < 0 then begin
              tHash[tCount] = FSurfsList.Group[c].Info[s].GradHash[e];
              inc(tCount);
            end else if tHash[a] = FSurfsList.Group[c].Info[s].GradHash[e]
                then break;
        end;

    FSurfsList.GradsCount := tCount;
    getmem(FSurfsList.Grads, FSurfsList.GradsCount * SizeOf(TSurfGrad));




    freemem(gHash);



    getmem(sCount, FSurfsList.GroupCount * SizeOf( Word));
    getmem(eCount, FSurfsList.GroupCount * SizeOf(PWord));

    // Подсчет объектов
    cNode := XMLDoc.DocumentElement.FirstChild;
    while cNode <> nil do
    begin
      if LowerCase(cNode.NodeName) = 'category' then
      begin
        sNode := cNode.FirstChild;
        while sNode <> nil do
        begin



          sNode := sNode.NextSibling;
        end;
      end;

      cNode := cNode.NextSibling;
    end;



  end;


end;

}

procedure TfrmMain.LoadEntryTilesList;
var
  fPath, s, attribute : string;
  XMLDoc: TXMLDocument;
  eNode, tNode : TDOMNode;
  e, t, a, value : Integer;
  // Создание миниатюр
  id : Integer;
  hue : Word;
  alt : integer;

  artHue  : THue;
  artEntry : TArt;
  dSize: TSize;
  entHash: QWORD;
  Resised: Float;
  tCRC32, CacheLen: DWORD;
  cacheHash, cachePost: PDWORD;
  imageData: TImageData;
  imageCache: TFileStream;
  imageTemp: TSingleImage;
  dX, dY, dZ, dW, dH : Integer;
  entStack, entFirst, entLast, entPrev, entNext: PByte;
  dstCanvas: TFastARGB32Canvas;
  srcCanvas: TFastARGB32Canvas;
begin
  frmInitialize.SetStatusLabel(Format(frmInitialize.SplashLoading, ['TilesEntry.xml']));
  if FileExists(FProfileDir + 'TilesEntry.xml')
    then fPath := (FProfileDir + 'TilesEntry.xml')
    else if FileExists(FLocalDir + 'TilesEntry.xml')
      then fPath := (FLocalDir + 'TilesEntry.xml')
      else if FileExists(FConfigDir + 'TilesEntry.xml')
        then fPath := (FConfigDir + 'TilesEntry.xml')
        else if FileExists(ResMan.GetFile('TilesEntry.xml'))
          then fPath := (ResMan.GetFile('TilesEntry.xml'))
          else Exit;

  FEntryList.Count := 0;

  // Читаем xml файл с жесткого диска
  ReadXMLFile(XMLDoc, fPath);
  if LowerCase(XMLDoc.DocumentElement.NodeName) = 'tilesentry' then
  begin
    // Подсчет объектов
    eNode := XMLDoc.DocumentElement.FirstChild;
    while eNode <> nil do
    begin
      if LowerCase(eNode.NodeName) = 'entry' then
        inc(FEntryList.Count);
      eNode := eNode.NextSibling;
    end;
    getmem(FEntryList.Entry, FEntryList.Count * SizeOf(PGroupEntry));

    // Построение списка
    eNode := XMLDoc.DocumentElement.FirstChild;
    for e := 0 to FEntryList.Count - 1 do begin
      //Logger.Send([lcInfo], 'Entry: %d / %d - Start...', [e, FEntryList.Count]);

      new (FEntryList.Entry[e]);
      FEntryList.Entry[e]^.Name := 'NoName';
      for a := eNode.Attributes.Length - 1 downto 0 do begin
         if LowerCase(eNode.Attributes[a].NodeName) = 'id' then
           if TryStrToInt(eNode.Attributes[a].NodeValue, value) then
             FEntryList.Entry[e]^.ID := value;
         if LowerCase(eNode.Attributes[a].NodeName) = 'name' then
           FEntryList.Entry[e]^.Name := CP1251ToUTF8(eNode.Attributes[a].NodeValue);
      end;

      // Подсчет тайлов
      tNode := eNode.FirstChild;
      FEntryList.Entry[e]^.Count := 0;
      while tNode <> nil do begin
        s := LowerCase(tNode.NodeName);
        if (s = 'tile') or (s = 'item') or (s = 'land') then
          inc(FEntryList.Entry[e]^.Count);
        tNode := tNode.NextSibling;
      end;
      getmem(FEntryList.Entry[e]^.ETile, FEntryList.Entry[e]^.Count * SizeOf(TEntryTile));

      // Чтение списка тайлов
      tNode := eNode.FirstChild;
      for t := 0 to FEntryList.Entry[e]^.Count - 1 do
      begin
        //Logger.Send([lcInfo], 'Entry: %d - Item: %d / %d', [e, t, FEntryList.Entry[e]^.Count]);
        FEntryList.Entry[e]^.ETile[t].ID  := 0;
        FEntryList.Entry[e]^.ETile[t].Hue := 0;
        FEntryList.Entry[e]^.ETile[t].X   := 0;
        FEntryList.Entry[e]^.ETile[t].Y   := 0;
        FEntryList.Entry[e]^.ETile[t].Z   := 0;
        for a := tNode.Attributes.Length - 1 downto 0 do begin
          attribute := LowerCase(tNode.Attributes[a].NodeName);
          if attribute = 'id' then begin
            if TryStrToInt(tNode.Attributes[a].NodeValue, value) then begin
              if s = 'item' then Inc(value, $4000);
              FEntryList.Entry[e]^.ETile[t].ID := value;
            end;
          end else if attribute = 'hue' then begin
            if TryStrToInt(tNode.Attributes[a].NodeValue, value)
              then FEntryList.Entry[e]^.ETile[t].Hue := value;
          end else if attribute = 'x' then begin
            if TryStrToInt(tNode.Attributes[a].NodeValue, value)
              then FEntryList.Entry[e]^.ETile[t].X := value;
          end else if attribute = 'y' then begin
            if TryStrToInt(tNode.Attributes[a].NodeValue, value)
              then FEntryList.Entry[e]^.ETile[t].Y := value;
          end else if attribute = 'z' then begin
            if TryStrToInt(tNode.Attributes[a].NodeValue, value)
              then FEntryList.Entry[e]^.ETile[t].Z := value;
          end;
        end;

        while (tNode <> nil) do begin
          tNode := tNode.NextSibling;
          if tNode <> nil then begin
            s := LowerCase(tNode.NodeName);
            if (s = 'tile') or (s = 'item') or (s = 'land')
            then break;
          end;
        end;

      end;

      while (eNode <> nil) do begin
        eNode := eNode.NextSibling;
        if (eNode <> nil) and (LowerCase(eNode.NodeName) = 'entry')
        then break;
      end;
    end;
  end;
  XMLDoc.Free;

  Logger.Send([lcInfo], 'TfrmMain.LoadEntryTilesList SizeOf(TImageFormat)', SizeOf(TImageFormat));
  // Создание миниатюр
  if (FProfileDir <> '')
    then fPath := FProfileDir
    else fPath := FConfigDir;
  fPath := fPath + 'TilesEntry.cache';
  if not FileExists(fPath)
     then FileClose(FileCreate(fPath));
  imageCache := TFileStream.Create(fPath, fmOpenReadWrite);
  if (imageCache.Size < 4) then begin
    value := 0;
    cacheHash := nil;
    cachePost := nil;
    imageCache.WriteDWord(value);
  end else begin
    value := imageCache.ReadDWord;
    getmem(cacheHash, 4 * value);
    getmem(cachePost, 4 * value);
  end;

  t := 0;
  CacheLen := value;
  while (t < value) do begin
    cacheHash[t] := imageCache.ReadDWord;
    cachePost[t] := imageCache.Position;
    imageCache.Position := imageCache.Position + 12;
    e := imageCache.ReadDWord;
    imageCache.Position := imageCache.Position + e;
    inc(t);
  end;

  for e := 0 to FEntryList.Count - 1 do begin
    s := Format('e:%d|n:%s|', [FEntryList.Entry[e]^.ID, FEntryList.Entry[e]^.Name]);
    for t := 0 to FEntryList.Entry[e]^.Count - 1 do
        s := s + Format('i:%d|h:%d|x:%d|y:%d|z:%d|', [FEntryList.Entry[e]^.ETile[t].ID, FEntryList.Entry[e]^.ETile[t].Hue,
                     FEntryList.Entry[e]^.ETile[t].X, FEntryList.Entry[e]^.ETile[t].Y, FEntryList.Entry[e]^.ETile[t].Z]);
    CalcStringCRC32(s, tCRC32);
    FEntryList.Entry[e]^.Image := nil;
    // Чтение изображения из кэша
    for t := 0 to value - 1 do
      if (cacheHash[t] = tCRC32) then begin
        imageCache.Position := cachePost[t];
        imageCache.Read(imageData, 16);
        getmem(imageData.Bits, imageData.Size);
        imageCache.Read(PByte(imageData.Bits)^, imageData.Size);
        imageData.Palette := nil;
        FEntryList.Entry[e]^.Image := TSingleImage.CreateFromData(imageData);
        freemem(imageData.Bits);
        //Logger.Send([lcInfo], 'TfrmMain.LoadEntryTilesList entry %d Loaded from Cache', [FEntryList.Entry[e]^.ID]);
        //FEntryList.Entry[e]^.Image.Format := ifA1R5G5B5;
        Break;
      end;
    if (FEntryList.Entry[e]^.Image <> nil)
      then Continue;

    // Сортировка тайлов для отрисовки
    dW := 0;
    dH := 0;
    alt:= 0;
    getmem(entStack, FEntryList.Entry[e]^.Count * 12);
    FillByte(entStack^, FEntryList.Entry[e]^.Count * 12, 0);
    entFirst := entStack;
    entLast  := entStack;
    for t := 0 to FEntryList.Entry[e]^.Count - 1 do begin
      dSize := ResMan.Art.GetArtSize(FEntryList.Entry[e]^.ETile[t].ID);
      dX :=  44 * (FEntryList.Entry[e]^.ETile[t].X - FEntryList.Entry[e]^.ETile[t].Y);
      dY := -22 * (FEntryList.Entry[e]^.ETile[t].X + FEntryList.Entry[e]^.ETile[t].Y) + 4 * FEntryList.Entry[e]^.ETile[t].Z;
      if (dY >= 0) then begin
        dH := max(dH, alt + dY + dSize.cy);
      end else begin
        a := alt;
        alt:= max(alt, Abs(dY));
        dH := alt + max(dH - a, max(0, dSize.cy + dY));
      end;
      dW := max(dW, Abs(dX) + dSize.cx);
      dX := min(max(0, FEntryList.Entry[e]^.ETile[t].X + 1023), 2047);
      dY := min(max(0, FEntryList.Entry[e]^.ETile[t].Y + 1023), 2047);
      dZ := min(max(0, FEntryList.Entry[e]^.ETile[t].Z +  255),  513);

      entHash := DWORD(dX) * 1064440 + DWORD(dY) * 520 + DWORD(dZ + 7);
      if (tdfFoliage in ResMan.Tiledata.TileData[FEntryList.Entry[e]^.ETile[t].ID].Flags)
      then Inc(entHash, +1);
      PDWord(entLast)^ := entHash;
      PDWord(entLast + 4)^ := DWord(t);

      if (entLast = entStack) then begin
        inc(entLast, 12);
      end else begin
        entNext := entFirst;
        entPrev  := nil;
        while True do begin
          if (entNext = nil) then begin
            PDWord(entPrev + 8)^ := PtrInt(entLast);
            inc(entLast, 12);
            Break;
          end else if (PDWord(entNext)^ >= entHash) then begin
            if (entPrev <> nil) then begin
              PDWord(entPrev + 8)^ := PtrInt(entLast);
            end else begin
              entFirst := entLast;
            end;
            PDWord(entLast + 8)^ := PtrInt(entNext);
            inc(entLast, 12);
            Break;
          end;
          entPrev := entNext;
          entNext := PByte(PDWord(entNext + 8)^);
        end;
      end;
    end;

    // Отрисовка изображения миниатюры
    //Logger.Send([lcInfo], 'TfrmMain.LoadEntryTilesList %d dW: %d dH: %d', [FEntryList.Entry[e]^.ID, dW, dH]);
    FEntryList.Entry[e]^.Image := TSingleImage.CreateFromParams(dW, dH, ifA8R8G8B8);
    dstCanvas := TFastARGB32Canvas.CreateForImage(FEntryList.Entry[e]^.Image);
    dstCanvas.FillColor32 := $00404040;
    dstCanvas.FillRect(Rect(0,0,FEntryList.Entry[e]^.Image.Width,FEntryList.Entry[e]^.Image.Height));
    entNext := entFirst;
    while (entNext <> nil) do begin

//if ((FEntryList.Entry[e]^.ID < 9120) and (FEntryList.Entry[e]^.ID > 9025))
//then break;
      t := PDWord(entNext + 4)^;
      //Logger.Send([lcInfo], 'TfrmMain.LoadEntryTilesList t: %d [%d/%d]', [t, alt, FEntryList.Entry[e]^.Image.Height]);
      entNext := PByte(PDWord(entNext + 8)^);
      id  := FEntryList.Entry[e]^.ETile[t].ID;
      hue := FEntryList.Entry[e]^.ETile[t].Hue;
      if ResMan.Art.Exists(id) then begin
        if hue > 0
           then artHue := ResMan.Hue.Hues[hue - 1]
           else artHue := nil;

        artEntry := ResMan.Art.GetArt(id, EncodeUOColor($0FF0F000), artHue, tdfPartialHue in TTileData(ResMan.Tiledata.Block[id]).Flags);
        srcCanvas := TFastARGB32Canvas.CreateForImage(artEntry.Graphic);
        dX := (FEntryList.Entry[e]^.Image.Width - artEntry.Graphic.Width) div 2 + 22 * (FEntryList.Entry[e]^.ETile[t].X - FEntryList.Entry[e]^.ETile[t].Y);
        dY := FEntryList.Entry[e]^.Image.Height - artEntry.Graphic.Height - alt +
                 22 * (FEntryList.Entry[e]^.ETile[t].X + FEntryList.Entry[e]^.ETile[t].Y) - 4 * FEntryList.Entry[e]^.ETile[t].Z;
        srcCanvas.DrawAlpha(Rect(0,0,artEntry.Graphic.Width,artEntry.Graphic.Height), dstCanvas, dX,dY);
        srcCanvas.Free;
        artEntry.Free;
      end;
    end;


    // Уменьшение изоображения
    dW := 93;
    dH := 96;
    dSize.cx := FEntryList.Entry[e]^.Image.Width;
    dSize.cy := FEntryList.Entry[e]^.Image.Height;
    Resised := Min(Min(Float(dW)/Float(dSize.cx), Float(dH)/Float(dSize.cy)), 1.0);
    dSize.cx := Trunc(Resised * Float(dSize.cx));
    dSize.cy := Trunc(Resised * Float(dSize.cy));
    imageTemp := TSingleImage.CreateFromParams(dSize.cx, dSize.cy, ifA8R8G8B8);
    srcCanvas := TFastARGB32Canvas.CreateForImage(imageTemp);
    dstCanvas.StretchDrawAdd(Rect(0,0,FEntryList.Entry[e]^.Image.Width,FEntryList.Entry[e]^.Image.Height),
                             srcCanvas, Rect(0,0,dSize.cx,dSize.cy), rfNearest);//rfBilinear);
    srcCanvas.Free;
    dstCanvas.Free;
    freemem(entStack);
    FEntryList.Entry[e]^.Image.Free;
    FEntryList.Entry[e]^.Image := imageTemp;

    // Сохранение изоображение в кэше
    imageCache.Position := imageCache.Size;
    imageCache.WriteDWord(tCRC32);
    imageData := FEntryList.Entry[e]^.Image.ImageDataPointer^;
    imageCache.Write(imageData, 16);
    imageCache.Write(imageData.Bits^, imageData.Size);
    inc(CacheLen, +1);
    //Logger.Send([lcInfo], 'TfrmMain.LoadEntryTilesList imageData.Palette <> nil', imageData.Palette <> nil);

    //
    Application.ProcessMessages;
  end;

  if (value > 0) then begin
    freemem(cacheHash);
    freemem(cachePost);
  end;
  imageCache.Position := 0;
  imageCache.WriteDWord(CacheLen);
  imageCache.Free;

  { // Отладка - проверка считанного файла
  Logger.EnterMethod([lcInfo, lcDebug], 'TfrmMain.LoadEntryTilesList');
  Logger.Send([lcInfo], 'Entries Number: %x', [FEntryList.Count]);
  for e := 0 to FEntryList.Count - 1 do begin
    Logger.Send([lcInfo], '<Entry Id="%.4d" Name="%s" Tiles="%x">',
      [FEntryList.Entry[e]^.ID, FEntryList.Entry[e]^.Name, FEntryList.Entry[e]^.Count]);
    for t := 0 to FEntryList.Entry[e]^.Count - 1 do
      Logger.Send([lcInfo], '    <Tile Id="0x%.5x" Hue="0x%.3x" X="%d" Y="%d" Z="%d">',
        [FEntryList.Entry[e]^.ETile[t].ID, FEntryList.Entry[e]^.ETile[t].Hue,
        FEntryList.Entry[e]^.ETile[t].X, FEntryList.Entry[e]^.ETile[t].Y, FEntryList.Entry[e]^.ETile[t].Z]);
    Logger.Send([lcInfo], '</Entry>');
  end;
  Logger.ExitMethod([lcInfo, lcDebug], 'TfrmMain.LoadEntryTilesList');
  }
end;

procedure TfrmMain.LoadBrushTilesList;
var
  fPath, s, attribute : string;
  XMLDoc: TXMLDocument;
  bNode, tNode, eNode : TDOMNode;
  z, b, t, a, e, value: Integer;
  uu, ur, ll, ul, dl, dr : Word;
  valueF : Single;
  brushTile: TBrushTile;
  // Создание миниатюр
  id : Integer;
  destColor, hue : Word;
  destRect: TRect;

  texmEntry : TTexture;
  dX, dY, dW, dH : Integer;
begin
  frmInitialize.SetStatusLabel(Format(frmInitialize.SplashLoading, ['TilesBrush.xml']));
  if FileExists(FProfileDir + 'TilesBrush.xml')
    then fPath := (FProfileDir + 'TilesBrush.xml')
    else if FileExists(FLocalDir + 'TilesBrush.xml')
      then fPath := (FLocalDir + 'TilesBrush.xml')
      else if FileExists(FConfigDir + 'TilesBrush.xml')
        then fPath := (FConfigDir + 'TilesBrush.xml')
        else if FileExists(ResMan.GetFile('TilesBrush.xml'))
          then fPath := (ResMan.GetFile('TilesBrush.xml'))
          else Exit;

  FBrushList.Count := 0;

  // Читаем xml файл с жесткого диска
  ReadXMLFile(XMLDoc, fPath);
  if LowerCase(XMLDoc.DocumentElement.NodeName) = 'tilesbrush' then
  begin
    // Подсчет объектов
    bNode := XMLDoc.DocumentElement.FirstChild;
    while bNode <> nil do
    begin
      if LowerCase(bNode.NodeName) = 'brush' then
        inc(FBrushList.Count);
      bNode := bNode.NextSibling;
    end;
    getmem(FBrushList.Brush, FBrushList.Count * SizeOf(PGroupBrush));

    // Построение списка
    bNode := XMLDoc.DocumentElement.FirstChild;
    for b := 0 to FBrushList.Count - 1 do begin
      //Logger.Send([lcInfo], 'Brush: %d / %d - Start...', [b+1, FBrushList.Count]);
      new (FBrushList.Brush[b]);
      FBrushList.Brush[b]^.ID := 0;
      FBrushList.Brush[b]^.Name := 'NoName';
      for a := bNode.Attributes.Length - 1 downto 0 do begin
         if LowerCase(bNode.Attributes[a].NodeName) = 'id' then
           if TryStrToInt(bNode.Attributes[a].NodeValue, value) then
             FBrushList.Brush[b]^.ID := value;
         if LowerCase(bNode.Attributes[a].NodeName) = 'name' then
           FBrushList.Brush[b]^.Name := CP1251ToUTF8(bNode.Attributes[a].NodeValue);
      end;

      // Тестирование...
      if LoadListError((FBrushList.Brush[b]^.ID = 0) or (FBrushList.Brush[b]^.ID > 9999),
         fPath, Format(GetParseErText('blTagBrushAttrID'),  [FBrushList.Brush[b]^.ID, FBrushList.Brush[b]^.Name]))
         //'Missmatch or wrong <ID> attribute in BrushID: %.4d ("%s"). Brush ID must be larger  0 and less than 9999', [FBrushList.Brush[b]^.ID, FBrushList.Brush[b]^.Name]))
         then begin LoadBrushTilesList; Exit; end;
      if b > 0 then for z := b-1 downto 0 do begin
        if LoadListError(FBrushList.Brush[z]^.ID = FBrushList.Brush[b]^.ID,
           fPath, Format(GetParseErText('blTagBrushDuplicate'), [FBrushList.Brush[b]^.ID]))
           then begin LoadBrushTilesList; Exit; end;
      end;

      // Подсчет тайлов и переходов
      tNode := bNode.FirstChild;
      FBrushList.Brush[b]^.Count  := 0;
      FBrushList.Brush[b]^.ECount := 0;
      while tNode <> nil do begin
        s := LowerCase(tNode.NodeName);
        if (s = 'brushTile') or (s = 'land') then
          inc(FBrushList.Brush[b]^.Count)
        else if (s = 'edge') then
          inc(FBrushList.Brush[b]^.ECount);
        tNode := tNode.NextSibling;
      end;
      getmem(FBrushList.Brush[b]^.BTile,  FBrushList.Brush[b]^.Count  * SizeOf(PBrushTile));
      getmem(FBrushList.Brush[b]^.EdgeId, FBrushList.Brush[b]^.ECount * SizeOf(PWord));
      getmem(FBrushList.Brush[b]^.BEdges, FBrushList.Brush[b]^.ECount * SizeOf(PGroupBrushEdge));

      // Тестирование...
      if LoadListError(FBrushList.Brush[b]^.Count = 0,
         fPath, Format(GetParseErText('blTagBrushEmpty'), [FBrushList.Brush[b]^.ID]))
         then begin LoadBrushTilesList; Exit; end;


      // Чтение списка тайлов
      tNode := bNode.FirstChild;
      t := 0; e := 0;
      while tNode <> nil do
      begin
        s := LowerCase(tNode.NodeName);
        if (s = 'brushTile') or (s = 'land') then begin
          //Logger.Send([lcInfo], 'Brush: %d - Land: %d / %d', [b+1, t+1, FBrushList.Brush[b]^.Count]);
          brushTile.ID := $FFFF;
          brushTile.Chance := 1.0;
          for a := tNode.Attributes.Length - 1 downto 0 do begin
            attribute := LowerCase(tNode.Attributes[a].NodeName);
            if attribute = 'id' then begin
              if TryStrToInt(tNode.Attributes[a].NodeValue, value)
                then brushTile.ID := value;
            end else if attribute = 'chance' then begin
              if TryStrToFloat(tNode.Attributes[a].NodeValue, valueF)
                then brushTile.Chance := valueF;
            end;
          end;
          brushTile.Mask   := $0F;
          brushTile.Brush1 := FBrushList.Brush[b];
          brushTile.Brush2 := FBrushList.Brush[b];

          // Тестирование...
          if LoadListError(brushTile.ID = $FFFF,
             fPath, Format(GetParseErText('blTagTileAttrID'), [FBrushList.Brush[b]^.ID]))
             then begin LoadBrushTilesList; Exit; end;
          if LoadListError(brushTile.ID > $3FFF,
             fPath, Format(GetParseErText('blTagTileAttrIDOutOfRange'), [brushTile.ID, brushTile.ID, FBrushList.Brush[b]^.ID]))
             then begin LoadBrushTilesList; Exit; end;
          if LoadListError(FBrushList.Tiles[brushTile.ID].ID = brushTile.ID,
             fPath, Format(GetParseErText('blTagTileRedeclaration'), [brushTile.ID, brushTile.ID, FBrushList.Brush[b]^.ID]))
             then begin LoadBrushTilesList; Exit; end;

          FBrushList.Tiles[brushTile.ID] := brushTile;
          FBrushList.Brush[b]^.BTile[t] := @FBrushList.Tiles[brushTile.ID];
          inc(t);
        end else if  (s = 'edge') then begin
          //Logger.Send([lcInfo], 'Brush: %d - Edge: %d / %d', [b+1, e+1, FBrushList.Brush[b]^.ECount]);
          //new (FBrushList.Brush[b]^.EdgeId[e]);
          new (FBrushList.Brush[b]^.BEdges[e]);
          FBrushList.Brush[b]^.BEdges[e]^.ID := 0;
          for a := tNode.Attributes.Length - 1 downto 0 do begin
            attribute := LowerCase(tNode.Attributes[a].NodeName);
            if attribute = 'to' then begin
              if TryStrToInt(tNode.Attributes[a].NodeValue, value)
                then FBrushList.Brush[b]^.BEdges[e]^.ID := value;
            end;
          end;
          // Тестирование...
          if LoadListError((FBrushList.Brush[b]^.BEdges[e]^.ID = 0) or (FBrushList.Brush[b]^.BEdges[e]^.ID > 9999),
             fPath, Format(GetParseErText('blTagEdgeAttrTo'), [FBrushList.Brush[b]^.ID]))
             then begin LoadBrushTilesList; Exit; end;

          // Подсчет тайлов в переходе
          eNode := tNode.FirstChild;
          FBrushList.Brush[b]^.BEdges[e]^.CountUU := 0;
          FBrushList.Brush[b]^.BEdges[e]^.CountUR := 0;
          FBrushList.Brush[b]^.BEdges[e]^.CountLL := 0;
          FBrushList.Brush[b]^.BEdges[e]^.CountUL := 0;
          FBrushList.Brush[b]^.BEdges[e]^.CountDL := 0;
          FBrushList.Brush[b]^.BEdges[e]^.CountDR := 0;
          while eNode <> nil do begin
            s := LowerCase(eNode.NodeName);
            if (s = 'brushTile') or (s = 'land') then begin
              attribute := '';
              for a := eNode.Attributes.Length - 1 downto 0 do begin
                attribute := LowerCase(eNode.Attributes[a].NodeName);
                if attribute = 'type' then begin
                  s := LowerCase(CP1251ToUTF8(eNode.Attributes[a].NodeValue));
                  if      s = 'uu' then inc(FBrushList.Brush[b]^.BEdges[e]^.CountUU)
                  else if s = 'ur' then inc(FBrushList.Brush[b]^.BEdges[e]^.CountUR)
                  else if s = 'll' then inc(FBrushList.Brush[b]^.BEdges[e]^.CountLL)
                  else if s = 'ul' then inc(FBrushList.Brush[b]^.BEdges[e]^.CountUL)
                  else if s = 'dl' then inc(FBrushList.Brush[b]^.BEdges[e]^.CountDL)
                  else if s = 'dr' then inc(FBrushList.Brush[b]^.BEdges[e]^.CountDR);
                  break;
                end;
              end;

              if LoadListError((attribute<>'type') or ((s<>'uu')and(s<>'ur')and(s<>'ll')and(s<>'ul')and(s<>'dl')and(s<>'dr')),
                 fPath, Format(GetParseErText('blTagTile2AttrType'), [brushTile.ID, brushTile.ID, FBrushList.Brush[b]^.BEdges[e]^.ID, FBrushList.Brush[b]^.ID]))
                 then begin LoadBrushTilesList; Exit; end;
            end;
            eNode := eNode.NextSibling;
          end;
          getmem(FBrushList.Brush[b]^.BEdges[e]^.BTileUU,  FBrushList.Brush[b]^.BEdges[e]^.CountUU  * SizeOf(PBrushTile));
          getmem(FBrushList.Brush[b]^.BEdges[e]^.BTileUR,  FBrushList.Brush[b]^.BEdges[e]^.CountUR  * SizeOf(PBrushTile));
          getmem(FBrushList.Brush[b]^.BEdges[e]^.BTileLL,  FBrushList.Brush[b]^.BEdges[e]^.CountLL  * SizeOf(PBrushTile));
          getmem(FBrushList.Brush[b]^.BEdges[e]^.BTileUL,  FBrushList.Brush[b]^.BEdges[e]^.CountUL  * SizeOf(PBrushTile));
          getmem(FBrushList.Brush[b]^.BEdges[e]^.BTileDL,  FBrushList.Brush[b]^.BEdges[e]^.CountDL  * SizeOf(PBrushTile));
          getmem(FBrushList.Brush[b]^.BEdges[e]^.BTileDR,  FBrushList.Brush[b]^.BEdges[e]^.CountDR  * SizeOf(PBrushTile));
          // Чтение списка тайлов в переходе
          uu := 0; ur := 0; ll := 0; ul := 0; dl := 0; dr := 0;
          eNode := tNode.FirstChild;
          while eNode <> nil do begin
            s := LowerCase(eNode.NodeName);
            if (s = 'brushTile') or (s = 'land') then begin
              //Logger.Send([lcInfo], 'Brush: %d - Edge: %d - Land: %d / %d', [b+1, e+1, uu+ur+ll+ul+dl+dr+1, FBrushList.Brush[b]^.BEdges[e]^.CountUU+FBrushList.Brush[b]^.BEdges[e]^.CountUR+FBrushList.Brush[b]^.BEdges[e]^.CountLL+FBrushList.Brush[b]^.BEdges[e]^.CountUL+FBrushList.Brush[b]^.BEdges[e]^.CountDL+FBrushList.Brush[b]^.BEdges[e]^.CountDR]);
              brushTile.ID := $FFFF;
              brushTile.Chance := 1.0;
              for a := eNode.Attributes.Length - 1 downto 0 do begin
                attribute := LowerCase(eNode.Attributes[a].NodeName);
                if attribute = 'type' then begin
                  s := LowerCase(CP1251ToUTF8(eNode.Attributes[a].NodeValue));
                  if      s = 'uu' then brushTile.Mask := $03
                  else if s = 'ur' then brushTile.Mask := $07
                  else if s = 'll' then brushTile.Mask := $09
                  else if s = 'ul' then brushTile.Mask := $0B
                  else if s = 'dl' then brushTile.Mask := $0D
                  else if s = 'dr' then brushTile.Mask := $0E;
                end else if attribute = 'id' then begin
                  if TryStrToInt(eNode.Attributes[a].NodeValue, value)
                    then brushTile.ID := value;
                end else if attribute = 'chance' then begin
                  if TryStrToFloat(eNode.Attributes[a].NodeValue, valueF)
                    then brushTile.Chance := valueF;
                end;
              end;

              // Тестирование...
              if LoadListError(brushTile.ID = $FFFF,
                 fPath, Format(GetParseErText('blTagTile2AttrID'), [FBrushList.Brush[b]^.ID]))
                 then begin LoadBrushTilesList; Exit; end;
              if LoadListError(brushTile.ID > $3FFF,
                 fPath, Format(GetParseErText('blTagTile2AttrIDOutOfRange'), [brushTile.ID, brushTile.ID, FBrushList.Brush[b]^.ID]))
                 then begin LoadBrushTilesList; Exit; end;
              if LoadListError(FBrushList.Tiles[brushTile.ID].ID = brushTile.ID,
                 fPath, Format(GetParseErText('blTagTile2Redeclaration'), [brushTile.ID, brushTile.ID, FBrushList.Brush[b]^.ID]))
                 then begin LoadBrushTilesList; Exit; end;

              brushTile.Brush1 := FBrushList.Brush[b];
              brushTile.Brush2 := nil;
              FBrushList.Tiles[brushTile.ID] := brushTile;
              if          brushTile.Mask = $03 then begin
                FBrushList.Brush[b]^.BEdges[e]^.BTileUU[uu] := @FBrushList.Tiles[brushTile.ID]; inc(uu);
              end else if brushTile.Mask = $07 then begin
                FBrushList.Brush[b]^.BEdges[e]^.BTileUR[ur] := @FBrushList.Tiles[brushTile.ID]; inc(ur);
              end else if brushTile.Mask = $09 then begin
                FBrushList.Brush[b]^.BEdges[e]^.BTileLL[ll] := @FBrushList.Tiles[brushTile.ID]; inc(ll);
              end else if brushTile.Mask = $0B then begin
                FBrushList.Brush[b]^.BEdges[e]^.BTileUL[ul] := @FBrushList.Tiles[brushTile.ID]; inc(ul);
              end else if brushTile.Mask = $0D then begin
                FBrushList.Brush[b]^.BEdges[e]^.BTileDL[dl] := @FBrushList.Tiles[brushTile.ID]; inc(dl);
              end else if brushTile.Mask = $0E then begin
                FBrushList.Brush[b]^.BEdges[e]^.BTileDR[dr] := @FBrushList.Tiles[brushTile.ID]; inc(dr);
              end;
              FBrushList.Tiles[brushTile.ID].ID := FBrushList.Brush[b]^.BEdges[e]^.ID; // Временно запоминаем ID перехода (позже востанавливаем ID тайла)
            end;
            eNode := eNode.NextSibling;
          end;
          FBrushList.Brush[b]^.EdgeId[e] := @(FBrushList.Brush[b]^.BEdges[e]^.ID);
          inc(e);
        end;
        tNode := tNode.NextSibling;
      end;
      bNode := bNode.NextSibling;
    end;
  end;
  XMLDoc.Free;

  // Задание вторичных кистей
  for t := 0 to $3FFF do begin
    if (FBrushList.Tiles[t].Brush1 <> nil) and (FBrushList.Tiles[t].Brush2 = nil)
    then begin
      for b := 0 to FBrushList.Count - 1 do
        if FBrushList.Brush[b]^.ID = FBrushList.Tiles[t].ID then begin
          FBrushList.Tiles[t].Brush2 := FBrushList.Brush[b];
          break;
        end;

      // Тестирование...
      if LoadListError((FBrushList.Tiles[t].Brush2 = nil),
         fPath, Format(GetParseErText('blTagEdgeUnknown'), [FBrushList.Tiles[t].Brush1^.ID, FBrushList.Tiles[t].ID]))
         then begin LoadBrushTilesList; Exit; end;

      FBrushList.Tiles[t].ID := t;
    end else if (FBrushList.Tiles[t].Brush1 = nil) and (FBrushList.Tiles[t].Brush2 = nil)
    then begin // Пустые тайлы
      FBrushList.Tiles[t].ID:= $FFFF;
      FBrushList.Tiles[t].Mask:= $00;
    end;
  end;

  // Тестирование...
//  for t := 0 to $3FFF do
//    if LoadListError((FBrushList.Tiles[t].Brush1 <> nil) and (FBrushList.Tiles[t].Brush2 = nil),
//       fPath, Format(GetParseErText('blTagTile2Redeclaration'), [FBrushList.Brush[b]^.ID]))
//       then begin LoadBrushTilesList; Exit; end;


  // Создание миниатюр
  for b := 0 to FBrushList.Count - 1 do begin
    //FBrushList.Brush[b]^.Image := TTexture(ResMan.Texmaps.Block[t]).Graphic;
    FBrushList.Brush[b]^.Image := TSingleImage.CreateFromParams(96, 96, ifA1R5G5B5);

    //FBrushList.Brush[b]^.Image.Bits .Canvas.Brush.Color := $00FF9000;//DecodeUOColor(destColor);
    //FBrushList.Brush[b]^.Image.Canvas.Clear;

    //if FBrushList.Brush[b]^.Image.FormatInfo.BytesPerPixel = 2
    // then FillWord(FBrushList.Brush[b]^.Image.Bits^,
    //        FBrushList.Brush[b]^.Image.Width * FBrushList.Brush[b]^.Image.Height,
    //        $0000)
    // else FillDWord(FBrushList.Brush[b]^.Image.Bits^,
    //        FBrushList.Brush[b]^.Image.Width * FBrushList.Brush[b]^.Image.Height,
    //        $00000000);

    t := ResMan.Tiledata.LandTiles[FBrushList.Brush[b]^.BTile[0]^.ID].TextureID;
    texmEntry := TTexture(ResMan.Texmaps.Block[t]);
    //texmEntry.Graphic.CopyTo(0, 0, texmEntry.Graphic.Width, texmEntry.Graphic.Height,
    //        FBrushList.Brush[b]^.Image, 0, 0);
    texmEntry.Graphic.StretchTo(0, 0, texmEntry.Graphic.Width, texmEntry.Graphic.Height,
            FBrushList.Brush[b]^.Image, 2, 2,
            FBrushList.Brush[b]^.Image.Width-4, FBrushList.Brush[b]^.Image.Height-4,
            rfNearest);
    texmEntry.Free;
  end;

  {  // Отладка - проверка считанного файла
  Logger.EnterMethod([lcInfo, lcDebug], 'TfrmMain.LoadBrushTilesList');
  Logger.Send([lcInfo], 'Brushes Number: %x', [FBrushList.Count]);
  for b := 0 to FBrushList.Count - 1 do begin
    Logger.Send([lcInfo], '<Brush Id="%.4d" Name="%s" Tiles="%d" Edges="%d">',
      [FBrushList.Brush[b]^.ID, FBrushList.Brush[b]^.Name, FBrushList.Brush[b]^.Count, FBrushList.Brush[b]^.ECount]);
    for t := 0 to FBrushList.Brush[b]^.Count - 1 do
      Logger.Send([lcInfo], '    <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BTile[t]^.ID, FBrushList.Brush[b]^.BTile[t]^.Chance, FBrushList.Brush[b]^.BTile[t]^.Mask]);
    for e := 0 to FBrushList.Brush[b]^.ECount - 1 do begin
      Logger.Send([lcInfo], '    <Edge To="%d" Tiles="%d">',
        [FBrushList.Brush[b]^.EdgeId[e]^, FBrushList.Brush[b]^.BEdges[e]^.CountUU+FBrushList.Brush[b]^.BEdges[e]^.CountUR+FBrushList.Brush[b]^.BEdges[e]^.CountLL+FBrushList.Brush[b]^.BEdges[e]^.CountUL+FBrushList.Brush[b]^.BEdges[e]^.CountDL+FBrushList.Brush[b]^.BEdges[e]^.CountDR]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountUU - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileUU[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileUU[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileUU[t]^.Mask]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountUR - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileUR[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileUR[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileUR[t]^.Mask]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountLL - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileLL[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileLL[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileLL[t]^.Mask]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountUL - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileUL[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileUL[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileUL[t]^.Mask]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountDL - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileDL[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileDL[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileDL[t]^.Mask]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountDR - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileDR[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileDR[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileDR[t]^.Mask]);
    Logger.Send([lcInfo], '    </Edge>');
    end;
    Logger.Send([lcInfo], '</Brush>');
  end;
  Logger.ExitMethod([lcInfo, lcDebug], 'TfrmMain.LoadBrushTilesList');
  }
end;

procedure TfrmMain.BuildGroupList;
var
  XMLDoc: TXMLDocument;
  iNode : TDOMNode;
  fPath : string;
  vNode : PVirtualNode;
  bTemp : array[0..$E000] of Word;
  gTemp : array[0..$FFFF] of Boolean;

  procedure SumItems(Node: PVirtualNode; Count: Integer; var Items: LongWord);
  var
    i    : Integer;
    item : PVirtualNode;
    group: PGroupNode;
  begin
    group := tvGroups.GetNodeData(Node);
    Items += group^.Count;
    Items += group^.Entries;
    Items += group^.Brushes;

    for i := 0 to group^.Links - 1 do
      if group^.GLink[i] <> nil then
        SumItems(group^.GLink[i], 0, Items);

    if (Count > 0) then
    begin
      item := tvGroups.GetFirstChild(Node);
      while item <> nil do
      begin
        SumItems(item, Count - 1, Items);
        item := tvGroups.GetNextSibling(item);
      end;
    end;
  end;

  procedure ProcessNode(Node: TDOMNode; TreeNode: PVirtualNode);
  var
    element: TDOMElement;
    cNode: TDOMNode;
    s: string;
    i, j, k, e, b, d, q, id: Integer;
    group: PGroupNode;
  begin
    if Node = nil then Exit; // выходим, если достигнут конец документа
    if LowerCase(Node.NodeName) <> 'group' then Exit;

    // добавляем узел в дерево
    TreeNode := tvGroups.AddChild(TreeNode);
    group := tvGroups.GetNodeData(TreeNode);

    // сохраняем данные узла
    e := 0;  b := 0;  d := 0;
    group^.Count := 0;
    cNode := Node.FirstChild;
    while cNode <> nil do
    begin
      s := LowerCase(cNode.NodeName);
      if (s = 'tile') or (s = 'land') or (s = 'item')
         then Inc(group^.Count, 1)
         else if (s = 'entry') then Inc(e, 1)
         else if (s = 'brush') then Inc(b, 1)
         else if (s = 'link')  then // Inc(d, 1);
           for i := 0 to cNode.Attributes.Length - 1 do
             if LowerCase(cNode.Attributes[i].NodeName) = 'groupid' then Inc(d, 1)
             else if LowerCase(cNode.Attributes[i].NodeName) = 'brushid' then
               if TryStrToInt(cNode.Attributes[i].NodeValue, id) then begin
                 for j := 0 to Length(FBrushList.Tiles) - 1 do
                   if (FBrushList.Tiles[j].ID <> $FFFF) and
                      ((id = FBrushList.Tiles[j].Brush1^.ID) or (id = FBrushList.Tiles[j].Brush2^.ID))
                   then Inc(group^.Count, 1);
               end;
      cNode := cNode.NextSibling;
    end;

    group^.Entries := e;  group^.Brushes := b;  group^.Links := d;
    k := 0; e := 0; b := 0; d := 0;
    getmem(group^.lids,  group^.Links * SizeOf(LongWord));
    getmem(group^.GLink, group^.Links * SizeOf(PVirtualnode));
    getmem(group^.GTile, group^.Count * SizeOf(TGroupTile));
    getmem(group^.Entry, group^.Entries * SizeOf(PGroupEntry));
    getmem(group^.Brush, group^.Brushes * SizeOf(PGroupBrush));
    cNode := Node.FirstChild;
    while cNode <> nil do
    begin
      s := LowerCase(cNode.NodeName);
      if (s = 'tile') or (s = 'land') or (s = 'item') then
      begin
        for i := cNode.Attributes.Length - 1 downto 0 do
        begin
          if LowerCase(cNode.Attributes[i].NodeName) = 'id' then
            if TryStrToInt(cNode.Attributes[i].NodeValue, id) then
            begin
              if s = 'item' then
                Inc(id, $4000);
              group^.GTile[k].ID := id;
              Inc(k, 1);
            end;
        end;
      end
      else if (s = 'entry') then
      begin
        for i := cNode.Attributes.Length - 1 downto 0 do
        begin
          if LowerCase(cNode.Attributes[i].NodeName) = 'id' then
            if TryStrToInt(cNode.Attributes[i].NodeValue, id) then
            begin
              group^.Entry[e] := nil;
              for j := 0 to FEntryList.Count - 1 do
                if FEntryList.Entry[j]^.ID = id then begin
                  group^.Entry[e] := FEntryList.Entry[j];
                  break;
                end;
              Inc(e, 1);
            end;
        end;
      end
      else if (s = 'brush') then
      begin
        for i := cNode.Attributes.Length - 1 downto 0 do
        begin
          if LowerCase(cNode.Attributes[i].NodeName) = 'id' then
            if TryStrToInt(cNode.Attributes[i].NodeValue, id) then
            begin
              group^.Brush[b] := nil;
              for j := 0 to FBrushList.Count - 1 do
                if FBrushList.Brush[j]^.ID = id then begin
                  group^.Brush[b] := FBrushList.Brush[j];
                  break;
                end;
              Inc(b, 1);
            end;
        end;
      end
      else if (s = 'link') then
      begin
        for i := 0 to cNode.Attributes.Length - 1 do
        begin
          if LowerCase(cNode.Attributes[i].NodeName) = 'groupid' then
            if TryStrToInt(cNode.Attributes[i].NodeValue, id) then
            begin
              group^.lids[d] := id;
              Inc(d, 1);
            end;
          if LowerCase(cNode.Attributes[i].NodeName) = 'brushid' then  //
            if TryStrToInt(cNode.Attributes[i].NodeValue, id) then
            begin
               bTemp[$0000]:=$0000; bTemp[$D000]:=$D000;
               bTemp[$1000]:=$1000; bTemp[$2000]:=$2000; bTemp[$3000]:=$3000; bTemp[$4000]:=$4000;
               bTemp[$5000]:=$5000; bTemp[$6000]:=$6000; bTemp[$7000]:=$7000; bTemp[$8000]:=$8000;
               bTemp[$9000]:=$9000; bTemp[$A000]:=$A000; bTemp[$B000]:=$B000; bTemp[$C000]:=$C000;
               for j := 0 to Length(gTemp)-1 do gTemp[j] := False;
               for j := 0 to Length(FBrushList.Tiles) - 1 do begin
                 if (FBrushList.Tiles[j].ID = $FFFF) then Continue;
                 if (id = FBrushList.Tiles[j].Brush1^.ID) and (id = FBrushList.Tiles[j].Brush2^.ID) then begin
                   inc(bTemp[$0000], 1); bTemp[bTemp[$0000]] := j; Continue;
                 end;

                 if (FBrushList.Tiles[j].Mask = $0B) and (id = FBrushList.Tiles[j].Brush2^.ID) then begin
                   inc(bTemp[$1000],1); bTemp[bTemp[$1000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush1^.ID]:=True;
                   bTemp[bTemp[$1000]+$0800]:=FBrushList.Tiles[j].Brush1^.ID;
                   Continue;
                 end;
                 if (FBrushList.Tiles[j].Mask = $0D) and (id = FBrushList.Tiles[j].Brush2^.ID) then begin
                   inc(bTemp[$2000],1); bTemp[bTemp[$2000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush1^.ID]:=True;
                   bTemp[bTemp[$2000]+$0800]:=FBrushList.Tiles[j].Brush1^.ID;
                   Continue;
                 end;
                 if (FBrushList.Tiles[j].Mask = $07) and (id = FBrushList.Tiles[j].Brush2^.ID) then begin
                   inc(bTemp[$3000],1); bTemp[bTemp[$3000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush1^.ID]:=True;
                   bTemp[bTemp[$3000]+$0800]:=FBrushList.Tiles[j].Brush1^.ID;
                   Continue;
                 end;
                 if (FBrushList.Tiles[j].Mask = $0E) and (id = FBrushList.Tiles[j].Brush2^.ID) then begin
                   inc(bTemp[$4000],1); bTemp[bTemp[$4000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush1^.ID]:=True;
                   bTemp[bTemp[$4000]+$0800]:=FBrushList.Tiles[j].Brush1^.ID;
                   Continue;
                 end;

                 if (FBrushList.Tiles[j].Mask = $09) and (id = FBrushList.Tiles[j].Brush2^.ID) then begin
                   inc(bTemp[$5000],1); bTemp[bTemp[$5000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush1^.ID]:=True;
                   bTemp[bTemp[$5000]+$0800]:=FBrushList.Tiles[j].Brush1^.ID;
                   Continue;
                 end;
                 if (FBrushList.Tiles[j].Mask = $03) and (id = FBrushList.Tiles[j].Brush2^.ID) then begin
                   inc(bTemp[$6000],1); bTemp[bTemp[$6000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush1^.ID]:=True;
                   bTemp[bTemp[$6000]+$0800]:=FBrushList.Tiles[j].Brush1^.ID;
                   Continue;
                 end;
                 if (FBrushList.Tiles[j].Mask = $03) and (id = FBrushList.Tiles[j].Brush1^.ID) then begin
                   inc(bTemp[$7000],1); bTemp[bTemp[$7000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush2^.ID]:=True;
                   bTemp[bTemp[$7000]+$0800]:=FBrushList.Tiles[j].Brush2^.ID;
                   Continue;
                 end;
                 if (FBrushList.Tiles[j].Mask = $09) and (id = FBrushList.Tiles[j].Brush1^.ID) then begin
                   inc(bTemp[$8000],1); bTemp[bTemp[$8000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush2^.ID]:=True;
                   bTemp[bTemp[$8000]+$0800]:=FBrushList.Tiles[j].Brush2^.ID;
                   Continue;
                 end;

                 if (FBrushList.Tiles[j].Mask = $0E) and (id = FBrushList.Tiles[j].Brush1^.ID) then begin
                   inc(bTemp[$9000],1); bTemp[bTemp[$9000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush2^.ID]:=True;
                   bTemp[bTemp[$9000]+$0800]:=FBrushList.Tiles[j].Brush2^.ID;
                   Continue;
                 end;
                 if (FBrushList.Tiles[j].Mask = $0D) and (id = FBrushList.Tiles[j].Brush1^.ID) then begin
                   inc(bTemp[$A000],1); bTemp[bTemp[$A000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush2^.ID]:=True;
                   bTemp[bTemp[$A000]+$0800]:=FBrushList.Tiles[j].Brush2^.ID;
                   Continue;
                 end;
                 if (FBrushList.Tiles[j].Mask = $07) and (id = FBrushList.Tiles[j].Brush1^.ID) then begin
                   inc(bTemp[$B000],1); bTemp[bTemp[$B000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush2^.ID]:=True;
                   bTemp[bTemp[$B000]+$0800]:=FBrushList.Tiles[j].Brush2^.ID;
                   Continue;
                 end;
                 if (FBrushList.Tiles[j].Mask = $0B) and (id = FBrushList.Tiles[j].Brush1^.ID) then begin
                   inc(bTemp[$C000],1); bTemp[bTemp[$C000]]:=j;
                   gTemp[FBrushList.Tiles[j].Brush2^.ID]:=True;
                   bTemp[bTemp[$C000]+$0800]:=FBrushList.Tiles[j].Brush2^.ID;
                   Continue;
                 end;

                 if (id = FBrushList.Tiles[j].Brush1^.ID) or (id = FBrushList.Tiles[j].Brush2^.ID) then begin
                   inc(bTemp[$D000],1);     // то что не удалось расортировать (вообще мы никогда не должны сюда попадать)
                   bTemp[bTemp[$D000]] := j;
                 end;

               end;

               for j := $0001 to bTemp[$0000] do begin
                 group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
               end;

               for q := 0 to Length(gTemp)-1 do
                 if gTemp[q] then begin
                   for j := $1001 to bTemp[$1000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $2001 to bTemp[$2000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $3001 to bTemp[$3000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $4001 to bTemp[$4000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $5001 to bTemp[$5000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $6001 to bTemp[$6000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $7001 to bTemp[$7000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $8001 to bTemp[$8000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $9001 to bTemp[$9000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $A001 to bTemp[$A000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $B001 to bTemp[$B000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                   for j := $C001 to bTemp[$C000] do if (bTemp[j+$0800]=q) then begin
                     group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
                   end;
                 end;

               for j := $D001 to bTemp[$D000] do begin
                 group^.GTile[k].ID := bTemp[j]; Inc(k, 1);
               end;

            end;
        end;
      end;
      cNode := cNode.NextSibling;
    end;

    // переходим к дочернему узлу
    cNode := Node.FirstChild;

    // проходим по всем дочерним узлам
    while cNode <> nil do
    begin
      ProcessNode(cNode, TreeNode);
      cNode := cNode.NextSibling;
    end;

    // сохраняем данные узла
    k:= 0;
    group^.ID   := 0;
    group^.Nodes:= 0;
    group^.Name := 'NoName';
    group^.Color:= TColor($00000000);
    if tvGroups.ChildCount[TreeNode] = 0
       then group^.Ital := True
       else group^.Ital := False;
    if tvGroups.ChildCount[TreeNode] > 4
       then group^.Bold := True
       else group^.Bold := False;
    if Node.HasAttributes and (Node.Attributes.Length>0) then
      for i := 0 to Node.Attributes.Length - 1 do
        if LowerCase(Node.Attributes[i].NodeName) = 'name' then begin
          group^.Name := CP1251ToUTF8(Node.Attributes[i].NodeValue)
        end else if LowerCase(Node.Attributes[i].NodeName) = 'color'  then begin
          if TryStrToInt(Node.Attributes[i].NodeValue, k) then
          group^.Color:=TColor(((k and$0000FF)shl 16)or(k and$00FF00)or((k and$FF0000)shr 16));
        end else if LowerCase(Node.Attributes[i].NodeName) = 'bold'   then begin
          TryStrToBool(Node.Attributes[i].NodeValue, group^.Bold);
        end else if LowerCase(Node.Attributes[i].NodeName) = 'ital'   then begin
          TryStrToBool(Node.Attributes[i].NodeValue, group^.Ital);
        end else if LowerCase(Node.Attributes[i].NodeName) = 'id'     then begin
          if TryStrToInt(Node.Attributes[i].NodeValue, d) then group^.ID := d;
        end else if LowerCase(Node.Attributes[i].NodeName) = 'nodes'  then begin
          if TryStrToInt(Node.Attributes[i].NodeValue, k) then
            begin if k=-1 then k:=$FFFF; group^.Nodes := Max(0, k); k:=0; end;
        end;
  end;

  procedure BuildLinks(Node: PVirtualNode);
  var
    i    : Integer;
    item : PVirtualNode;
    group: PGroupNode;
  begin
    group := tvGroups.GetNodeData(Node);

    for i := 0 to group^.Links - 1 do
    begin
      //Logger.Send([lcClient, lcDebug], 'BuildLinks: %.4d / %.4d - id: %d', [i+1, group^.Links, group^.lids[i]]);
      group^.GLink[i] := nil;
      if group^.lids[i] = 0 then continue;

      item := tvGroups.GetFirst();
      while item <> nil do
      begin
        if group^.lids[i] = PGroupNode(tvGroups.GetNodeData(item))^.ID then
        begin
          //Logger.Send([lcClient, lcDebug], 'BuildLinks: Group id: %d Found', [group^.lids[i]]);
          group^.GLink[i] := item;
          break;
        end;
        item := tvGroups.GetNext(item);
      end
    end;

    FreeMem(group^.lids);
    group^.lids := nil;
  end;

begin
  tvGroups.BeginUpdate;
  tvGroups.Clear;

  frmInitialize.SetStatusLabel(Format(frmInitialize.SplashLoading, ['TilesGroup.xml']));
  if FileExists(FProfileDir + 'TilesGroup.xml')
    then fPath := (FProfileDir + 'TilesGroup.xml')
    else if FileExists(FLocalDir + 'TilesGroup.xml')
      then fPath := (FLocalDir + 'TilesGroup.xml')
      else if FileExists(FConfigDir + 'TilesGroup.xml')
        then fPath := (FConfigDir + 'TilesGroup.xml')
        else if FileExists(ResMan.GetFile('TilesGroup.xml'))
          then fPath := (ResMan.GetFile('TilesGroup.xml'))
          else Exit;

  // Читаем xml файл с жесткого диска
  ReadXMLFile(XMLDoc, fPath);
  if LowerCase(XMLDoc.DocumentElement.NodeName) = 'tilesgroup' then
  begin
    iNode := XMLDoc.DocumentElement.FirstChild;
    while iNode <> nil do
    begin
      if LowerCase(iNode.NodeName) = 'group' then
        ProcessNode(iNode, nil); // Рекурсия
      iNode := iNode.NextSibling;
    end;

    // Построение Указателей
    vNode := tvGroups.GetFirst();
    while vNode <> nil do
    begin
      BuildLinks(vNode);
      vNode := tvGroups.GetNext(vNode);
    end;
    // Подсчет Items
    vNode := tvGroups.GetFirst();
    while vNode <> nil do
    begin
      SumItems(vNode, PGroupNode(tvGroups.GetNodeData(vNode))^.Nodes,
                      PGroupNode(tvGroups.GetNodeData(vNode))^.Items);
      vNode := tvGroups.GetNext(vNode);
    end;
  end;

  XMLDoc.Free;
  tvGroups.EndUpdate;
end;

procedure TfrmMain.FreeGroupLists;
var i, j, k : Integer;
begin
  for i:=0 to FEntryList.Count-1 do begin
    FreeMem(FEntryList.Entry[i]^.ETile);
    FEntryList.Entry[i]^.Image.Destroy;
  end;
  FreeMem(FEntryList.Entry);
  FEntryList.Count:=0;

  for i:=0 to FBrushList.Count-1 do begin
    //for j:=0 to FBrushList.Brush[i]^.Count-1 do begin
    //  FreeMem(FBrushList.Brush[i]^.BTile[j]);
    //end;
    FreeMem(FBrushList.Brush[i]^.BEdges);
    FreeMem(FBrushList.Brush[i]^.BTile);
    FBrushList.Brush[i]^.Image.Destroy;
    FBrushList.Count:=0;
  end;

  for i:=0 to $3FFF do begin
    FBrushList.Tiles[i].ID := 0;
    FBrushList.Tiles[i].Brush1:=nil;
    FBrushList.Tiles[i].Brush2:=nil;
  end;

end;

procedure TfrmMain.BuildTileList;
var
  minID, maxID, i, k, lastID: Integer;
  item : PVirtualItem;
  groupNode : PVirtualNode;
  tileInfo: PTileInfo;
  filter  : string;
  nodeData: PGroupNode;

  procedure AddNodeTiles(Node: PVirtualNode; Count: Integer);
  var
    item: PVirtualItem;
    groupNode: PVirtualNode;
    nodeData: PGroupNode;
    tileInfo: PTileInfo;
    lastID: LongWord;
    i, j: Integer;

    //b,t,e: Integer;
  begin
    nodeData := tvGroups.GetNodeData(Node);
    Logger.Send([lcInfo], 'Brushes: %d - Entries: %d - Tiles: %d', [nodeData^.Brushes, nodeData^.Entries, nodeData^.Count]);
  {
  Logger.EnterMethod([lcInfo, lcDebug], 'TfrmMain.AddNodeTiles-Entries');
  Logger.Send([lcInfo], 'Entries Number: %d', [FEntryList.Count]);
  for e := 0 to FEntryList.Count - 1 do begin
    Logger.Send([lcInfo], '<Entry Id="%.4d" Name="%s" Tiles="%x">',
      [FEntryList.Entry[e]^.ID, FEntryList.Entry[e]^.Name, FEntryList.Entry[e]^.Count]);
    for t := 0 to FEntryList.Entry[e]^.Count - 1 do
      Logger.Send([lcInfo], '    <Tile Id="0x%.5x" Hue="0x%.3x" X="%d" Y="%d" Z="%d">',
        [FEntryList.Entry[e]^.ETile[t].ID, FEntryList.Entry[e]^.ETile[t].Hue,
        FEntryList.Entry[e]^.ETile[t].X, FEntryList.Entry[e]^.ETile[t].Y, FEntryList.Entry[e]^.ETile[t].Z]);
    Logger.Send([lcInfo], '</Entry>');
  end;
  Logger.ExitMethod([lcInfo, lcDebug], 'TfrmMain.AddNodeTiles-Entries');
  Logger.EnterMethod([lcInfo, lcDebug], 'TfrmMain.AddNodeTiles-Brushes');
  Logger.Send([lcInfo], 'Brushes Number: %x', [FBrushList.Count]);
  for b := 0 to FBrushList.Count - 1 do begin
    Logger.Send([lcInfo], '<Brush Id="%.4d" Name="%s" Tiles="%d" Edges="%d">',
      [FBrushList.Brush[b]^.ID, FBrushList.Brush[b]^.Name, FBrushList.Brush[b]^.Count, FBrushList.Brush[b]^.ECount]);
    for t := 0 to FBrushList.Brush[b]^.Count - 1 do
      Logger.Send([lcInfo], '    <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BTile[t]^.ID, FBrushList.Brush[b]^.BTile[t]^.Chance, FBrushList.Brush[b]^.BTile[t]^.Mask]);
    for e := 0 to FBrushList.Brush[b]^.ECount - 1 do begin
      Logger.Send([lcInfo], '    <Edge To="%d" Tiles="%d">',
        [FBrushList.Brush[b]^.EdgeId[e]^, FBrushList.Brush[b]^.BEdges[e]^.CountUU+FBrushList.Brush[b]^.BEdges[e]^.CountUR+FBrushList.Brush[b]^.BEdges[e]^.CountLL+FBrushList.Brush[b]^.BEdges[e]^.CountUL+FBrushList.Brush[b]^.BEdges[e]^.CountDL+FBrushList.Brush[b]^.BEdges[e]^.CountDR]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountUU - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileUU[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileUU[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileUU[t]^.Mask]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountUR - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileUR[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileUR[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileUR[t]^.Mask]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountLL - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileLL[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileLL[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileLL[t]^.Mask]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountUL - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileUL[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileUL[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileUL[t]^.Mask]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountDL - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileDL[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileDL[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileDL[t]^.Mask]);
    for t := 0 to FBrushList.Brush[b]^.BEdges[e]^.CountDR - 1 do
      Logger.Send([lcInfo], '        <Land Id="0x%.4x" Chance="%.2f" Mask="0x%.2x">',
        [FBrushList.Brush[b]^.BEdges[e]^.BTileDR[t]^.ID, FBrushList.Brush[b]^.BEdges[e]^.BTileDR[t]^.Chance, FBrushList.Brush[b]^.BEdges[e]^.BTileDR[t]^.Mask]);
    Logger.Send([lcInfo], '    </Edge>');
    end;
    Logger.Send([lcInfo], '</Brush>');
  end;
  Logger.ExitMethod([lcInfo, lcDebug], 'TfrmMain.AddNodeTiles-Brushes');
  }

    for i := 0 to nodeData^.Count - 1 do
    begin
      lastID := nodeData^.GTile[i].ID;
      if FVisibleTiles[lastID] and ResMan.Art.Exists(lastID) then
      begin
        if (filter <> '') and (Pos(filter, AnsiLowerCase(
          ResMan.Tiledata.TileData[lastID].TileName)) = 0) then Continue;
        item := vdtTiles.AddItem(nil);
        tileInfo := vdtTiles.GetNodeData(item);
        tileInfo^.ID := lastID;
      end;
    end;

    for i := 0 to nodeData^.Brushes - 1 do
    begin
      if nodeData^.Brush[i] = nil then Continue;
      lastID := $2F000000 + LongWord(nodeData^.Brush[i]^.ID);
      //if FVisibleTiles[lastID] and ResMan.Art.Exists(lastID) then
      begin
        if (filter <> '') and (Pos(filter, AnsiLowerCase(
            nodeData^.Brush[i]^.Name)) = 0) then Continue;
        item := vdtTiles.AddItem(nil);
        tileInfo := vdtTiles.GetNodeData(item);
        tileInfo^.ID := lastID;
        tileInfo^.ptr:= nodeData^.Brush[i];
      end;
    end;

    for i := 0 to nodeData^.Entries - 1 do
    begin
      if nodeData^.Entry[i] = nil then Continue;
      lastID := $1F000000 + LongWord(nodeData^.Entry[i]^.ID);
      //if FVisibleTiles[lastID] and ResMan.Art.Exists(lastID) then
      begin
        if (filter <> '') and (Pos(filter, AnsiLowerCase(
            nodeData^.Entry[i]^.Name)) = 0) then Continue;
        item := vdtTiles.AddItem(nil);
        tileInfo := vdtTiles.GetNodeData(item);
        tileInfo^.ID := lastID;
        tileInfo^.ptr:= nodeData^.Entry[i];
      end;
    end;

    // Добавление сылок групп
    for i := 0 to nodeData^.Links - 1 do
      if nodeData^.GLink[i] <> nil then
        AddNodeTiles(nodeData^.GLink[i], 0);

    // Добавление вложенных подгрупп
    if (Count > 0) then
    begin
      groupNode := tvGroups.GetFirstChild(Node);
      while groupNode <> nil do
      begin
        AddNodeTiles(groupNode, Count - 1);
        groupNode := tvGroups.GetNextSibling(groupNode);
      end;
    end;

  end;

begin
  filter := AnsiLowerCase(UTF8ToCP1251(edFilter.Text));

  Logger.Send([lcInfo], 'TfrmMain.BuildTileList: %s', ['Start']);
  // Сортировка по группам
  if (not cbStatics.Checked) and (not cbTerrain.Checked) then
  begin
    vdtTiles.BeginUpdate;
    vdtTiles.Clear;
    groupNode := tvGroups.GetFirstSelected();
    while groupNode <> nil do
    begin
      nodeData := tvGroups.GetNodeData(groupNode);
      AddNodeTiles(groupNode, nodeData^.Nodes);
      groupNode := tvGroups.GetNextSelected(groupNode);
    end;
    vdtTiles.EndUpdate;
  end else
  // Старое построение - список всех land и\или item тайлов
  begin
    Logger.Send([lcInfo], 'TfrmMain.BuildTileList: start');
    maxID := $3FFF;
    if cbTerrain.Checked then minID := $0 else minID := $4000;
    if cbStatics.Checked then maxID := maxID + FLandscape.MaxStaticID;

    item := vdtTiles.GetFirstSelected;
    if item <> nil then
    begin
      tileInfo := vdtTiles.GetNodeData(item);
      lastID := tileInfo^.ID;
    end else
      lastID := -1;
  
    vdtTiles.BeginUpdate;
    vdtTiles.Clear;

    Logger.Send([lcInfo], 'TfrmMain.BuildTileList: from %.4x to %.4x', [minID, maxID]);
    for i := minID to maxID do
    begin
      if FVisibleTiles[i] and ResMan.Art.Exists(i) then
      begin
        if (filter <> '') and (Pos(filter, AnsiLowerCase(
          ResMan.Tiledata.TileData[i].TileName)) = 0) then Continue;
        item := vdtTiles.AddItem(nil);
        tileInfo := vdtTiles.GetNodeData(item);
        tileInfo^.ID := i;

        //Logger.Send([lcInfo], 'TfrmMain.BuildTileList: tileInfo %.4x == %.4x', [i, PTileInfo(vdtTiles.GetNodeData(vdtTiles.GetFirst()))^.ID]);

        if i = lastID then
          vdtTiles.Selected[item] := True;
      end;
    end;
  
    vdtTiles.EndUpdate;
  
    item := vdtTiles.GetFirstSelected;
    if item <> nil then
      vdtTiles.FocusedNode := item;
  end;

  Logger.Send([lcInfo], 'TfrmMain.BuildTileList: %s', ['End']);
end;

procedure TfrmMain.ProcessToolState;
var
  blockInfo: PBlockInfo;
begin
  if acSelect.Checked then
  begin
    //lblTip.Caption := 'Нажатие правой кнопки мышки покажет меню со всеми инструментами.';
    //lblTip.Caption := 'Нажмите и удерживайте левую кнопку мышки, чтобы просмотреть список действий.';
    //'Press and hold the left mouse button to show a list with actions (eg. grab hue).';
    oglGameWindow.Cursor := +01;//crDefault;

    //no highlighted tiles in "selection" mode
    Logger.Send([lcClient, lcDebug], 'Disable highlighting');
    blockInfo := nil;
    while FScreenBuffer.Iterate(blockInfo) do
      if blockInfo^.State = ssNormal then
        blockInfo^.Highlighted := False;
  end else
  begin
    //lblTip.Caption := 'Нажмите и удерживайте левую кнопку мышки, чтобы выбрать область.';
    //'Press and hold the left mouse button to target an area.';
    oglGameWindow.Cursor := +03;//crHandPoint;
  end;

  FRepaintNeeded := True;
end;

procedure TfrmMain.ProcessAccessLevel;
begin
  mnuAdministration.Visible := (dmNetwork.AccessLevel >= alDeveloper);
  mnuShutdown.Visible := (dmNetwork.AccessLevel >= alAdministrator);
  mnuAccountControl.Visible := (dmNetwork.AccessLevel >= alAdministrator);
  mnuRegionControl.Visible := (dmNetwork.AccessLevel >= alAdministrator);
  acSelection.Enabled := (dmNetwork.AccessLevel >= alNormal);
  acDraw.Enabled := (dmNetwork.AccessLevel >= alNormal);
  acMove.Enabled := (dmNetwork.AccessLevel >= alNormal);
  acElevate.Enabled := (dmNetwork.AccessLevel >= alNormal);
  acSurfElevate.Enabled := (dmNetwork.AccessLevel >= alNormal);
  acSurfStretch.Enabled := (dmNetwork.AccessLevel >= alNormal);
  acSurfSmooth.Enabled := (dmNetwork.AccessLevel >= alNormal);
  acDelete.Enabled := (dmNetwork.AccessLevel >= alNormal);
  acHue.Enabled := (dmNetwork.AccessLevel >= alNormal);
  acFill.Enabled := (dmNetwork.AccessLevel >= alNormal);
  Caption := Format('UO CentrED+ v%s    [%s "%s" (%s)  | %s "%s"]',
  //Caption := Format('UO CentrED+ v%s !!! pre-release (not stable version) !!!    [%s "%s" (%s)  | %s "%s"]',
    [VersionInfo.GetProductVersionString, lbFormTitleAccount, dmNetwork.Username,
    GetAccessLevel(dmNetwork.AccessLevel), lbFormTitleProfile, dmNetwork.Profile]);
end;

procedure TfrmMain.RebuildScreenBuffer;
var
  blockInfo: PBlockInfo;
  i, tileX, tileY: Integer;
  virtualTile: TVirtualTile;
  zoom: Single;
begin
  //Logger.EnterMethod([lcClient], 'RebuildScreenBuffer');
  if tbZoom.Down then zoom:= tbZoom.Tag / 1000.0 else zoom:= 1.0;

  FDrawDistance := Trunc(Sqrt(oglGameWindow.Width * oglGameWindow.Width +
    oglGamewindow.Height * oglGamewindow.Height) / (44 * zoom));
  //Logger.Send([lcClient], 'DrawDistance', FDrawDistance);

  {$HINTS off}{$WARNINGS off}
  if FX - FDrawDistance < 0 then FLowOffsetX := -FX else FLowOffsetX := -FDrawDistance;
  if FY - FDrawDistance < 0 then FLowOffsetY := -FY else FLowOffsetY := -FDrawDistance;
  if FX + FDrawDistance >= FLandscape.Width * 8 then FHighOffsetX := FLandscape.Width * 8 - FX - 1 else FHighOffsetX := FDrawDistance;
  if FY + FDrawDistance >= FLandscape.Height * 8 then FHighOffsetY := FLandscape.Height * 8 - FY - 1 else FHighOffsetY := FDrawDistance;
  {$HINTS on}{$WARNINGS on}

  FRangeX := FHighOffsetX - FLowOffsetX;
  FRangeY := FHighOffsetY - FLowOffsetY;

  FLandscape.PrepareBlocks((FX + FLowOffsetX) div 8, (FY + FLowOffsetY) div 8,
    (FX + FHighOffsetX) div 8 + 1, (FY + FHighOffsetY) div 8 + 1);

  if frmVirtualLayer.cbShowLayer.Checked then
  begin
    //Logger.Send([lcClient, lcDebug], 'Preparing Virtual Layer');

    if FVLayerMaterial = nil then
      FVLayerMaterial := TSimpleMaterial.Create(FVLayerImage);

    i := 0;
    for tileX := FX + FLowOffsetX to FX + FHighOffsetX do
    begin
      for tileY := FY + FLowOffsetY to FY + FHighOffsetY do
      begin
        while (i < FVirtualTiles.Count) and (not (FVirtualTiles[i] is TVirtualTile)) do
          Inc(i);

        if i < FVirtualTiles.Count then
        begin
          virtualTile := TVirtualTile(FVirtualTiles[i]);
        end else
        begin
          virtualTile := TVirtualTile.Create(nil);
          FVirtualTiles.Add(virtualTile);
        end;

        virtualTile.X := tileX;
        virtualTile.Y := tileY;
        virtualTile.Z := frmVirtualLayer.seZ.Value;
        virtualTile.Priority := virtualTile.Z;
        virtualTile.PriorityBonus := High(ShortInt);

        Inc(i);
      end;
    end;
    while i < FVirtualTiles.Count do
    begin
      if FVirtualTiles[i] is TVirtualTile then
        FVirtualTiles.Delete(i)
      else
        Inc(i);
    end;
  end else
  begin
    for i := FVirtualTiles.Count - 1 downto 0 do
      if FVirtualTiles[i] is TVirtualTile then
        FVirtualTiles.Delete(i);
  end;

  if acNoDraw.Checked and mnuShowLightSource.Checked then
  begin
    if FVLightSrcMaterial = nil then begin
      getmem(FVLightSrcMaterial, FVLightSrcImageCount * SizeOf(TSimpleMaterial));
      for i := 1 to FVLightSrcImageCount do
          FVLightSrcMaterial[i-1] := TSimpleMaterial.Create(FVLightSrcImage[i]);
    end;
  end;

  //Logger.Send([lcClient, lcDebug], 'VirtualTiles', FVirtualTiles.Count);

  FLandscape.FillDrawList(FScreenBuffer, FX + FLowOffsetX, FY + FLowOffsetY,
    FRangeX, FRangeY, acTerrain.Checked, acStatics.Checked, mnuShowWalls.Checked,
    mnuShowBridges.Checked, mnuShowRoofs.Checked, mnuShowSurfaces.Checked, mnuShowFoliage.Checked,
    mnuShowWater.Checked, acNoDraw.Checked and mnuShowNoDrawTiles.Checked, FVirtualTiles);

  //Pre-process the buffer
  blockInfo := nil;
  while FScreenBuffer.Iterate(blockInfo) do
    PrepareScreenBlock(blockInfo);

  FScreenBuffer.UpdateShortcuts;
  FScreenBufferState := [sbsValid, sbsIndexed];

  //Logger.ExitMethod([lcClient], 'RebuildScreenBuffer');
end;

procedure TfrmMain.UpdateCurrentTile;
var
  localPos: TPoint;
begin
  if oglGameWindow.MouseEntered then
  begin
    localPos := oglGameWindow.ScreenToClient(Mouse.CursorPos);
    UpdateCurrentTile(localPos.X, localPos.Y);
  end;
end;

procedure TfrmMain.UpdateCurrentTile(AX, AY: Integer);
var
  blockInfo: PBlockInfo;
  zoom: Single;
begin
  //Logger.EnterMethod([lcClient, lcDebug], 'UpdateCurrentTile');
  FOverlayUI.ActiveArrow := FOverlayUI.HitTest(AX, AY);
  if FOverlayUI.ActiveArrow > -1 then
  begin
    //Logger.Send([lcClient, lcDebug], 'Overlay active');
    CurrentTile := nil;
    //Logger.ExitMethod([lcClient, lcDebug], 'UpdateCurrentTile');
    Exit;
  end;

  if tbZoom.Down then zoom := tbZoom.Tag / 1000.0 else zoom := 1.0;

  blockInfo := FScreenBuffer.Find(Point(AX, AY), zoom);
  if blockInfo <> nil then
    CurrentTile := blockInfo^.Item
  else
    CurrentTile := nil;

  //Logger.ExitMethod([lcClient, lcDebug], 'UpdateCurrentTile');
end;

procedure TfrmMain.UpdateFilter;
var
  blockInfo: PBlockInfo;
  tileData: TTiledata;
  staticTileData: TStaticTileData;
  lastSurface: PBlockInfo;
  surfaceTop: Integer;
  zoom: Single;
begin
  blockInfo := nil;
  lastSurface := nil;
  while FScreenBuffer.Iterate(blockInfo) do
  begin
    if blockInfo^.State in [ssNormal, ssFiltered] then
    begin
      blockInfo^.State := ssNormal;
      if (blockInfo^.Item.X < frmBoundaries.seMinX.Value) or
         (blockInfo^.Item.X > frmBoundaries.seMaxX.Value) or
         (blockInfo^.Item.Y < frmBoundaries.seMinY.Value) or
         (blockInfo^.Item.Y > frmBoundaries.seMaxY.Value) or
         (blockInfo^.Item.Z < frmBoundaries.tbMinZ.Position) or
         (blockInfo^.Item.Z > frmBoundaries.tbMaxZ.Position) then
      begin
        blockInfo^.State := ssFiltered;
      end else
      if tbFilter.Down and (blockInfo^.Item is TStaticItem) and
        (not frmFilter.Filter(TStaticItem(blockInfo^.Item))) then
      begin
        blockInfo^.State := ssFiltered;
      end;

      blockInfo^.WalkRestriction := wrNone;
      if acWalkable.Checked then
      begin
        if blockInfo^.Item is TMapCell then
        begin
          tileData := ResMan.Tiledata.LandTiles[blockInfo^.Item.TileID];
          if tdfImpassable in tileData.Flags then
          begin
            blockInfo^.WalkRestriction := wrCannotWalk;
            lastSurface := nil;
          end else
          begin
            blockInfo^.WalkRestriction := wrCanWalk;
            lastSurface := blockInfo;
            surfaceTop := blockInfo^.Item.Z;
          end;
        end else
        begin
          staticTileData := ResMan.Tiledata.StaticTiles[blockInfo^.Item.TileID];
          if (lastSurface <> nil) and (lastSurface^.WalkRestriction = wrCanWalk) and
             (lastSurface^.Item.X = blockInfo^.Item.X) and
             (lastSurface^.Item.Y = blockInfo^.Item.Y) and ([tdfSurface,
              tdfImpassable] * staticTileData.Flags <> []) then
          begin
            if (blockInfo^.Item.Z < surfaceTop + 16) and
               ((blockInfo^.Item.Z > lastSurface^.Item.Z + 2) or
                not (tdfSurface in staticTileData.Flags)) then
              lastSurface^.WalkRestriction := wrCannotWalk;
          end;

          if tdfSurface in staticTileData.Flags then
          begin
            if tdfImpassable in staticTileData.Flags then
            begin
              blockInfo^.WalkRestriction := wrCannotWalk;
              lastSurface := nil;
            end else
            begin
              blockInfo^.WalkRestriction := wrCanWalk;
              lastSurface := blockInfo;
              surfaceTop := blockInfo^.Item.Z + staticTileData.Height;
            end;
          end;
        end;
      end; //acWalkable.Checked

    end;
  end;

  Include(FScreenBufferState, sbsFiltered);

  if tbZoom.Down then zoom := tbZoom.Tag / 1000.0 else zoom := 1.0;
  if (FLightManager.LightLevel > 0) and not acFlat.Checked then
    FLightManager.UpdateLightMap(FX + FLowOffsetX, FRangeX + 1, FY + FLowOffsetY,
      FRangeY + 1, FScreenBuffer, zoom);
end;

procedure TfrmMain.UpdateSelection;

  procedure SetHighlight(ABlockInfo: PBlockInfo; AHighlighted: Boolean);
  begin
    if (ABlockInfo^.Item is TStaticItem) and acHue.Checked then
    begin
      if ABlockInfo^.HueOverride <> AHighlighted then
      begin
        ABlockInfo^.HueOverride := AHighlighted;
        if AHighlighted then
        begin
          ABlockInfo^.Hue := frmHueSettings.GetHue;
          ABlockInfo^.LowRes := FTextureManager.GetStaticMaterial(
            TStaticItem(ABlockInfo^.Item), ABlockInfo^.Hue);
        end else
        begin
          ABlockInfo^.LowRes := FTextureManager.GetStaticMaterial(
            TStaticItem(ABlockInfo^.Item));
      end;
      end;
    end else
    begin
      ABlockInfo^.Highlighted := AHighlighted;
    end;
  end;

  procedure AddGhostTile(AX, AY: Word; ABaseTile: TWorldItem; selecetion: TRect);

    // Получение ID текстуры для тайлов при использовании кисти
    function GetTileId(brush: PGroupBrush; mask: byte; tileId: LongWord) : Integer;
    var
      i, j : Integer;
      tileMask : Byte;
      tileBrush, tempBrush: PGroupBrush;
      brushEdge: PGroupBrushEdge;
      randnf, chance, factor: Float;
      brushCount: LongWord;
      brushTiles: ^PBrushTile;
    begin
      GetTileId := tileId;
      if FBrushList.Tiles[tileId].ID <> tileId
      then exit;

      if (FBrushList.Tiles[tileId].Brush1^.ID <> brush^.ID) and (FBrushList.Tiles[tileId].Brush2^.ID <> brush^.ID)
        and (FBrushList.Tiles[tileId].Brush1^.ID <> FBrushList.Tiles[tileId].Brush2^.ID)
      then begin
        i:=0; j:=0;
        tileMask := FBrushList.Tiles[tileId].Mask and not(mask);
        if (tileMask and $01) <> 0 then inc(i);
        if (tileMask and $02) <> 0 then inc(i);
        if (tileMask and $04) <> 0 then inc(i);
        if (tileMask and $08) <> 0 then inc(i);
        tileMask := not(FBrushList.Tiles[tileId].Mask or $F0) and not(mask);
        if (tileMask and $01) <> 0 then inc(j);
        if (tileMask and $02) <> 0 then inc(j);
        if (tileMask and $04) <> 0 then inc(j);
        if (tileMask and $08) <> 0 then inc(j);
        if i > j
           then tileBrush := FBrushList.Tiles[tileId].Brush1
           else tileBrush := FBrushList.Tiles[tileId].Brush2;
        tileMask := $00;
      end else
      if (FBrushList.Tiles[tileId].Brush1^.ID = brush^.ID) and (FBrushList.Tiles[tileId].Brush2^.ID = brush^.ID)
      then begin
        tileBrush := nil;
      end else if (FBrushList.Tiles[tileId].Brush1^.ID = FBrushList.Tiles[tileId].Brush2^.ID)
      then begin
        tileBrush := FBrushList.Tiles[tileId].Brush1;
        tileMask  := $00;
      end else begin
        if brush^.ID = FBrushList.Tiles[tileId].Brush1^.ID then begin
          tileBrush := FBrushList.Tiles[tileId].Brush2;
          tileMask  := FBrushList.Tiles[tileId].Mask;
        end else
        if brush^.ID = FBrushList.Tiles[tileId].Brush2^.ID then begin
          tileBrush := FBrushList.Tiles[tileId].Brush1;
          tileMask  := not(FBrushList.Tiles[tileId].Mask or $F0);
        end;
      end;

      // Сумирование масок
      if tileBrush = nil then begin
        mask := $0F;
      end else begin
        //Logger.Send([lcInfo], '1 Brush Id= %d  Mask= 0x%.2x  TileMask= 0x%.2x  tileId= 0x%.4x', [brush^.ID, mask, tileMask, tileId]);
        mask := tileMask or mask;
        //Logger.Send([lcInfo], '2 Brush Id= %d  Mask= 0x%.2x  TileMask= 0x%.2x  tileId= 0x%.4x', [brush^.ID, mask, tileMask, tileId]);
        if (mask = $01) or (mask = $02) or (mask = $04) or (mask = $05) or
           (mask = $06) or (mask = $08) or (mask = $0A) or (mask = $0C)
        then begin
          mask := not(mask or $F0);
          tempBrush:= brush;
          brush:= tileBrush;
          tileBrush:= tempBrush;
        end;
      end;

      // Получение данных кисти
      brushCount:= 0;  brushEdge:= nil;  brushTiles:= nil;
      if mask = $0F then begin
        brushCount := brush^.Count;        brushTiles := brush^.BTile;
      end else begin
        for i := 0 to brush^.ECount-1 do begin
         // Logger.Send([lcInfo], 'i= %d/%d  EdgeId= %d  BrushId= %d', [i, brush^.ECount-1, mask, brush^.EdgeId[i]^, tileBrush^.ID]);
          if brush^.EdgeId[i]^ = tileBrush^.ID then begin
            brushEdge := brush^.BEdges[i];
            break;
          end; end;
        if brushEdge <> nil then
          case mask of
            $0E: begin brushCount := brushEdge^.CountDR;  brushTiles := brushEdge^.BTileDR; end;
            $0D: begin brushCount := brushEdge^.CountDL;  brushTiles := brushEdge^.BTileDL; end;
            $0B: begin brushCount := brushEdge^.CountUL;  brushTiles := brushEdge^.BTileUL; end;
            $07: begin brushCount := brushEdge^.CountUR;  brushTiles := brushEdge^.BTileUR; end;
            $09: begin brushCount := brushEdge^.CountLL;  brushTiles := brushEdge^.BTileLL; end;
            $03: begin brushCount := brushEdge^.CountUU;  brushTiles := brushEdge^.BTileUU; end;
          end;
      end;

      //Logger.Send([lcInfo], 'Brush Id= %d  Mask= 0x%.2x   TileMask= 0x%.2x  tileId= 0x%.4x', [brush^.ID, mask, tileMask, tileId]);

      // Находение ID тайла
      chance := 0; factor := 0;
      for i := 0 to brushCount - 1 do
        chance := chance + brushTiles[i]^.Chance;
      if chance > 0 then
        factor := 1.0 / chance;
      randnf := Random; chance := 0;
      for i := 0 to brushCount - 1 do begin
        chance := chance + factor * brushTiles[i]^.Chance;
        if randnf <= chance then begin
          GetTileId := brushTiles[i]^.ID;
          break;
        end;
      end;

      //if (FBrushList.Tiles[GetTileId].Mask <> FBrushList.Tiles[tileId].Mask)
      //or (FBrushList.Tiles[GetTileId].ID <> GetTileId) or (FBrushList.Tiles[tileId].ID <> tileId)
      //or (FBrushList.Tiles[GetTileId].Brush1^.ID <> FBrushList.Tiles[tileId].Brush1^.ID)
      //or (FBrushList.Tiles[GetTileId].Brush2^.ID <> FBrushList.Tiles[tileId].Brush2^.ID)
      //or (GetTileId < 0) or (GetTileId > $3FFF) then GetTileId:= -1;
    end;

  var
    blockInfo: PBlockInfo;
    tileInfo: PTileInfo;
    item: PVirtualItem;
    node: PVirtualItem;
    cell: TMapCell;
    ghostTile: TGhostTile;
    i, randalt: Integer;
  begin
    if frmDrawSettings.cbProbability.Checked and frmDrawSettings.cbProbability.Enabled
      and (frmDrawSettings.seProbability.Value < 100 * Random)
      then exit;

    tileInfo := nil;
    if frmDrawSettings.rbTileList.Checked then
    begin
      item := vdtTiles.GetFirstSelected;
      if item <> nil then
        tileInfo := vdtTiles.GetNodeData(item);
    end else if frmDrawSettings.rbRandom.Checked then
    begin
      node := vdlRandom.GetFirst;
      for i := 1 to Random(vdlRandom.TilesCount) do
        node := vdlRandom.GetNext(node);

      if node <> nil then
        tileInfo := vdlRandom.GetNodeData(node);
    end;

    if tileInfo <> nil then
    begin
      if tileInfo^.ID > $2F000000 then
      begin                                                // **** Кисти ****
        cell := FLandscape.MapCell[AX, AY];
        if cell <> nil then
        begin
          //Logger.Send([lcInfo], '!!! AX= %d  AY= %d ', [AX, AY]);
          //Logger.Send([lcInfo], 'MapCell GhostId= 0x%.4x  TileId= 0x%.4x  Id= 0x%.4x', [cell.RawTileID, cell.TileID, cell.ID]);
          inc(selecetion.Left, -1);  inc(selecetion.Top, -1);
          if not IsInRect(AX, AY, selecetion) then exit;
          inc(selecetion.Left, +1);  inc(selecetion.Top, +1);

          if (AX = selecetion.Left) then begin
            AddGhostTile(selecetion.Left-1,  AY,                  ABaseTile, selecetion);
          end;
          if (AY = selecetion.Top) then begin
            AddGhostTile(AX,                 selecetion.Top-1,    ABaseTile, selecetion);
          end;
          if (AX = selecetion.Left) and (AY = selecetion.Top) then begin
            AddGhostTile(selecetion.Left-1,  selecetion.Top-1,    ABaseTile, selecetion);
          end;

          if (AX = selecetion.Right) and (AY = selecetion.Bottom)
          then randalt := GetTileId(PGroupBrush(tileInfo^.ptr), $01, cell.RawTileID)
          else if (AX = selecetion.Left-1) and (AY = selecetion.Bottom)
          then randalt := GetTileId(PGroupBrush(tileInfo^.ptr), $02, cell.RawTileID)
          else if (AX = selecetion.Left-1) and (AY = selecetion.Top-1)
          then randalt := GetTileId(PGroupBrush(tileInfo^.ptr), $04, cell.RawTileID)
          else if (AX = selecetion.Right)  and (AY = selecetion.Top-1)
          then randalt := GetTileId(PGroupBrush(tileInfo^.ptr), $08, cell.RawTileID)
          else if (AX = selecetion.Right)
          then randalt := GetTileId(PGroupBrush(tileInfo^.ptr), $09, cell.RawTileID)
          else if (AY = selecetion.Bottom)
          then randalt := GetTileId(PGroupBrush(tileInfo^.ptr), $03, cell.RawTileID)
          else if (AY = selecetion.Top-1)
          then randalt := GetTileId(PGroupBrush(tileInfo^.ptr), $0C, cell.RawTileID)
          else if (AX = selecetion.Left-1)
          then randalt := GetTileId(PGroupBrush(tileInfo^.ptr), $06, cell.RawTileID)
          else randalt := GetTileId(PGroupBrush(tileInfo^.ptr), $0F, cell.RawTileID);

          if (randalt > 0) then begin //Добавление текстур
            cell.IsGhost := True;
            cell.GhostID := randalt;
            if frmDrawSettings.cbForceAltitude.Checked then
              cell.GhostZ := frmDrawSettings.seForceAltitude.Value
            else
              cell.GhostZ := cell.RawZ;
            if frmDrawSettings.cbRandomHeight.Checked then
              cell.GhostZ := cell.GhostZ + Random(frmDrawSettings.seRandomHeight.Value);

            PrepareMapCell(cell);
          end;
        end;
      end else if tileInfo^.ID > $1F000000 then
      begin                                                // **** Объекты *****
        for i := 0 to PGroupEntry(tileInfo^.ptr)^.Count - 1 do begin
          if frmDrawSettings.cbUseFreeTilesOnly.Checked then begin
            blockInfo := FScreenBuffer.Find(AX + PGroupEntry(tileInfo^.ptr)^.ETile[i].X,
                                            AY + PGroupEntry(tileInfo^.ptr)^.ETile[i].Y);
            if (blockInfo <> nil) and (blockInfo^.Next <> nil) and
              (blockInfo^.Item.X = blockInfo^.Next^.Item.X) and (blockInfo^.Item.Y = blockInfo^.Next^.Item.Y)
              then exit;
          end;
        end;

        if frmDrawSettings.cbRandomHeight.Checked
          then randalt := Random(frmDrawSettings.seRandomHeight.Value)
          else randalt := 0;
        for i := 0 to PGroupEntry(tileInfo^.ptr)^.Count - 1 do begin
          ghostTile := TGhostTile.Create(nil, nil, 0, 0);
          ghostTile.TileID := PGroupEntry(tileInfo^.ptr)^.ETile[i].ID - $4000;
          ghostTile.Hue := PGroupEntry(tileInfo^.ptr)^.ETile[i].Hue;
          ghostTile.X := AX + PGroupEntry(tileInfo^.ptr)^.ETile[i].X;
          ghostTile.Y := AY + PGroupEntry(tileInfo^.ptr)^.ETile[i].Y;
          ghostTile.CenterX := AX;
          ghostTile.CenterY := AY;
          if ((not frmDrawSettings.cbForceAltitude.Enabled) or (not frmDrawSettings.cbForceAltitude.Checked)) then
          begin
            if frmDrawSettings.cbUseSurfaceAltitude.Checked then begin
              blockInfo := FScreenBuffer.Find(AX, AY);
              if blockInfo <> nil then
                 ABaseTile := blockInfo^.Item;
              ghostTile.Z := ABaseTile.Z;
            end;

            if ABaseTile is TStaticItem then
              ghostTile.Z := ABaseTile.Z + ResMan.Tiledata.StaticTiles[ABaseTile.TileID].Height
            else if ABaseTile is TMapCell then
              ghostTile.Z := ResMan.Landscape.GetEffectiveAltitude(TMapCell(ABaseTile))
            else // if ABaseTile is TVirtualTile then
              ghostTile.Z := ABaseTile.Z;
          end else
            ghostTile.Z := frmDrawSettings.seForceAltitude.Value;
          ghostTile.Z := ghostTile.Z + randalt + PGroupEntry(tileInfo^.ptr)^.ETile[i].Z;;
          {
          Logger.Send([lcInfo], '    <Entry Id="%.4d  Tile %.2d/%.2d  -  Id="0x%.4x" Hue="0x%.3x" X="%d" Y="%d" Z="%d">',
            [PGroupEntry(tileInfo^.ptr)^.ID, i, PGroupEntry(tileInfo^.ptr)^.Count, ghostTile.TileID,
            ghostTile.Hue, ghostTile.X, ghostTile.Y, ghostTile.Z]);
          }
          ghostTile.UpdatePriorities(ResMan.Tiledata.StaticTiles[ghostTile.TileID], MaxInt);
          ghostTile.CanBeEdited := True;

          FVirtualTiles.Add(ghostTile);
          blockInfo := FScreenBuffer.Insert(ghostTile);
          blockInfo^.State := ssGhost;
          PrepareScreenBlock(blockInfo);
        end;
      end else if tileInfo^.ID < $4000 then
      begin                                                // **** Текстуры ****
        cell := FLandscape.MapCell[AX, AY];
        if cell <> nil then
        begin
          cell.IsGhost := True;
          cell.GhostID := tileInfo^.ID;
          if frmDrawSettings.cbForceAltitude.Checked then
            cell.GhostZ := frmDrawSettings.seForceAltitude.Value
          else
            cell.GhostZ := cell.RawZ;
          if frmDrawSettings.cbRandomHeight.Checked then
            cell.GhostZ := cell.GhostZ + Random(frmDrawSettings.seRandomHeight.Value);

          PrepareMapCell(cell);
        end;
      end else
      begin                                                // **** Статика *****
        if frmDrawSettings.cbUseFreeTilesOnly.Checked then begin
          blockInfo := FScreenBuffer.Find(AX, AY);
          if (blockInfo <> nil) and (blockInfo^.Next <> nil) and
            (blockInfo^.Item.X = blockInfo^.Next^.Item.X) and (blockInfo^.Item.Y = blockInfo^.Next^.Item.Y)
            then exit;
        end;

        ghostTile := TGhostTile.Create(nil, nil, 0, 0);
        ghostTile.TileID := tileInfo^.ID - $4000;
        ghostTile.Hue := frmHueSettings.GetHue;
        ghostTile.X := AX;    ghostTile.CenterX := AX;
        ghostTile.Y := AY;    ghostTile.CenterY := AY;

        if ((not frmDrawSettings.cbForceAltitude.Enabled) or (not frmDrawSettings.cbForceAltitude.Checked)) then
        begin
          if frmDrawSettings.cbUseSurfaceAltitude.Checked then begin
            blockInfo := FScreenBuffer.Find(AX, AY);
            if blockInfo <> nil then
              ABaseTile := blockInfo^.Item;
          end;
          //ghostTile.Z := ABaseTile.Z;
          if ABaseTile is TStaticItem then
            ghostTile.Z := ABaseTile.Z + ResMan.Tiledata.StaticTiles[ABaseTile.TileID].Height
          else if ABaseTile is TMapCell then
            ghostTile.Z := ResMan.Landscape.GetEffectiveAltitude(TMapCell(ABaseTile))
          else // if ABaseTile is TVirtualTile then
            ghostTile.Z := ABaseTile.Z;
        end else
          ghostTile.Z := frmDrawSettings.seForceAltitude.Value;
        if frmDrawSettings.cbRandomHeight.Checked then
          ghostTile.Z := ghostTile.Z +
            Random(frmDrawSettings.seRandomHeight.Value);

        ghostTile.UpdatePriorities(ResMan.Tiledata.StaticTiles[ghostTile.TileID],
          MaxInt);
        ghostTile.CanBeEdited := True;

        FVirtualTiles.Add(ghostTile);
        blockInfo := FScreenBuffer.Insert(ghostTile);
        blockInfo^.State := ssGhost;
        PrepareScreenBlock(blockInfo);
      end;

    end;
  end;

var
  selectedRect: TRect;
  blockInfo: PBlockInfo;
  item: TWorldItem;
  cell: TMapCell;
  i, tileX, tileY: Integer;
  brushMod: Boolean; // Актевирован Режим работы с кистями
begin
  //Logger.EnterMethod([lcClient, lcDebug], 'UpdateSelection');

  //If the current tile is nil, but we still have a selected tile, the
  //procedure is pointless - the selection should stay intact.
  if (CurrentTile <> nil) or (SelectedTile = nil) then
  begin
    brushMod := (vdtTiles.GetFirstSelected <> nil) and (PTileInfo(vdtTiles.GetNodeData(vdtTiles.GetFirstSelected))^.ID >= $2F000000);

    if CurrentTile = nil then
      selectedRect := Rect(-1, -1, -1, -1)
    else
      selectedRect := GetSelectedRect;

    //clean up old ghost tiles
    //Logger.Send([lcClient, lcDebug], 'Cleaning ghost tiles');
    for i := FVirtualTiles.Count - 1 downto 0 do
    begin
      item := FVirtualTiles[i];
      if (item is TGhostTile) and not IsInRect((item as TGhostTile).CenterX,
         (item as TGhostTile).CenterY, selectedRect) then
      begin
        FScreenBuffer.Delete(item);
        FVirtualTiles.Delete(i);
      end;
    end;
    //Logger.Send([lcClient, lcDebug], 'FSelection', FSelection);
    if brushMod then begin // Для кистей
      i:= -1; inc(selectedRect.Left,  -1);  inc(selectedRect.Top,    -1);
    end else i:=  0;
    for tileX := FSelection.Left+i to FSelection.Right do
      for tileY := FSelection.Top+i to FSelection.Bottom do
        if not IsInRect(tileX, tileY, selectedRect) then
        begin
          cell := FLandscape.MapCell[tileX, tileY];
          if (cell <> nil) and cell.IsGhost then
          begin
            cell.IsGhost := False;
            PrepareMapCell(cell);
          end;
        end;
    if brushMod then begin // Для кистей
      inc(selectedRect.Left,  +1);  inc(selectedRect.Top,    +1);
    end;

    if (CurrentTile <> nil) and (not acSelect.Checked) then
    begin
      blockInfo := nil;
      if (SelectedTile <> nil) and (CurrentTile <> SelectedTile) then
      begin
        {Logger.Send([lcClient, lcDebug], 'Multiple Targets');
        Logger.Send([lcClient, lcDebug], 'SelectedRect', selectedRect);}
        //Logger.Send([lcClient, lcDebug], 'SelectedTile: %.5x (%.6d)', [SelectedTile.TileID, SelectedTile.TileID]);
        //set new ghost tiles

        if acDraw.Checked then begin
          for tileX := selectedRect.Left to selectedRect.Right do
            for tileY := selectedRect.Top to selectedRect.Bottom do
              if not IsInRect(tileX, tileY, FSelection) then
                AddGhostTile(tileX, tileY, SelectedTile, selectedRect);
          if brushMod then begin // Для кистей
            if (selectedRect.Left > FSelection.Left) then
              for tileY := selectedRect.Top to selectedRect.Bottom do
                AddGhostTile(selectedRect.Left, tileY, SelectedTile, selectedRect);
            if (selectedRect.Top > FSelection.Top) then
              for tileX := selectedRect.Left to selectedRect.Right do
                AddGhostTile(tileX, selectedRect.Top, SelectedTile, selectedRect);
            if (selectedRect.Right < FSelection.Right) then
              for tileY := selectedRect.Top to selectedRect.Bottom do
                AddGhostTile(selectedRect.Right, tileY, SelectedTile, selectedRect);
            if (selectedRect.Bottom < FSelection.Bottom) then
              for tileX := selectedRect.Left to selectedRect.Right do
                AddGhostTile(tileX, selectedRect.Bottom, SelectedTile, selectedRect);
            if (selectedRect.Right > FSelection.Right) then
              for tileY := FSelection.Top to FSelection.Bottom do
                AddGhostTile(FSelection.Right, tileY, SelectedTile, selectedRect);
            if (selectedRect.Bottom > FSelection.Bottom) then
              for tileX := FSelection.Left to FSelection.Right do
                AddGhostTile(tileX, FSelection.Bottom, SelectedTile, selectedRect);
          end;
        end;
        while FScreenBuffer.Iterate(blockInfo) do
          if (blockInfo^.State = ssNormal) then
            SetHighlight(blockInfo, IsInRect(blockInfo^.Item.X, blockInfo^.Item.Y,
              selectedRect) and not acDraw.Checked);
      end else
      begin
        //Logger.Send([lcClient, lcDebug], 'Single Target');
        //Logger.Send([lcClient, lcDebug], 'CurrentTile: %.5x (%.6d)', [CurrentTile.TileID, CurrentTile.TileID]);
        if acDraw.Checked and not IsInRect(CurrentTile.X, CurrentTile.Y, FSelection) then
          AddGhostTile(CurrentTile.X, CurrentTile.Y, CurrentTile, selectedRect);
        while FScreenBuffer.Iterate(blockInfo) do
          if blockInfo^.State = ssNormal then
            SetHighlight(blockInfo, (blockInfo^.Item = CurrentTile) and not acDraw.Checked);
      end;
    end;
    FSelection := selectedRect;
  end;
  {Logger.Send([lcClient, lcDebug], 'Virtual Tiles', FVirtualTiles.Count);
  Logger.ExitMethod([lcClient, lcDebug], 'UpdateSelection');}
end;

procedure TfrmMain.OnTileRemoved(ATile: TMulBlock);
begin
  if ATile = FCurrentTile then
    FCurrentTile := nil
  else if ATile = FSelectedTile then
    FSelectedTile := nil;
end;

procedure TfrmMain.WriteChatMessage(ASender, AMessage: string);
var
  node: PVirtualNode;
  chatInfo: PChatInfo;
begin
  node := vstChat.AddChild(nil);
  chatInfo := vstChat.GetNodeData(node);
  chatInfo^.Time := Now;
  chatInfo^.Sender := ASender;
  chatInfo^.Msg := AMessage;
  if vstChat.RootNodeCount > 30 then
    vstChat.DeleteNode(vstChat.GetFirst);
  vstChat.ScrollIntoView(node, False);
  
  if not pnlChat.Visible then
  begin
    lblChatHeaderCaption.Font.Bold := True;
    lblChatHeaderCaption.Font.Italic := True;
    lblChatHeaderCaption.Font.Color := clRed;
  end;
end;

procedure TfrmMain.OnClientHandlingPacket(ABuffer: TEnhancedMemoryStream);
var
  sender, msg: string;
  i: Integer;
  accessLevel: TAccessLevel;
  clientNode: PVirtualNode;
  clientInfo: PClientInfo;
begin
  case ABuffer.ReadByte of
    $01: //client connected
      begin
        sender := ABuffer.ReadStringNull;
        vstClients.BeginUpdate;
        clientNode := vstClients.AddChild(nil);
        clientInfo := vstClients.GetNodeData(clientNode);
        clientInfo^.Name := sender;
        clientInfo^.AccessLevel := TAccessLevel(ABuffer.ReadByte);
        clientInfo^.LogonDateTime := Now;
        vstClients.EndUpdate;
        if sender <> dmNetwork.Username then
          WriteChatMessage('System', Format(lbUserLoginedMsg, [sender]));
      end;
    $02:
      begin
        sender := ABuffer.ReadStringNull;
        vstClients.BeginUpdate;
        clientNode := vstClients.GetFirst;
        while clientNode <> nil do begin
          clientInfo := vstClients.GetNodeData(clientNode);
          if (clientInfo^.Name = sender)
          then begin
            vstClients.DeleteNode(clientNode);
            break;
          end
          else clientNode := vstClients.GetNext(clientNode);
        end;
        vstClients.EndUpdate;
        if sender <> dmNetwork.Username then
          WriteChatMessage('System', Format(lbUserLogoutedMsg, [sender]));
      end;
    $03: //Client list
      begin
        vstClients.Clear;
        while ABuffer.Position < ABuffer.Size do
        begin
          sender := ABuffer.ReadStringNull;
          clientNode := vstClients.AddChild(nil);
          clientInfo := vstClients.GetNodeData(clientNode);
          clientInfo^.Name := sender;
          clientInfo^.AccessLevel := TAccessLevel(ABuffer.ReadByte);
          clientInfo^.LogonDateTime := IncSecond(dmNetwork.ServerStart, ABuffer.ReadDWord);
        end;
      end;
    $04: //Set pos
      begin
        FX := ABuffer.ReadWord;
        FY := ABuffer.ReadWord;
        SetPos(FX, FY);
      end;
    $05: //chat
      begin
        sender := ABuffer.ReadStringNull;
        msg := ABuffer.ReadStringNull;
        WriteChatMessage(sender, msg);
      end;
    $07: //access changed
      begin
        accessLevel := TAccessLevel(ABuffer.ReadByte);
        FLandscape.UpdateWriteMap(ABuffer);
        FRepaintNeeded := True;

        if accessLevel <> dmNetwork.AccessLevel then
        begin
          dmNetwork.AccessLevel := accessLevel;
          if accessLevel = alNone then
          begin
            MessageDlg(lbDlgBlockedAccessCaption, lbDlgBlockedAccess, mtWarning, [mbOK], 0);
            mnuDisconnectClick(nil);
          end else
          begin
            ProcessAccessLevel;
            MessageDlg(lbDlgCnangedAccessCaption, Format(lbDlgCnangedAccess, [GetAccessLevel(accessLevel)]), mtWarning, [mbOK], 0);
          end;
        end;

        for i := FAccessChangedListeners.Count - 1 downto 0 do
          FAccessChangedListeners[i](accessLevel);
      end;
  end;
end;

function TfrmMain.GetInternalTileID(ATile: TWorldItem): LongWord;
begin
  Result := ATile.TileID;
  if ATile is TStaticItem then
    Inc(Result, $4000);
end;

function TfrmMain.GetSelectedRect: TRect;
begin
  if CurrentTile <> nil then
  begin
    if SelectedTile <> nil then
    begin
      Result.Left := Min(CurrentTile.X, SelectedTile.X);
      Result.Top := Min(CurrentTile.Y, SelectedTile.Y);
      Result.Right := Max(CurrentTile.X, SelectedTile.X);
      Result.Bottom := Max(CurrentTile.Y, SelectedTile.Y);
    end else
    begin
      Result.Left := CurrentTile.X;
      Result.Top := CurrentTile.Y;
      Result.Right := CurrentTile.X;
      Result.Bottom := CurrentTile.Y;
    end;
  end;
end;

function TfrmMain.ConfirmAction: Boolean;
begin
  if acMove.Checked and frmMoveSettings.cbAsk.Checked then
  begin
    Result := frmMoveSettings.ShowModal = mrYes;
  end else
  if not mnuSecurityQuestion.Checked then
  begin
    Result := True;
  end else
  begin
    frmConfirmation.Left := Mouse.CursorPos.x - frmConfirmation.btnYes.Left - frmConfirmation.btnYes.Width div 2;
    frmConfirmation.Top := Mouse.CursorPos.y - frmConfirmation.btnYes.Top - frmConfirmation.btnYes.Height div 2;
    Result := frmConfirmation.ShowModal = mrYes;
  end;

  if not oglGameWindow.MouseEntered then
    oglGameWindowMouseLeave(nil);
end;

function TfrmMain.FindRandomPreset(AName: String): TDOMElement;
var
  preset: TDOMElement;
  presets: TDOMNodeList;
  i: Integer;
begin
  presets := FRandomPresetsDoc.DocumentElement.ChildNodes;
  Result := nil;
  i := 0;
  while (i < presets.Count) and (Result = nil) do
  begin
    preset := TDOMElement(presets[i]);
    if SameText(preset.AttribStrings['Name'], AName) then
      Result := preset
    else
      Inc(i);
  end;
end;

procedure TfrmMain.ForceUpdateCurrentTile;
begin
  CurrentTile := nil;
  UpdateCurrentTile;
end;

procedure TfrmMain.GetDrawOffset(AX, AY: Integer; out DrawX, DrawY: Integer); inline;
var
  zoom: Single;
begin
  if tbZoom.Down then zoom := tbZoom.Tag / 1000.0 else zoom := 1.0;

  Dec(AX, FX);
  Dec(AY, FY);
  DrawX := (oglGameWindow.Width div 2) + Trunc((AX - AY) * 22 * zoom);
  DrawY := (oglGamewindow.Height div 2) + Trunc((AX + AY) * 22 * zoom);
end;

initialization
  {$I UfrmMain.lrs}

end.

