DEADSOFTWARE

i10n: use resourcestring and gettext for localization
[d2df-editor.git] / src / editor / f_main.pas
index 90652570f29530fb3330912e0865815499e7b582..86ec6b4f7681bfe84d94c061e20eaaeeea963337 100644 (file)
@@ -5,10 +5,10 @@ unit f_main;
 interface
 
 uses
-  LCLIntf, LCLType, LMessages, SysUtils, Variants, Classes, Graphics,
-  Controls, Forms, Dialogs, ImgList, StdCtrls, Buttons,
-  ComCtrls, ValEdit, Types, ToolWin, Menus, ExtCtrls,
-  CheckLst, Grids, OpenGLContext, utils, UTF8Process;
+  LCLIntf, LCLType, SysUtils, Variants, Classes, Graphics,
+  Controls, Forms, Dialogs, StdCtrls, Buttons,
+  ComCtrls, ValEdit, Types, Menus, ExtCtrls,
+  CheckLst, Grids, OpenGLContext, Utils, UTF8Process;
 
 type
 
@@ -18,17 +18,27 @@ type
     lLoad: TLabel;
   // Главное меню:
     MainMenu: TMainMenu;
+  // Apple menu:
+    miApple: TMenuItem;
+    miAppleAbout: TMenuItem;
+    miAppleLine0: TMenuItem;
+    miApplePref: TMenuItem;
+    miAppleLine1: TMenuItem;
   // "Файл":
     miMenuFile: TMenuItem;
     miNewMap: TMenuItem;
     miOpenMap: TMenuItem;
     miSaveMap: TMenuItem;
     miSaveMapAs: TMenuItem;
+    miMacRecentSubMenu: TMenuItem;
+    miMacRecentClear: TMenuItem;
     miOpenWadMap: TMenuItem;
     miLine1: TMenuItem;
+    miReopenMap: TMenuItem;
     miSaveMiniMap: TMenuItem;
     miDeleteMap: TMenuItem;
     miPackMap: TMenuItem;
+    miWinRecent: TMenuItem;
     miLine2: TMenuItem;
     miExit: TMenuItem;
   // "Правка":
@@ -43,11 +53,8 @@ type
     miLine5: TMenuItem;
     miToFore: TMenuItem;
     miToBack: TMenuItem;
-  // "Инструменты":
-    miMenuTools: TMenuItem;
-    miSnapToGrid: TMenuItem;
-    miMiniMap: TMenuItem;
-    miSwitchGrid: TMenuItem;
+  // View menu:
+    miMenuView: TMenuItem;
     miShowEdges: TMenuItem;
     miLayers: TMenuItem;
     miLayer1: TMenuItem;
@@ -59,24 +66,29 @@ type
     miLayer7: TMenuItem;
     miLayer8: TMenuItem;
     miLayer9: TMenuItem;
+    miViewLine1: TMenuItem;
+    miMiniMap: TMenuItem;
+    miViewLine2: TMenuItem;
+    miMapPreview: TMenuItem;
+    miSnapToGrid: TMenuItem;
+    miSwitchGrid: TMenuItem;
+    miLine6: TMenuItem;
+    miOptions: TMenuItem;
+    miMapOptions: TMenuItem;
   // "Сервис":
     miMenuService: TMenuItem;
     miCheckMap: TMenuItem;
     miOptimmization: TMenuItem;
-    miMapPreview: TMenuItem;
     miTestMap: TMenuItem;
-  // "Настройка":
-    miMenuSettings: TMenuItem;
-    miMapOptions: TMenuItem;
-    miLine6: TMenuItem;
-    miOptions: TMenuItem;
-    miLine7: TMenuItem;
-    miMapTestSettings: TMenuItem;
+  // Window menu:
+    miMenuWindow: TMenuItem;
+    miMacMinimize: TMenuItem;
+    miMacZoom: TMenuItem;
   // "Справка":
     miMenuHelp: TMenuItem;
     miAbout: TMenuItem;
   // Скрытый пункт меню для Ctrl+Tab:
-    miHidden1: TMenuItem;
+    miMenuHidden: TMenuItem;
     minexttab: TMenuItem;
 
   // Панель инструментов:
@@ -84,6 +96,10 @@ type
     pbLoad: TProgressBar;
     pLoadProgress: TPanel;
     RenderPanel: TOpenGLControl;
+    Separator1: TMenuItem;
+    miMacRecentEnd: TMenuItem;
+    miWinRecentStart: TMenuItem;
+    Separator2: TMenuItem;
     tbNewMap: TToolButton;
     tbOpenMap: TToolButton;
     tbSaveMap: TToolButton;
@@ -108,9 +124,6 @@ type
     miLayerP7: TMenuItem;
     miLayerP8: TMenuItem;
     miLayerP9: TMenuItem;
-  // Всплывающее меню для кнопки теста карты:
-    pmMapTest: TPopupMenu;
-    miMapTestPMSet: TMenuItem;
 
   // Панель карты:
     PanelMap: TPanel;
@@ -123,6 +136,7 @@ type
   // Панель применения свойств:
     PanelPropApply: TPanel;
     bApplyProperty: TButton;
+    MapTestTimer: TTimer;
   // Редактор свойств объектов:
     vleObjectProperty: TValueListEditor;
 
@@ -204,17 +218,26 @@ type
     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
+    procedure FormDropFiles(Sender: TObject; const FileNames: array of String);
     procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
     procedure FormResize(Sender: TObject);
+    procedure FormWindowStateChange(Sender: TObject);
+    procedure miMacRecentClearClick(Sender: TObject);
+    procedure miMacZoomClick(Sender: TObject);
     procedure lbTextureListClick(Sender: TObject);
     procedure lbTextureListDrawItem(Control: TWinControl; Index: Integer;
       ARect: TRect; State: TOwnerDrawState);
+    procedure miMacMinimizeClick(Sender: TObject);
+    procedure miReopenMapClick(Sender: TObject);
     procedure RenderPanelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
     procedure RenderPanelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
     procedure RenderPanelMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
     procedure RenderPanelPaint(Sender: TObject);
     procedure RenderPanelResize(Sender: TObject);
+    procedure Splitter1Moved(Sender: TObject);
+    procedure MapTestCheck(Sender: TObject);
     procedure vleObjectPropertyEditButtonClick(Sender: TObject);
+    procedure vleObjectPropertyApply(Sender: TObject);
     procedure vleObjectPropertyGetPickList(Sender: TObject; const KeyName: String; Values: TStrings);
     procedure vleObjectPropertyKeyDown(Sender: TObject; var Key: Word;
                                        Shift: TShiftState);
@@ -238,8 +261,6 @@ type
     procedure miSaveMiniMapClick(Sender: TObject);
     procedure bClearTextureClick(Sender: TObject);
     procedure miPackMapClick(Sender: TObject);
-    procedure aRecentFileExecute(Sender: TObject);
-    procedure miMapTestSettingsClick(Sender: TObject);
     procedure miTestMapClick(Sender: TObject);
     procedure sbVerticalScroll(Sender: TObject; ScrollCode: TScrollCode;
       var ScrollPos: Integer);
@@ -258,8 +279,12 @@ type
   private
     procedure Draw();
     procedure OnIdle(Sender: TObject; var Done: Boolean);
+    procedure RefillRecentMenu (menu: TMenuItem; start: Integer; fmt: AnsiString);
   public
     procedure RefreshRecentMenu();
+    procedure OpenMapFile(FileName: String);
+    function RenderMousePos(): TPoint;
+    procedure RecountSelectedObjects();
   end;
 
 const
@@ -278,19 +303,20 @@ const
 
 var
   MainForm: TMainForm;
-  EditorDir: String;
+  StartMap: String;
   OpenedMap: String;
   OpenedWAD: String;
 
   DotColor: TColor;
   DotEnable: Boolean;
-  DotStep: Byte;
-  DotStepOne, DotStepTwo: Byte;
+  DotStep: Word;
+  DotStepOne, DotStepTwo: Word;
   DotSize: Byte;
   DrawTexturePanel: Boolean;
   DrawPanelSize: Boolean;
   BackColor: TColor;
   PreviewColor: TColor;
+  UseCheckerboard: Boolean;
   Scale: Byte;
   RecentCount: Integer;
   RecentFiles: TStringList;
@@ -304,12 +330,14 @@ var
   TestOptionsAllowExit: Boolean;
   TestOptionsWeaponStay: Boolean;
   TestOptionsMonstersDM: Boolean;
-  TestD2dExe: String;
+  TestD2dExe, TestD2DArgs: String;
   TestMapOnce: Boolean;
 
   LayerEnabled: Array [LAYER_BACK..LAYER_TRIGGERS] of Boolean =
     (True, True, True, True, True, True, True, True, True);
-  PreviewMode: Boolean = False;
+  ContourEnabled: Array [LAYER_BACK..LAYER_TRIGGERS] of Boolean =
+    (False, False, False, False, False, False, False, False, False);
+  PreviewMode: Byte = 0;
   gLanguage: String;
 
   FormCaption: String;
@@ -323,14 +351,14 @@ procedure ChangeShownProperty(Name: String; NewValue: String);
 implementation
 
 uses
-  f_options, e_graphics, e_log, GL, GLExt, Math,
+  f_options, e_graphics, e_log, GL, Math,
   f_mapoptions, g_basic, f_about, f_mapoptimization,
   f_mapcheck, f_addresource_texture, g_textures,
-  f_activationtype, f_keys, MAPWRITER, MAPSTRUCT,
-  MAPREADER, f_selectmap, f_savemap, WADEDITOR, WADSTRUCT, MAPDEF,
+  f_activationtype, f_keys, wadreader, fileutil,
+  MAPREADER, f_selectmap, f_savemap, WADEDITOR, MAPDEF,
   g_map, f_saveminimap, f_addresource, CONFIG, f_packmap,
-  f_addresource_sound, f_maptest, f_choosetype,
-  g_language, f_selectlang, ClipBrd;
+  f_addresource_sound, f_choosetype,
+  g_language, ClipBrd, g_resources, g_options;
 
 const
   UNDO_DELETE_PANEL   = 1;
@@ -380,7 +408,7 @@ const
   SELECTFLAG_SHOTPANEL  = 7;
   SELECTFLAG_SELECTED   = 8;
 
-  RECENT_FILES_MENU_START = 11;
+  RECENT_FILES_MENU_START = 12;
 
   CLIPBOARD_SIG         = 'DF:ED';
 
@@ -431,8 +459,10 @@ var
   LastMovePoint: Types.TPoint;
   MouseLDown: Boolean;
   MouseRDown: Boolean;
+  MouseMDown: Boolean;
   MouseLDownPos: Types.TPoint;
   MouseRDownPos: Types.TPoint;
+  MouseMDownPos: Types.TPoint;
 
   SelectFlag: Byte = SELECTFLAG_NONE;
   MouseAction: Byte = MOUSEACTION_NONE;
@@ -444,6 +474,8 @@ var
 
   UndoBuffer: Array of Array of TUndoRec = nil;
 
+  MapTestProcess: TProcessUTF8;
+  MapTestFile: String;
 
 {$R *.lfm}
 
@@ -683,10 +715,10 @@ begin
   begin
     ScaleSz := 16 div Scale;
   // Размер видимой части карты:
-    rx := min(Normalize16(Width), Normalize16(gMapInfo.Width)) div 2;
-    ry := min(Normalize16(Height), Normalize16(gMapInfo.Height)) div 2;
+    rx := Min(Normalize16(Width), Normalize16(gMapInfo.Width)) div 2;
+    ry := Min(Normalize16(Height), Normalize16(gMapInfo.Height)) div 2;
   // Место клика на мини-карте:
-    MapOffset.X := X - (Width-max(gMapInfo.Width div ScaleSz, 1)-1);
+    MapOffset.X := X - (Width - Max(gMapInfo.Width div ScaleSz, 1) - 1);
     MapOffset.Y := Y - 1;
   // Это же место на "большой" карте:
     MapOffset.X := MapOffset.X * ScaleSz;
@@ -695,17 +727,11 @@ begin
     MapOffset.X := MapOffset.X - rx;
     MapOffset.Y := MapOffset.Y - ry;
   // Выход за границы:
-    if MapOffset.X < 0 then
-      MapOffset.X := 0;
-    if MapOffset.Y < 0 then
-      MapOffset.Y := 0;
-    if MapOffset.X > MainForm.sbHorizontal.Max then
-      MapOffset.X := MainForm.sbHorizontal.Max;
-    if MapOffset.Y > MainForm.sbVertical.Max then
-      MapOffset.Y := MainForm.sbVertical.Max;
+    MapOffset.X := EnsureRange(MapOffset.X, MainForm.sbHorizontal.Min, MainForm.sbHorizontal.Max);
+    MapOffset.Y := EnsureRange(MapOffset.Y, MainForm.sbVertical.Min, MainForm.sbVertical.Max);
   // Кратно 16:
-    MapOffset.X := Normalize16(MapOffset.X);
-    MapOffset.Y := Normalize16(MapOffset.Y);
+  //  MapOffset.X := Normalize16(MapOffset.X);
+  //  MapOffset.Y := Normalize16(MapOffset.Y);
   end;
 
   MainForm.sbHorizontal.Position := MapOffset.X;
@@ -730,6 +756,7 @@ var
   str: String;
 begin
   MainForm.vleObjectProperty.Strings.Clear();
+  MainForm.RecountSelectedObjects();
 
 // Отображаем свойства если выделен только один объект:
   if SelectedObjectCount() <> 1 then
@@ -740,7 +767,7 @@ begin
     Exit;
 
   with MainForm.vleObjectProperty do
-    with ItemProps[InsertRow(_lc[I_PROP_ID], IntToStr(SelectedObjects[_id].ID), True)] do
+    with ItemProps[InsertRow(MsgPropId, IntToStr(SelectedObjects[_id].ID), True)] do
     begin
       EditStyle := esSimple;
       ReadOnly := True;
@@ -752,31 +779,31 @@ begin
         with MainForm.vleObjectProperty,
              gPanels[SelectedObjects[_id].ID] do
         begin
-          with ItemProps[InsertRow(_lc[I_PROP_X], IntToStr(X), True)] do
+          with ItemProps[InsertRow(MsgPropX, IntToStr(X), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_Y], IntToStr(Y), True)] do
+          with ItemProps[InsertRow(MsgPropY, IntToStr(Y), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_WIDTH], IntToStr(Width), True)] do
+          with ItemProps[InsertRow(MsgPropWidth, IntToStr(Width), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_HEIGHT], IntToStr(Height), True)] do
+          with ItemProps[InsertRow(MsgPropHeight, IntToStr(Height), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_PANEL_TYPE], GetPanelName(PanelType), True)] do
+          with ItemProps[InsertRow(MsgPropPanelType, GetPanelName(PanelType), True)] do
           begin
             EditStyle := esEllipsis;
             ReadOnly := True;
@@ -784,7 +811,7 @@ begin
 
           if IsTexturedPanel(PanelType) then
           begin // Может быть текстура
-            with ItemProps[InsertRow(_lc[I_PROP_PANEL_TEX], TextureName, True)] do
+            with ItemProps[InsertRow(MsgPropPanelTex, TextureName, True)] do
             begin
               EditStyle := esEllipsis;
               ReadOnly := True;
@@ -792,13 +819,13 @@ begin
 
             if TextureName <> '' then
             begin // Есть текстура
-              with ItemProps[InsertRow(_lc[I_PROP_PANEL_ALPHA], IntToStr(Alpha), True)] do
+              with ItemProps[InsertRow(MsgPropPanelAlpha, IntToStr(Alpha), True)] do
               begin
                 EditStyle := esSimple;
                 MaxLength := 3;
               end;
 
-              with ItemProps[InsertRow(_lc[I_PROP_PANEL_BLEND], BoolNames[Blending], True)] do
+              with ItemProps[InsertRow(MsgPropPanelBlend, BoolNames[Blending], True)] do
               begin
                 EditStyle := esPickList;
                 ReadOnly := True;
@@ -813,25 +840,25 @@ begin
         with MainForm.vleObjectProperty,
              gItems[SelectedObjects[_id].ID] do
         begin
-          with ItemProps[InsertRow(_lc[I_PROP_X], IntToStr(X), True)] do
+          with ItemProps[InsertRow(MsgPropX, IntToStr(X), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_Y], IntToStr(Y), True)] do
+          with ItemProps[InsertRow(MsgPropY, IntToStr(Y), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_DM_ONLY], BoolNames[OnlyDM], True)] do
+          with ItemProps[InsertRow(MsgPropDmOnly, BoolNames[OnlyDM], True)] do
           begin
             EditStyle := esPickList;
             ReadOnly := True;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_ITEM_FALLS], BoolNames[Fall], True)] do
+          with ItemProps[InsertRow(MsgPropItemFalls, BoolNames[Fall], True)] do
           begin
             EditStyle := esPickList;
             ReadOnly := True;
@@ -844,19 +871,19 @@ begin
         with MainForm.vleObjectProperty,
              gMonsters[SelectedObjects[_id].ID] do
         begin
-          with ItemProps[InsertRow(_lc[I_PROP_X], IntToStr(X), True)] do
+          with ItemProps[InsertRow(MsgPropX, IntToStr(X), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_Y], IntToStr(Y), True)] do
+          with ItemProps[InsertRow(MsgPropY, IntToStr(Y), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_DIRECTION], DirNames[Direction], True)] do
+          with ItemProps[InsertRow(MsgPropDirection, DirNames[Direction], True)] do
           begin
             EditStyle := esPickList;
             ReadOnly := True;
@@ -869,19 +896,19 @@ begin
         with MainForm.vleObjectProperty,
              gAreas[SelectedObjects[_id].ID] do
         begin
-          with ItemProps[InsertRow(_lc[I_PROP_X], IntToStr(X), True)] do
+          with ItemProps[InsertRow(MsgPropX, IntToStr(X), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_Y], IntToStr(Y), True)] do
+          with ItemProps[InsertRow(MsgPropY, IntToStr(Y), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_DIRECTION], DirNames[Direction], True)] do
+          with ItemProps[InsertRow(MsgPropDirection, DirNames[Direction], True)] do
           begin
             EditStyle := esPickList;
             ReadOnly := True;
@@ -894,55 +921,55 @@ begin
         with MainForm.vleObjectProperty,
              gTriggers[SelectedObjects[_id].ID] do
         begin
-          with ItemProps[InsertRow(_lc[I_PROP_TR_TYPE], GetTriggerName(TriggerType), True)] do
+          with ItemProps[InsertRow(MsgPropTrType, GetTriggerName(TriggerType), True)] do
           begin
             EditStyle := esSimple;
             ReadOnly := True;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_X], IntToStr(X), True)] do
+          with ItemProps[InsertRow(MsgPropX, IntToStr(X), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_Y], IntToStr(Y), True)] do
+          with ItemProps[InsertRow(MsgPropY, IntToStr(Y), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_WIDTH], IntToStr(Width), True)] do
+          with ItemProps[InsertRow(MsgPropWidth, IntToStr(Width), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_HEIGHT], IntToStr(Height), True)] do
+          with ItemProps[InsertRow(MsgPropHeight, IntToStr(Height), True)] do
           begin
             EditStyle := esSimple;
             MaxLength := 5;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_TR_ENABLED], BoolNames[Enabled], True)] do
+          with ItemProps[InsertRow(MsgPropTrEnabled, BoolNames[Enabled], True)] do
           begin
             EditStyle := esPickList;
             ReadOnly := True;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_TR_TEXTURE_PANEL], IntToStr(TexturePanel), True)] do
+          with ItemProps[InsertRow(MsgPropTrTexturePanel, IntToStr(TexturePanel), True)] do
           begin
             EditStyle := esEllipsis;
             ReadOnly := True;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_TR_ACTIVATION], ActivateToStr(ActivateType), True)] do
+          with ItemProps[InsertRow(MsgPropTrActivation, ActivateToStr(ActivateType), True)] do
           begin
             EditStyle := esEllipsis;
             ReadOnly := True;
           end;
 
-          with ItemProps[InsertRow(_lc[I_PROP_TR_KEYS], KeyToStr(Key), True)] do
+          with ItemProps[InsertRow(MsgPropTrKeys, KeyToStr(Key), True)] do
           begin
             EditStyle := esEllipsis;
             ReadOnly := True;
@@ -951,7 +978,8 @@ begin
           case TriggerType of
             TRIGGER_EXIT:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_NEXT_MAP], Data.MapName, True)] do
+                str := win2utf(Data.MapName);
+                with ItemProps[InsertRow(MsgPropTrNextMap, str, True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
@@ -960,25 +988,25 @@ begin
 
             TRIGGER_TELEPORT:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_TELEPORT_TO], Format('(%d:%d)', [Data.TargetPoint.X, Data.TargetPoint.Y]), True)] do
+                with ItemProps[InsertRow(MsgPropTrTeleportTo, Format('(%d:%d)', [Data.TargetPoint.X, Data.TargetPoint.Y]), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_D2D], BoolNames[Data.d2d_teleport], True)] do
+                with ItemProps[InsertRow(MsgPropTrD2d, BoolNames[Data.d2d_teleport], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_TELEPORT_SILENT], BoolNames[Data.silent_teleport], True)] do
+                with ItemProps[InsertRow(MsgPropTrTeleportSilent, BoolNames[Data.silent_teleport], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_TELEPORT_DIR], DirNamesAdv[Data.TlpDir], True)] do
+                with ItemProps[InsertRow(MsgPropTrTeleportDir, DirNamesAdv[Data.TlpDir], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -988,19 +1016,19 @@ begin
             TRIGGER_OPENDOOR, TRIGGER_CLOSEDOOR,
             TRIGGER_DOOR, TRIGGER_DOOR5:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_DOOR_PANEL], IntToStr(Data.PanelID), True)] do
+                with ItemProps[InsertRow(MsgPropTrDoorPanel, IntToStr(Data.PanelID), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SILENT], BoolNames[Data.NoSound], True)] do
+                with ItemProps[InsertRow(MsgPropTrSilent, BoolNames[Data.NoSound], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_D2D], BoolNames[Data.d2d_doors], True)] do
+                with ItemProps[InsertRow(MsgPropTrD2d, BoolNames[Data.d2d_doors], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1009,19 +1037,19 @@ begin
 
             TRIGGER_CLOSETRAP, TRIGGER_TRAP:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_TRAP_PANEL], IntToStr(Data.PanelID), True)] do
+                with ItemProps[InsertRow(MsgPropTrTrapPanel, IntToStr(Data.PanelID), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SILENT], BoolNames[Data.NoSound], True)] do
+                with ItemProps[InsertRow(MsgPropTrSilent, BoolNames[Data.NoSound], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_D2D], BoolNames[Data.d2d_doors], True)] do
+                with ItemProps[InsertRow(MsgPropTrD2d, BoolNames[Data.d2d_doors], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1031,33 +1059,33 @@ begin
             TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF,
             TRIGGER_ONOFF:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EX_AREA],
+                with ItemProps[InsertRow(MsgPropTrExArea,
                                  Format('(%d:%d %d:%d)', [Data.tX, Data.tY, Data.tWidth, Data.tHeight]), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EX_DELAY], IntToStr(Data.Wait), True)] do
+                with ItemProps[InsertRow(MsgPropTrExDelay, IntToStr(Data.Wait), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EX_COUNT], IntToStr(Data.Count), True)] do
+                with ItemProps[InsertRow(MsgPropTrExCount, IntToStr(Data.Count), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EX_MONSTER], IntToStr(Data.MonsterID-1), True)] do
+                with ItemProps[InsertRow(MsgPropTrExMonster, IntToStr(Data.MonsterID-1), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
                 if TriggerType = TRIGGER_PRESS then
-                  with ItemProps[InsertRow(_lc[I_PROP_TR_EX_RANDOM], BoolNames[Data.ExtRandom], True)] do
+                  with ItemProps[InsertRow(MsgPropTrExRandom, BoolNames[Data.ExtRandom], True)] do
                   begin
                     EditStyle := esPickList;
                     ReadOnly := True;
@@ -1069,19 +1097,19 @@ begin
 
             TRIGGER_LIFTUP, TRIGGER_LIFTDOWN, TRIGGER_LIFT:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_LIFT_PANEL], IntToStr(Data.PanelID), True)] do
+                with ItemProps[InsertRow(MsgPropTrLiftPanel, IntToStr(Data.PanelID), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SILENT], BoolNames[Data.NoSound], True)] do
+                with ItemProps[InsertRow(MsgPropTrSilent, BoolNames[Data.NoSound], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_D2D], BoolNames[Data.d2d_doors], True)] do
+                with ItemProps[InsertRow(MsgPropTrD2d, BoolNames[Data.d2d_doors], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1090,13 +1118,13 @@ begin
 
             TRIGGER_TEXTURE:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_TEXTURE_ONCE], BoolNames[Data.ActivateOnce], True)] do
+                with ItemProps[InsertRow(MsgPropTrTextureOnce, BoolNames[Data.ActivateOnce], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_TEXTURE_ANIM_ONCE], BoolNames[Data.AnimOnce], True)] do
+                with ItemProps[InsertRow(MsgPropTrTextureAnimOnce, BoolNames[Data.AnimOnce], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1105,37 +1133,38 @@ begin
 
             TRIGGER_SOUND:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SOUND_NAME], Data.SoundName, True)] do
+                str := win2utf(Data.SoundName);
+                with ItemProps[InsertRow(MsgPropTrSoundName, str, True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SOUND_VOLUME], IntToStr(Data.Volume), True)] do
+                with ItemProps[InsertRow(MsgPropTrSoundVolume, IntToStr(Data.Volume), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 3;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SOUND_PAN], IntToStr(Data.Pan), True)] do
+                with ItemProps[InsertRow(MsgPropTrSoundPan, IntToStr(Data.Pan), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 3;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SOUND_COUNT], IntToStr(Data.PlayCount), True)] do
+                with ItemProps[InsertRow(MsgPropTrSoundCount, IntToStr(Data.PlayCount), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 3;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SOUND_LOCAL], BoolNames[Data.Local], True)] do
+                with ItemProps[InsertRow(MsgPropTrSoundLocal, BoolNames[Data.Local], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SOUND_SWITCH], BoolNames[Data.SoundSwitch], True)] do
+                with ItemProps[InsertRow(MsgPropTrSoundSwitch, BoolNames[Data.SoundSwitch], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1144,70 +1173,70 @@ begin
 
             TRIGGER_SPAWNMONSTER:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_MONSTER_TYPE], MonsterToStr(Data.MonType), True)] do
+                with ItemProps[InsertRow(MsgPropTrMonsterType, MonsterToStr(Data.MonType), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SPAWN_TO],
+                with ItemProps[InsertRow(MsgPropTrSpawnTo,
                                  Format('(%d:%d)', [Data.MonPos.X, Data.MonPos.Y]), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_DIRECTION], DirNames[TDirection(Data.MonDir)], True)] do
+                with ItemProps[InsertRow(MsgPropDirection, DirNames[TDirection(Data.MonDir)], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_HEALTH], IntToStr(Data.MonHealth), True)] do
+                with ItemProps[InsertRow(MsgPropTrHealth, IntToStr(Data.MonHealth), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_MONSTER_ACTIVE], BoolNames[Data.MonActive], True)] do
+                with ItemProps[InsertRow(MsgPropTrMonsterActive, BoolNames[Data.MonActive], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_COUNT], IntToStr(Data.MonCount), True)] do
+                with ItemProps[InsertRow(MsgPropTrCount, IntToStr(Data.MonCount), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_FX_TYPE], EffectToStr(Data.MonEffect), True)] do
+                with ItemProps[InsertRow(MsgPropTrFxType, EffectToStr(Data.MonEffect), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SPAWN_MAX], IntToStr(Data.MonMax), True)] do
+                with ItemProps[InsertRow(MsgPropTrSpawnMax, IntToStr(Data.MonMax), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SPAWN_DELAY], IntToStr(Data.MonDelay), True)] do
+                with ItemProps[InsertRow(MsgPropTrSpawnDelay, IntToStr(Data.MonDelay), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
                 case Data.MonBehav of
-                  1: str := _lc[I_PROP_TR_MONSTER_BEHAVIOUR_1];
-                  2: str := _lc[I_PROP_TR_MONSTER_BEHAVIOUR_2];
-                  3: str := _lc[I_PROP_TR_MONSTER_BEHAVIOUR_3];
-                  4: str := _lc[I_PROP_TR_MONSTER_BEHAVIOUR_4];
-                  5: str := _lc[I_PROP_TR_MONSTER_BEHAVIOUR_5];
-                  else str := _lc[I_PROP_TR_MONSTER_BEHAVIOUR_0];
+                  1: str := MsgPropTrMonsterBehaviour1;
+                  2: str := MsgPropTrMonsterBehaviour2;
+                  3: str := MsgPropTrMonsterBehaviour3;
+                  4: str := MsgPropTrMonsterBehaviour4;
+                  5: str := MsgPropTrMonsterBehaviour5;
+                  else str := MsgPropTrMonsterBehaviour0;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_MONSTER_BEHAVIOUR], str, True)] do
+                with ItemProps[InsertRow(MsgPropTrMonsterBehaviour, str, True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1216,50 +1245,50 @@ begin
 
             TRIGGER_SPAWNITEM:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_ITEM_TYPE], ItemToStr(Data.ItemType), True)] do
+                with ItemProps[InsertRow(MsgPropTrItemType, ItemToStr(Data.ItemType), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SPAWN_TO],
+                with ItemProps[InsertRow(MsgPropTrSpawnTo,
                                  Format('(%d:%d)', [Data.ItemPos.X, Data.ItemPos.Y]), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_DM_ONLY], BoolNames[Data.ItemOnlyDM], True)] do
+                with ItemProps[InsertRow(MsgPropDmOnly, BoolNames[Data.ItemOnlyDM], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_ITEM_FALLS], BoolNames[Data.ItemFalls], True)] do
+                with ItemProps[InsertRow(MsgPropItemFalls, BoolNames[Data.ItemFalls], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_COUNT], IntToStr(Data.ItemCount), True)] do
+                with ItemProps[InsertRow(MsgPropTrCount, IntToStr(Data.ItemCount), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_FX_TYPE], EffectToStr(Data.ItemEffect), True)] do
+                with ItemProps[InsertRow(MsgPropTrFxType, EffectToStr(Data.ItemEffect), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SPAWN_MAX], IntToStr(Data.ItemMax), True)] do
+                with ItemProps[InsertRow(MsgPropTrSpawnMax, IntToStr(Data.ItemMax), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SPAWN_DELAY], IntToStr(Data.ItemDelay), True)] do
+                with ItemProps[InsertRow(MsgPropTrSpawnDelay, IntToStr(Data.ItemDelay), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
@@ -1268,18 +1297,19 @@ begin
 
            TRIGGER_MUSIC:
              begin
-               with ItemProps[InsertRow(_lc[I_PROP_TR_MUSIC_NAME], Data.MusicName, True)] do
+               str := win2utf(Data.MusicName);
+               with ItemProps[InsertRow(MsgPropTrMusicName, str, True)] do
                begin
                  EditStyle := esEllipsis;
                  ReadOnly := True;
                end;
 
                if Data.MusicAction = 1 then
-                 str := _lc[I_PROP_TR_MUSIC_ON]
+                 str := MsgPropTrMusicOn
                else
-                 str := _lc[I_PROP_TR_MUSIC_OFF];
+                 str := MsgPropTrMusicOff;
 
-               with ItemProps[InsertRow(_lc[I_PROP_TR_MUSIC_ACT], str, True)] do
+               with ItemProps[InsertRow(MsgPropTrMusicAct, str, True)] do
                begin
                  EditStyle := esPickList;
                  ReadOnly := True;
@@ -1288,17 +1318,17 @@ begin
 
             TRIGGER_PUSH:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_PUSH_ANGLE], IntToStr(Data.PushAngle), True)] do
+                with ItemProps[InsertRow(MsgPropTrPushAngle, IntToStr(Data.PushAngle), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 4;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_PUSH_FORCE], IntToStr(Data.PushForce), True)] do
+                with ItemProps[InsertRow(MsgPropTrPushForce, IntToStr(Data.PushForce), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 4;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_PUSH_RESET], BoolNames[Data.ResetVel], True)] do
+                with ItemProps[InsertRow(MsgPropTrPushReset, BoolNames[Data.ResetVel], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1308,38 +1338,38 @@ begin
             TRIGGER_SCORE:
               begin
                 case Data.ScoreAction of
-                  1: str := _lc[I_PROP_TR_SCORE_ACT_1];
-                  2: str := _lc[I_PROP_TR_SCORE_ACT_2];
-                  3: str := _lc[I_PROP_TR_SCORE_ACT_3];
-                  else str := _lc[I_PROP_TR_SCORE_ACT_0];
+                  1: str := MsgPropTrScoreAct1;
+                  2: str := MsgPropTrScoreAct2;
+                  3: str := MsgPropTrScoreAct3;
+                  else str := MsgPropTrScoreAct0;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SCORE_ACT], str, True)] do
+                with ItemProps[InsertRow(MsgPropTrScoreAct, str, True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_COUNT], IntToStr(Data.ScoreCount), True)] do
+                with ItemProps[InsertRow(MsgPropTrCount, IntToStr(Data.ScoreCount), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 3;
                 end;
                 case Data.ScoreTeam of
-                  1: str := _lc[I_PROP_TR_SCORE_TEAM_1];
-                  2: str := _lc[I_PROP_TR_SCORE_TEAM_2];
-                  3: str := _lc[I_PROP_TR_SCORE_TEAM_3];
-                  else str := _lc[I_PROP_TR_SCORE_TEAM_0];
+                  1: str := MsgPropTrScoreTeam1;
+                  2: str := MsgPropTrScoreTeam2;
+                  3: str := MsgPropTrScoreTeam3;
+                  else str := MsgPropTrScoreTeam0;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SCORE_TEAM], str, True)] do
+                with ItemProps[InsertRow(MsgPropTrScoreTeam, str, True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SCORE_CON], BoolNames[Data.ScoreCon], True)] do
+                with ItemProps[InsertRow(MsgPropTrScoreCon, BoolNames[Data.ScoreCon], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SCORE_MSG], BoolNames[Data.ScoreMsg], True)] do
+                with ItemProps[InsertRow(MsgPropTrScoreMsg, BoolNames[Data.ScoreMsg], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1349,33 +1379,34 @@ begin
             TRIGGER_MESSAGE:
               begin
                 case Data.MessageKind of
-                  1: str := _lc[I_PROP_TR_MESSAGE_KIND_1];
-                  else str := _lc[I_PROP_TR_MESSAGE_KIND_0];
+                  1: str := MsgPropTrMessageKind1;
+                  else str := MsgPropTrMessageKind0;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_MESSAGE_KIND], str, True)] do
+                with ItemProps[InsertRow(MsgPropTrMessageKind, str, True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
                 case Data.MessageSendTo of
-                  1: str := _lc[I_PROP_TR_MESSAGE_TO_1];
-                  2: str := _lc[I_PROP_TR_MESSAGE_TO_2];
-                  3: str := _lc[I_PROP_TR_MESSAGE_TO_3];
-                  4: str := _lc[I_PROP_TR_MESSAGE_TO_4];
-                  5: str := _lc[I_PROP_TR_MESSAGE_TO_5];
-                  else str := _lc[I_PROP_TR_MESSAGE_TO_0];
+                  1: str := MsgPropTrMessageTo1;
+                  2: str := MsgPropTrMessageTo2;
+                  3: str := MsgPropTrMessageTo3;
+                  4: str := MsgPropTrMessageTo4;
+                  5: str := MsgPropTrMessageTo5;
+                  else str := MsgPropTrMessageTo0;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_MESSAGE_TO], str, True)] do
+                with ItemProps[InsertRow(MsgPropTrMessageTo, str, True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_MESSAGE_TEXT], Data.MessageText, True)] do
+                str := win2utf(Data.MessageText);
+                with ItemProps[InsertRow(MsgPropTrMessageText, str, True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 100;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_MESSAGE_TIME], IntToStr(Data.MessageTime), True)] do
+                with ItemProps[InsertRow(MsgPropTrMessageTime, IntToStr(Data.MessageTime), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
@@ -1384,36 +1415,50 @@ begin
 
             TRIGGER_DAMAGE:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_DAMAGE_VALUE], IntToStr(Data.DamageValue), True)] do
+                with ItemProps[InsertRow(MsgPropTrDamageValue, IntToStr(Data.DamageValue), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_INTERVAL], IntToStr(Data.DamageInterval), True)] do
+                with ItemProps[InsertRow(MsgPropTrInterval, IntToStr(Data.DamageInterval), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
+                case Data.DamageKind of
+                  3: str := MsgPropTrDamageKind3;
+                  4: str := MsgPropTrDamageKind4;
+                  5: str := MsgPropTrDamageKind5;
+                  6: str := MsgPropTrDamageKind6;
+                  7: str := MsgPropTrDamageKind7;
+                  8: str := MsgPropTrDamageKind8;
+                  else str := MsgPropTrDamageKind0;
+                end;
+                with ItemProps[InsertRow(MsgPropTrDamageKind, str, True)] do
+                begin
+                  EditStyle := esPickList;
+                  ReadOnly := True;
+                end;
               end;
 
             TRIGGER_HEALTH:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_HEALTH], IntToStr(Data.HealValue), True)] do
+                with ItemProps[InsertRow(MsgPropTrHealth, IntToStr(Data.HealValue), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_INTERVAL], IntToStr(Data.HealInterval), True)] do
+                with ItemProps[InsertRow(MsgPropTrInterval, IntToStr(Data.HealInterval), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_HEALTH_MAX], BoolNames[Data.HealMax], True)] do
+                with ItemProps[InsertRow(MsgPropTrHealthMax, BoolNames[Data.HealMax], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SILENT], BoolNames[Data.HealSilent], True)] do
+                with ItemProps[InsertRow(MsgPropTrSilent, BoolNames[Data.HealSilent], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1422,89 +1467,89 @@ begin
 
             TRIGGER_SHOT:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_TYPE], ShotToStr(Data.ShotType), True)] do
+                with ItemProps[InsertRow(MsgPropTrShotType, ShotToStr(Data.ShotType), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_SOUND], BoolNames[Data.ShotSound], True)] do
+                with ItemProps[InsertRow(MsgPropTrShotSound, BoolNames[Data.ShotSound], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_PANEL], IntToStr(Data.ShotPanelID), True)] do
+                with ItemProps[InsertRow(MsgPropTrShotPanel, IntToStr(Data.ShotPanelID), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
                 case Data.ShotTarget of
-                  1: str := _lc[I_PROP_TR_SHOT_TO_1];
-                  2: str := _lc[I_PROP_TR_SHOT_TO_2];
-                  3: str := _lc[I_PROP_TR_SHOT_TO_3];
-                  4: str := _lc[I_PROP_TR_SHOT_TO_4];
-                  5: str := _lc[I_PROP_TR_SHOT_TO_5];
-                  6: str := _lc[I_PROP_TR_SHOT_TO_6];
-                  else str := _lc[I_PROP_TR_SHOT_TO_0];
+                  1: str := MsgPropTrShotTo1;
+                  2: str := MsgPropTrShotTo2;
+                  3: str := MsgPropTrShotTo3;
+                  4: str := MsgPropTrShotTo4;
+                  5: str := MsgPropTrShotTo5;
+                  6: str := MsgPropTrShotTo6;
+                  else str := MsgPropTrShotTo0;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_TO], str, True)] do
+                with ItemProps[InsertRow(MsgPropTrShotTo, str, True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_SIGHT], IntToStr(Data.ShotIntSight), True)] do
+                with ItemProps[InsertRow(MsgPropTrShotSight, IntToStr(Data.ShotIntSight), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 3;
                 end;
 
                 case Data.ShotAim of
-                  1: str := _lc[I_PROP_TR_SHOT_AIM_1];
-                  2: str := _lc[I_PROP_TR_SHOT_AIM_2];
-                  3: str := _lc[I_PROP_TR_SHOT_AIM_3];
-                  else str := _lc[I_PROP_TR_SHOT_AIM_0];
+                  1: str := MsgPropTrShotAim1;
+                  2: str := MsgPropTrShotAim2;
+                  3: str := MsgPropTrShotAim3;
+                  else str := MsgPropTrShotAim0;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_AIM], str, True)-1] do
+                with ItemProps[InsertRow(MsgPropTrShotAim, str, True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SPAWN_TO],
+                with ItemProps[InsertRow(MsgPropTrSpawnTo,
                                  Format('(%d:%d)', [Data.ShotPos.X, Data.ShotPos.Y]), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_ANGLE], IntToStr(Data.ShotAngle), True)] do
+                with ItemProps[InsertRow(MsgPropTrShotAngle, IntToStr(Data.ShotAngle), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 4;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EX_DELAY], IntToStr(Data.ShotWait), True)] do
+                with ItemProps[InsertRow(MsgPropTrExDelay, IntToStr(Data.ShotWait), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_ACC], IntToStr(Data.ShotAccuracy), True)] do
+                with ItemProps[InsertRow(MsgPropTrShotAcc, IntToStr(Data.ShotAccuracy), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_AMMO], IntToStr(Data.ShotAmmo), True)] do
+                with ItemProps[InsertRow(MsgPropTrShotAmmo, IntToStr(Data.ShotAmmo), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_RELOAD], IntToStr(Data.ShotIntReload), True)] do
+                with ItemProps[InsertRow(MsgPropTrShotReload, IntToStr(Data.ShotIntReload), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 4;
@@ -1513,17 +1558,17 @@ begin
 
             TRIGGER_EFFECT:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_COUNT], IntToStr(Data.FXCount), True)] do
+                with ItemProps[InsertRow(MsgPropTrCount, IntToStr(Data.FXCount), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 3;
                 end;
 
                 if Data.FXType = 0 then
-                  str := _lc[I_PROP_TR_EFFECT_PARTICLE]
+                  str := MsgPropTrEffectParticle
                 else
-                  str := _lc[I_PROP_TR_EFFECT_ANIMATION];
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EFFECT_TYPE], str, True)] do
+                  str := MsgPropTrEffectAnimation;
+                with ItemProps[InsertRow(MsgPropTrEffectType, str, True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
@@ -1533,17 +1578,17 @@ begin
                 if Data.FXType = 0 then
                   case Data.FXSubType of
                     TRIGGER_EFFECT_SLIQUID:
-                      str := _lc[I_PROP_TR_EFFECT_SLIQUID];
+                      str := MsgPropTrEffectSliquid;
                     TRIGGER_EFFECT_LLIQUID:
-                      str := _lc[I_PROP_TR_EFFECT_LLIQUID];
+                      str := MsgPropTrEffectLliquid;
                     TRIGGER_EFFECT_DLIQUID:
-                      str := _lc[I_PROP_TR_EFFECT_DLIQUID];
+                      str := MsgPropTrEffectDliquid;
                     TRIGGER_EFFECT_BLOOD:
-                      str := _lc[I_PROP_TR_EFFECT_BLOOD];
+                      str := MsgPropTrEffectBlood;
                     TRIGGER_EFFECT_SPARK:
-                      str := _lc[I_PROP_TR_EFFECT_SPARK];
+                      str := MsgPropTrEffectSpark;
                     TRIGGER_EFFECT_BUBBLE:
-                      str := _lc[I_PROP_TR_EFFECT_BUBBLE];
+                      str := MsgPropTrEffectBubble;
                   end;
                 if Data.FXType = 1 then
                 begin
@@ -1551,61 +1596,61 @@ begin
                     Data.FXSubType := EFFECT_TELEPORT;
                   str := EffectToStr(Data.FXSubType);
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EFFECT_SUBTYPE], str, True)] do
+                with ItemProps[InsertRow(MsgPropTrEffectSubtype, str, True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EFFECT_COLOR], IntToStr(Data.FXColorR or (Data.FXColorG shl 8) or (Data.FXColorB shl 16)), True)] do
+                with ItemProps[InsertRow(MsgPropTrEffectColor, IntToStr(Data.FXColorR or (Data.FXColorG shl 8) or (Data.FXColorB shl 16)), True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EFFECT_CENTER], BoolNames[Data.FXPos = 0], True)] do
+                with ItemProps[InsertRow(MsgPropTrEffectCenter, BoolNames[Data.FXPos = 0], True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EX_DELAY], IntToStr(Data.FXWait), True)] do
+                with ItemProps[InsertRow(MsgPropTrExDelay, IntToStr(Data.FXWait), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EFFECT_VELX], IntToStr(Data.FXVelX), True)] do
+                with ItemProps[InsertRow(MsgPropTrEffectVelx, IntToStr(Data.FXVelX), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 4;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EFFECT_VELY], IntToStr(Data.FXVelY), True)] do
+                with ItemProps[InsertRow(MsgPropTrEffectVely, IntToStr(Data.FXVelY), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 4;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EFFECT_SPL], IntToStr(Data.FXSpreadL), True)] do
+                with ItemProps[InsertRow(MsgPropTrEffectSpl, IntToStr(Data.FXSpreadL), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 3;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EFFECT_SPR], IntToStr(Data.FXSpreadR), True)] do
+                with ItemProps[InsertRow(MsgPropTrEffectSpr, IntToStr(Data.FXSpreadR), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 3;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EFFECT_SPU], IntToStr(Data.FXSpreadU), True)] do
+                with ItemProps[InsertRow(MsgPropTrEffectSpu, IntToStr(Data.FXSpreadU), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 3;
                 end;
 
-                with ItemProps[InsertRow(_lc[I_PROP_TR_EFFECT_SPD], IntToStr(Data.FXSpreadD), True)] do
+                with ItemProps[InsertRow(MsgPropTrEffectSpd, IntToStr(Data.FXSpreadD), True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 3;
@@ -1761,6 +1806,7 @@ begin
   RemoveSelectFromObjects();
 
   MainForm.miUndo.Enabled := UndoBuffer <> nil;
+  MainForm.RecountSelectedObjects();
 end;
 
 procedure Undo_Add(ObjectType: Byte; ID: DWORD; Group: Boolean = False);
@@ -1812,7 +1858,7 @@ end;
 
 procedure ErrorMessageBox(str: String);
 begin
-  MessageBox(0, PChar(str), PChar(_lc[I_MSG_ERROR]),
+  Application.MessageBox(PChar(str), PChar(MsgMsgError),
              MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1);
 end;
 
@@ -1828,41 +1874,41 @@ begin
     with gPanels[SelectedObjects[_id].ID] do
     begin
       if TextureWidth <> 0 then
-        if StrToIntDef(MainForm.vleObjectProperty.Values[_lc[I_PROP_WIDTH]], 1) mod TextureWidth <> 0 then
+        if StrToIntDef(MainForm.vleObjectProperty.Values[MsgPropWidth], 1) mod TextureWidth <> 0 then
         begin
-          ErrorMessageBox(Format(_lc[I_MSG_WRONG_TEXWIDTH],
+          ErrorMessageBox(Format(MsgMsgWrongTexwidth,
                                  [TextureWidth]));
           Exit;
         end;
 
       if TextureHeight <> 0 then
-        if StrToIntDef(Trim(MainForm.vleObjectProperty.Values[_lc[I_PROP_HEIGHT]]), 1) mod TextureHeight <> 0 then
+        if StrToIntDef(Trim(MainForm.vleObjectProperty.Values[MsgPropHeight]), 1) mod TextureHeight <> 0 then
         begin
-          ErrorMessageBox(Format(_lc[I_MSG_WRONG_TEXHEIGHT],
+          ErrorMessageBox(Format(MsgMsgWrongTexheight,
                                  [TextureHeight]));
           Exit;
         end;
 
       if IsTexturedPanel(PanelType) and (TextureName <> '') then
-        if not (StrToIntDef(MainForm.vleObjectProperty.Values[_lc[I_PROP_PANEL_ALPHA]], -1) in [0..255]) then
+        if not (StrToIntDef(MainForm.vleObjectProperty.Values[MsgPropPanelAlpha], -1) in [0..255]) then
         begin
-          ErrorMessageBox(_lc[I_MSG_WRONG_ALPHA]);
+          ErrorMessageBox(MsgMsgWrongAlpha);
           Exit;
         end;
     end;
 
   if SelectedObjects[_id].ObjectType in [OBJECT_PANEL, OBJECT_TRIGGER] then
-    if (StrToIntDef(MainForm.vleObjectProperty.Values[_lc[I_PROP_WIDTH]], 0) <= 0) or
-       (StrToIntDef(MainForm.vleObjectProperty.Values[_lc[I_PROP_HEIGHT]], 0) <= 0) then
+    if (StrToIntDef(MainForm.vleObjectProperty.Values[MsgPropWidth], 0) <= 0) or
+       (StrToIntDef(MainForm.vleObjectProperty.Values[MsgPropHeight], 0) <= 0) then
     begin
-      ErrorMessageBox(_lc[I_MSG_WRONG_SIZE]);
+      ErrorMessageBox(MsgMsgWrongSize);
       Exit;
     end;
 
-  if (Trim(MainForm.vleObjectProperty.Values[_lc[I_PROP_X]]) = '') or
-     (Trim(MainForm.vleObjectProperty.Values[_lc[I_PROP_Y]]) = '') then
+  if (Trim(MainForm.vleObjectProperty.Values[MsgPropX]) = '') or
+     (Trim(MainForm.vleObjectProperty.Values[MsgPropY]) = '') then
   begin
-    ErrorMessageBox(_lc[I_MSG_WRONG_XY]);
+    ErrorMessageBox(MsgMsgWrongXy);
     Exit;
   end;
 
@@ -1881,47 +1927,52 @@ var
   ok: Boolean;
   FileName: String;
   ResourceName: String;
-  UResourceName: String;
   FullResourceName: String;
   SectionName: String;
   Data: Pointer;
   Width, Height: Word;
   fn: String;
 begin
+  Data := nil;
+  FrameLen := 0;
+  Width := 0;
+  Height := 0;
+
   if aSection = '..' then
     SectionName := ''
   else
     SectionName := aSection;
 
-  if aWAD = _lc[I_WAD_SPECIAL_MAP] then
+  if aWAD = '' then
+    aWAD := MsgWadSpecialMap;
+
+  if aWAD = MsgWadSpecialMap then
     begin // Файл карты
       g_ProcessResourceStr(OpenedMap, @fn, nil, nil);
-    //FileName := EditorDir+'maps\'+ExtractFileName(fn);
       FileName := fn;
       ResourceName := ':'+SectionName+'\'+aTex;
     end
   else
-    if aWAD = _lc[I_WAD_SPECIAL_TEXS] then
+    if aWAD = MsgWadSpecialTexs then
       begin // Спец. текстуры
         FileName := '';
         ResourceName := aTex;
       end
     else
       begin // Внешний WAD
-        FileName := EditorDir+'wads/'+aWAD;
+        FileName := WadsDir + DirectorySeparator + aWAD;
         ResourceName := aWAD+':'+SectionName+'\'+aTex;
       end;
 
   ok := True;
-  UResourceName := win2utf(ResourceName);
 
 // Есть ли уже такая текстура:
   for a := 0 to MainForm.lbTextureList.Items.Count-1 do
-    if UResourceName = MainForm.lbTextureList.Items[a] then
+    if ResourceName = MainForm.lbTextureList.Items[a] then
     begin
       if not silent then
-        ErrorMessageBox(Format(_lc[I_MSG_TEXTURE_ALREADY],
-                               [UResourceName]));
+        ErrorMessageBox(Format(MsgMsgTextureAlready,
+                               [ResourceName]));
       ok := False;
     end;
 
@@ -1929,17 +1980,17 @@ begin
   if Length(ResourceName) > 64 then
   begin
     if not silent then
-      ErrorMessageBox(Format(_lc[I_MSG_RES_NAME_64],
-                             [UResourceName]));
+      ErrorMessageBox(Format(MsgMsgResName64,
+                             [ResourceName]));
     ok := False;
   end;
 
   if ok then
   begin
     a := -1;
-    if aWAD = _lc[I_WAD_SPECIAL_TEXS] then
+    if aWAD = MsgWadSpecialTexs then
     begin
-      a := MainForm.lbTextureList.Items.Add(UResourceName);
+      a := MainForm.lbTextureList.Items.Add(ResourceName);
       if not silent then
         SelectTexture(a);
       Result := True;
@@ -1952,14 +2003,21 @@ begin
       begin // Аним. текстура
         GetFrame(FullResourceName, Data, FrameLen, Width, Height);
 
-        if g_CreateTextureMemorySize(Data, FrameLen, ResourceName, 0, 0, Width, Height, 1) then
-          a := MainForm.lbTextureList.Items.Add(UResourceName);
+        if not g_CreateTextureMemorySize(Data, FrameLen, ResourceName, 0, 0, Width, Height, 1) then
+          ok := False;
+        a := MainForm.lbTextureList.Items.Add(ResourceName);
       end
     else // Обычная текстура
       begin
-        if g_CreateTextureWAD(ResourceName, FullResourceName) then
-          a := MainForm.lbTextureList.Items.Add(UResourceName);
+        if not g_CreateTextureWAD(ResourceName, FullResourceName) then
+          ok := False;
+        a := MainForm.lbTextureList.Items.Add(ResourceName);
       end;
+    if (not ok) and (slInvalidTextures.IndexOf(ResourceName) = -1) then
+    begin
+      slInvalidTextures.Add(ResourceName);
+      ok := True;
+    end;
     if (a > -1) and (not silent) then
       SelectTexture(a);
   end;
@@ -1987,6 +2045,7 @@ var
   MapName: String;
   idx: Integer;
 begin
+  SelectMapForm.Caption := MsgCapOpen;
   SelectMapForm.GetMaps(FileName);
 
   if (FileName = OpenedWAD) and
@@ -2047,7 +2106,7 @@ begin
     lbTextureList.Sorted := True;
     lbTextureList.Sorted := False;
 
-    UpdateCaption(win2utf(gMapInfo.Name), ExtractFileName(FileName), MapName);
+    UpdateCaption(gMapInfo.Name, ExtractFileName(FileName), MapName);
   end;
 end;
 
@@ -2200,6 +2259,7 @@ procedure SwitchMap();
 begin
   ShowMap := not ShowMap;
   MainForm.tbShowMap.Down := ShowMap;
+  MainForm.miMiniMap.Checked := ShowMap;
 end;
 
 procedure ShowEdges();
@@ -2208,12 +2268,13 @@ begin
     drEdge[3] := 255
   else
     drEdge[3] := gAlphaEdge;
+  MainForm.miShowEdges.Checked := drEdge[3] <> 255;
 end;
 
 function SelectedTexture(): String;
 begin
   if MainForm.lbTextureList.ItemIndex <> -1 then
-    Result := utf2win(MainForm.lbTextureList.Items[MainForm.lbTextureList.ItemIndex])
+    Result := MainForm.lbTextureList.Items[MainForm.lbTextureList.ItemIndex]
   else
     Result := '';
 end;
@@ -2221,7 +2282,7 @@ end;
 function IsSpecialTextureSel(): Boolean;
 begin
   Result := (MainForm.lbTextureList.ItemIndex <> -1) and
-            IsSpecialTexture(utf2win(MainForm.lbTextureList.Items[MainForm.lbTextureList.ItemIndex]));
+            IsSpecialTexture(MainForm.lbTextureList.Items[MainForm.lbTextureList.ItemIndex]);
 end;
 
 function CopyBufferToString(var CopyBuf: TCopyRecArray): String;
@@ -2317,7 +2378,8 @@ begin
   Result := Res;
 end;
 
-procedure StringToCopyBuffer(Str: String; var CopyBuf: TCopyRecArray);
+procedure StringToCopyBuffer(Str: String; var CopyBuf: TCopyRecArray;
+  var pmin: TPoint);
 var
   i, j, t: Integer;
 
@@ -2399,6 +2461,8 @@ begin
             PanelType := StrToIntDef(GetNext(), PANEL_WALL);
             X := StrToIntDef(GetNext(), 0);
             Y := StrToIntDef(GetNext(), 0);
+            pmin.X := Min(X, pmin.X);
+            pmin.Y := Min(Y, pmin.Y);
             Width := StrToIntDef(GetNext(), 16);
             Height := StrToIntDef(GetNext(), 16);
             TextureName := GetNext();
@@ -2413,6 +2477,8 @@ begin
           ItemType := StrToIntDef(GetNext(), ITEM_MEDKIT_SMALL);
           X := StrToIntDef(GetNext(), 0);
           Y := StrToIntDef(GetNext(), 0);
+          pmin.X := Min(X, pmin.X);
+          pmin.Y := Min(Y, pmin.Y);
           OnlyDM := (GetNext() = '1');
           Fall := (GetNext() = '1');
         end;
@@ -2423,6 +2489,8 @@ begin
           MonsterType := StrToIntDef(GetNext(), MONSTER_DEMON);
           X := StrToIntDef(GetNext(), 0);
           Y := StrToIntDef(GetNext(), 0);
+          pmin.X := Min(X, pmin.X);
+          pmin.Y := Min(Y, pmin.Y);
 
           if GetNext() = '1' then
             Direction := D_LEFT
@@ -2436,6 +2504,8 @@ begin
           AreaType := StrToIntDef(GetNext(), AREA_PLAYERPOINT1);
           X := StrToIntDef(GetNext(), 0);
           Y := StrToIntDef(GetNext(), 0);
+          pmin.X := Min(X, pmin.X);
+          pmin.Y := Min(Y, pmin.Y);
           if GetNext() = '1' then
             Direction := D_LEFT
           else
@@ -2448,6 +2518,8 @@ begin
           TriggerType := StrToIntDef(GetNext(), TRIGGER_EXIT);
           X := StrToIntDef(GetNext(), 0);
           Y := StrToIntDef(GetNext(), 0);
+          pmin.X := Min(X, pmin.X);
+          pmin.Y := Min(Y, pmin.Y);
           Width := StrToIntDef(GetNext(), 16);
           Height := StrToIntDef(GetNext(), 16);
           ActivateType := StrToIntDef(GetNext(), 0);
@@ -2457,6 +2529,34 @@ begin
 
           for j := 0 to 127 do
             Data.Default[j] := StrToIntDef(GetNext(), 0);
+
+          case TriggerType of
+            TRIGGER_TELEPORT:
+              begin
+                pmin.X := Min(Data.TargetPoint.X, pmin.X);
+                pmin.Y := Min(Data.TargetPoint.Y, pmin.Y);
+              end;
+            TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF:
+              begin
+                pmin.X := Min(Data.tX, pmin.X);
+                pmin.Y := Min(Data.tY, pmin.Y);
+              end;
+            TRIGGER_SPAWNMONSTER:
+              begin
+                pmin.X := Min(Data.MonPos.X, pmin.X);
+                pmin.Y := Min(Data.MonPos.Y, pmin.Y);
+              end;
+            TRIGGER_SPAWNITEM:
+              begin
+                pmin.X := Min(Data.ItemPos.X, pmin.X);
+                pmin.Y := Min(Data.ItemPos.Y, pmin.Y);
+              end;
+            TRIGGER_SHOT:
+              begin
+                pmin.X := Min(Data.ShotPos.X, pmin.X);
+                pmin.Y := Min(Data.ShotPos.Y, pmin.Y);
+              end;
+          end;
         end;
     end;
   end;
@@ -2466,63 +2566,105 @@ end;
 //Закончились вспомогательные процедуры
 //----------------------------------------
 
-procedure TMainForm.RefreshRecentMenu();
-var
-  i: Integer;
-  MI: TMenuItem;
+type
+  TRecentHandler = class
+    private
+      FForm: TMainForm;
+      FPath: String;
+    public
+      constructor Create (form: TMainForm; path: String);
+      procedure Execute (Sender: TObject);
+  end;
+
+constructor TRecentHandler.Create (form: TMainForm; path: String);
 begin
-// Лишние запомненные карты:
-  while RecentFiles.Count > RecentCount do
-    RecentFiles.Delete(RecentFiles.Count-1);
+  Assert(form <> nil);
+  FForm := form;
+  FPath := path;
+end;
 
-// Лишние строки меню:
-  while MainMenu.Items[0].Count > RECENT_FILES_MENU_START do
-    MainMenu.Items[0].Delete(MainMenu.Items[0].Count-1);
+procedure TRecentHandler.Execute (Sender: TObject);
+  var fn: AnsiString;
+begin
+  fn := g_ExtractWadName(FPath);
+  if FileExists(fn) then
+    OpenMap(fn, g_ExtractFilePathName(FPath))
+  else
+    Application.MessageBox('', 'File not available anymore', MB_OK);
+//  if Application.MessageBox(PChar(MsgMsgDelRecentPromt), PChar(MsgMsgDelRecent), MB_ICONQUESTION or MB_YESNO) = idYes then
+//  begin
+//    RecentFiles.Delete(n);
+//    RefreshRecentMenu();
+//  end;
+end;
 
-// Отделение списка карт от строки "Выход":
-  if RecentFiles.Count > 0 then
+procedure TMainForm.RefillRecentMenu (menu: TMenuItem; start: Integer; fmt: AnsiString);
+  var i: Integer; MI: TMenuItem; cb: TMethod; h: TRecentHandler; s: AnsiString;
+begin
+  Assert(menu <> nil);
+  Assert(start >= 0);
+  Assert(start <= menu.Count);
+
+  // clear all recent entries from menu
+  i := start;
+  while i < menu.Count do
   begin
-    MI := TMenuItem.Create(MainMenu.Items[0]);
-    MI.Caption := '-';
-    MainMenu.Items[0].Add(MI);
+    MI := menu.Items[i];
+    cb := TMethod(MI.OnClick);
+    if cb.Code = @TRecentHandler.Execute then
+    begin
+      // this is recent menu entry
+      // remove it and free callback handler
+      h := TRecentHandler(cb.Data);
+      menu.Delete(i);
+      MI.Free();
+      h.Free();
+    end
+    else
+      Inc(i);
   end;
 
-// Добавление в меню списка запомненных карт:
-  for i := 0 to RecentFiles.Count-1 do
+  // fill with a new ones
+  for i := 0 to RecentFiles.Count - 1 do
   begin
-    MI := TMenuItem.Create(MainMenu.Items[0]);
-    MI.Caption := IntToStr(i+1) + '  ' + RecentFiles[i];
-    MI.OnClick := aRecentFileExecute;
-    MainMenu.Items[0].Add(MI);
+    s := RecentFiles[i];
+    h := TRecentHandler.Create(self, s);
+    MI := TMenuItem.Create(menu);
+    MI.Caption := Format(fmt, [i + 1, g_ExtractWadNameNoPath(s), g_ExtractFilePathName(s)]);
+    MI.OnClick := h.Execute;
+    menu.Insert(start + i, MI);
   end;
 end;
 
-procedure TMainForm.aRecentFileExecute(Sender: TObject);
-var
-  n, pw: Integer;
-  s, fn: String;
+procedure TMainForm.RefreshRecentMenu();
+  var start: Integer;
 begin
-  s := LowerCase((Sender as TMenuItem).Caption);
-  Delete(s, Pos('&', s), 1);
-  s := Trim(Copy(s, 1, 2));
-  n := StrToIntDef(s, 0) - 1;
+  while RecentFiles.Count > RecentCount do
+    RecentFiles.Delete(RecentFiles.Count - 1);
 
-  if (n < 0) or (n >= RecentFiles.Count) then
-    Exit;
+  if miMacRecentSubMenu.Visible then
+  begin
+    // Reconstruct OSX-like recent list
+    RefillRecentMenu(miMacRecentSubMenu, 0, '%1:s - %2:s');
+    miMacRecentEnd.Enabled := RecentFiles.Count <> 0;
+    miMacRecentEnd.Visible := RecentFiles.Count <> 0;
+  end;
 
-  s := RecentFiles[n];
-  pw := Pos('.wad:\', LowerCase(s));
-  
-  if pw > 0 then
-    begin // Map name included
-      fn := Copy(s, 1, pw + 3);
-      Delete(s, 1, pw + 5);
-      if (FileExists(fn)) then
-        OpenMap(fn, s);
-    end
-  else // Only wad name
-    if (FileExists(s)) then
-      OpenMap(s, '');
+  if miWinRecentStart.Visible then
+  begin
+    // Reconstruct Windows-like recent list
+    start := miMenuFile.IndexOf(miWinRecent);
+    if start < 0 then start := miMenuFile.Count else start := start + 1;
+    RefillRecentMenu(miMenuFile, start, '%0:d %1:s:%2:s');
+    miWinRecent.Enabled := False;
+    miWinRecent.Visible := RecentFiles.Count = 0;
+  end;
+end;
+
+procedure TMainForm.miMacRecentClearClick(Sender: TObject);
+begin
+  RecentFiles.Clear();
+  RefreshRecentMenu();
 end;
 
 procedure TMainForm.aEditorOptionsExecute(Sender: TObject);
@@ -2535,21 +2677,15 @@ var
   cwdt, chgt: Byte;
   spc: ShortInt;
   ID: DWORD;
-  wad: TWADEditor_1;
   cfgdata: Pointer;
   cfglen: Integer;
   config: TConfig;
 begin
-  cfglen := 0;
-
-  wad := TWADEditor_1.Create;
-  if wad.ReadFile(EditorDir+'data/Game.wad') then
-    wad.GetResource('FONTS', cfgres, cfgdata, cfglen);
-  wad.Free();
-
-  if cfglen <> 0 then
+  ID := 0;
+  g_ReadResource(GameWad, 'FONTS', cfgres, cfgdata, cfglen);
+  if cfgdata <> nil then
   begin
-    if not g_CreateTextureWAD('FONT_STD', EditorDir+'data/Game.wad:FONTS\'+texture) then
+    if not g_CreateTextureWAD('FONT_STD', GameWad + ':FONTS\' + texture) then
       e_WriteLog('ERROR ERROR ERROR', MSG_WARNING);
 
     config := TConfig.CreateMem(cfgdata, cfglen);
@@ -2558,14 +2694,15 @@ begin
     spc := Min(Max(config.ReadInt('FontMap', 'Kerning', 0), -128), 127);
 
     if g_GetTexture('FONT_STD', ID) then
-      e_TextureFontBuild(ID, FontID, cwdt, chgt, spc-2);
+      e_TextureFontBuild(ID, FontID, cwdt, chgt, spc - 2);
 
     config.Free();
+    FreeMem(cfgdata)
   end
   else
-    e_WriteLog('Could not load FONT_STD', MSG_WARNING);
-
-  if cfglen <> 0 then FreeMem(cfgdata);
+  begin
+    e_WriteLog('Could not load FONT_STD', MSG_WARNING)
+  end
 end;
 
 procedure TMainForm.FormCreate(Sender: TObject);
@@ -2576,9 +2713,69 @@ var
 begin
   Randomize();
 
-  EditorDir := ExtractFilePath(Application.ExeName);
-
-  e_InitLog(EditorDir+'Editor.log', WM_NEWFILE);
+  {$IFDEF DARWIN}
+    miApple.Enabled := True;
+    miApple.Visible := True;
+    miMacRecentSubMenu.Enabled := True;
+    miMacRecentSubMenu.Visible := True;
+    miWinRecentStart.Enabled := False;
+    miWinRecentStart.Visible := False;
+    miWinRecent.Enabled := False;
+    miWinRecent.Visible := False;
+    miLine2.Enabled := False;
+    miLine2.Visible := False;
+    miExit.Enabled := False;
+    miExit.Visible := False;
+    miOptions.Enabled := False;
+    miOptions.Visible := False;
+    miMenuWindow.Enabled := True;
+    miMenuWindow.Visible := True;
+    miAbout.Enabled := False;
+    miAbout.Visible := False;
+  {$ELSE}
+    miApple.Enabled := False;
+    miApple.Visible := False;
+    miMacRecentSubMenu.Enabled := False;
+    miMacRecentSubMenu.Visible := False;
+    miWinRecentStart.Enabled := True;
+    miWinRecentStart.Visible := True;
+    miWinRecent.Enabled := True;
+    miWinRecent.Visible := True;
+    miLine2.Enabled := True;
+    miLine2.Visible := True;
+    miExit.Enabled := True;
+    miExit.Visible := True;
+    miOptions.Enabled := True;
+    miOptions.Visible := True;
+    miMenuWindow.Enabled := False;
+    miMenuWindow.Visible := False;
+    miAbout.Enabled := True;
+    miAbout.Visible := True;
+  {$ENDIF}
+
+  miNewMap.ShortCut := ShortCut(VK_N, [ssModifier]);
+  miOpenMap.ShortCut := ShortCut(VK_O, [ssModifier]);
+  miSaveMap.ShortCut := ShortCut(VK_S, [ssModifier]);
+  {$IFDEF DARWIN}
+    miSaveMapAs.ShortCut := ShortCut(VK_S, [ssModifier, ssShift]);
+    miReopenMap.ShortCut := ShortCut(VK_F5, [ssModifier]);
+  {$ENDIF}
+  miUndo.ShortCut := ShortCut(VK_Z, [ssModifier]);
+  miCopy.ShortCut := ShortCut(VK_C, [ssModifier]);
+  miCut.ShortCut := ShortCut(VK_X, [ssModifier]);
+  miPaste.ShortCut := ShortCut(VK_V, [ssModifier]);
+  miSelectAll.ShortCut := ShortCut(VK_A, [ssModifier]);
+  miToFore.ShortCut := ShortCut(VK_LCL_CLOSE_BRACKET, [ssModifier]);
+  miToBack.ShortCut := ShortCut(VK_LCL_OPEN_BRACKET, [ssModifier]);
+  {$IFDEF DARWIN}
+    miMapOptions.Shortcut := ShortCut(VK_P, [ssModifier, ssAlt]);
+    selectall1.Shortcut := ShortCut(VK_A, [ssModifier, ssAlt]);
+  {$ENDIF}
+
+  e_WriteLog('Doom 2D: Forever Editor version ' + EDITOR_VERSION, MSG_NOTIFY);
+  e_WriteLog('Build date: ' + EDITOR_BUILDDATE + ' ' + EDITOR_BUILDTIME, MSG_NOTIFY);
+  e_WriteLog('Build hash: ' + g_GetBuildHash(), MSG_NOTIFY);
+  e_WriteLog('Build by: ' + g_GetBuilderName(), MSG_NOTIFY);
 
   slInvalidTextures := TStringList.Create;
 
@@ -2598,7 +2795,7 @@ begin
   OpenedMap := '';
   OpenedWAD := '';
 
-  config := TConfig.CreateFile(EditorDir+'Editor.cfg');
+  config := TConfig.CreateFile(CfgFileName);
 
   if config.ReadInt('Editor', 'XPos', -1) = -1 then
     Position := poDesktopCenter
@@ -2625,6 +2822,7 @@ begin
   DrawPanelSize := config.ReadBool('Editor', 'DrawPanelSize', True);
   BackColor := config.ReadInt('Editor', 'BackColor', $7F6040);
   PreviewColor := config.ReadInt('Editor', 'PreviewColor', $00FF00);
+  UseCheckerboard := config.ReadBool('Editor', 'UseCheckerboard', True);
   gColorEdge := config.ReadInt('Editor', 'EdgeColor', COLOR_EDGE);
   gAlphaEdge := config.ReadInt('Editor', 'EdgeAlpha', ALPHA_EDGE);
   if gAlphaEdge = 255 then
@@ -2642,6 +2840,8 @@ begin
   gAlphaTriggerArea := config.ReadInt('Editor', 'TriggerAlpha', ALPHA_AREA);
   if gAlphaTriggerArea = 255 then
     gAlphaTriggerArea := ALPHA_AREA;
+  gAlphaMonsterRect := config.ReadInt('Editor', 'MonsterRectAlpha', 0);
+  gAlphaAreaRect := config.ReadInt('Editor', 'AreaRectAlpha', 0);
   if config.ReadInt('Editor', 'Scale', 0) = 1 then
     Scale := 2
   else
@@ -2650,12 +2850,33 @@ begin
     DotSize := 2
   else
     DotSize := 1;
-  OpenDialog.InitialDir := config.ReadStr('Editor', 'LastOpenDir', EditorDir);
-  SaveDialog.InitialDir := config.ReadStr('Editor', 'LastSaveDir', EditorDir);
+  OpenDialog.InitialDir := config.ReadStr('Editor', 'LastOpenDir', MapsDir);
+  SaveDialog.InitialDir := config.ReadStr('Editor', 'LastSaveDir', MapsDir);
 
   s := config.ReadStr('Editor', 'Language', '');
   gLanguage := s;
 
+  Compress := config.ReadBool('Editor', 'Compress', True);
+  Backup := config.ReadBool('Editor', 'Backup', True);
+
+  TestGameMode := config.ReadStr('TestRun', 'GameMode', 'DM');
+  TestLimTime := config.ReadStr('TestRun', 'LimTime', '0');
+  TestLimScore := config.ReadStr('TestRun', 'LimScore', '0');
+  TestOptionsTwoPlayers := config.ReadBool('TestRun', 'TwoPlayers', False);
+  TestOptionsTeamDamage := config.ReadBool('TestRun', 'TeamDamage', False);
+  TestOptionsAllowExit := config.ReadBool('TestRun', 'AllowExit', True);
+  TestOptionsWeaponStay := config.ReadBool('TestRun', 'WeaponStay', False);
+  TestOptionsMonstersDM := config.ReadBool('TestRun', 'MonstersDM', False);
+  TestMapOnce := config.ReadBool('TestRun', 'MapOnce', False);
+  {$IF DEFINED(DARWIN)}
+    TestD2dExe := config.ReadStr('TestRun', 'ExeDrawin', GameExeFile);
+  {$ELSEIF DEFINED(WINDOWS)}
+    TestD2dExe := config.ReadStr('TestRun', 'ExeWindows', GameExeFile);
+  {$ELSE}
+    TestD2dExe := config.ReadStr('TestRun', 'ExeUnix', GameExeFile);
+  {$ENDIF}
+  TestD2DArgs := config.ReadStr('TestRun', 'Args', '');
+
   RecentCount := config.ReadInt('Editor', 'RecentCount', 5);
   if RecentCount > 10 then
     RecentCount := 10;
@@ -2665,7 +2886,11 @@ begin
   RecentFiles := TStringList.Create();
   for i := 0 to RecentCount-1 do
   begin
-    s := config.ReadStr('RecentFiles', IntToStr(i+1), '');
+    {$IFDEF WINDOWS}
+      s := config.ReadStr('RecentFilesWin', IntToStr(i), '');
+    {$ELSE}
+      s := config.ReadStr('RecentFilesUnix', IntToStr(i), '');
+    {$ENDIF}
     if s <> '' then
       RecentFiles.Add(s);
   end;
@@ -2676,7 +2901,7 @@ begin
   tbShowMap.Down := ShowMap;
   tbGridOn.Down := DotEnable;
   pcObjects.ActivePageIndex := 0;
-  Application.Title := _lc[I_EDITOR_TITLE];
+  Application.Title := MsgEditorTitle;
 
   Application.OnIdle := OnIdle;
 end;
@@ -2697,6 +2922,11 @@ var
   ObjCount: Word;
   aX, aY, aX2, aY2, XX, ScaleSz: Integer;
 begin
+  ID := 0;
+  PID := 0;
+  Width := 0;
+  Height := 0;
+
   e_BeginRender();
 
   e_Clear(GL_COLOR_BUFFER_BIT,
@@ -2741,30 +2971,46 @@ begin
   end;
 
 // Рисуем сетку:
-  if DotEnable and (not PreviewMode) then
+  if DotEnable and (PreviewMode = 0) then
   begin
     if DotSize = 2 then
       a := -1
     else
       a := 0;
 
-    for x := 0 to (RenderPanel.Width div DotStep) do
-      for y := 0 to (RenderPanel.Height div DotStep) do
-        e_DrawPoint(DotSize, x*DotStep + a, y*DotStep + a,
+    x := MapOffset.X mod DotStep;
+    y := MapOffset.Y mod DotStep;
+
+    while x < RenderPanel.Width do
+    begin
+      while y < RenderPanel.Height do
+      begin
+        e_DrawPoint(DotSize, x + a, y + a,
                     GetRValue(DotColor),
                     GetGValue(DotColor),
                     GetBValue(DotColor));
+        y += DotStep;
+      end;
+      x += DotStep;
+      y := MapOffset.Y mod DotStep;
+    end;
   end;
 
 // Превью текстуры:
   if (lbTextureList.ItemIndex <> -1) and (cbPreview.Checked) and
-     (not IsSpecialTextureSel()) and (not PreviewMode) then
+     (not IsSpecialTextureSel()) and (PreviewMode = 0) then
   begin
     if not g_GetTexture(SelectedTexture(), ID) then
       g_GetTexture('NOTEXTURE', ID);
     g_GetTextureSizeByID(ID, Width, Height);
-    if g_GetTexture('PREVIEW', PID) then
-       e_DrawFill(PID, RenderPanel.Width-Width, RenderPanel.Height-Height, Width div 16 + 1, Height div 16 + 1, 0, True, False);
+    if UseCheckerboard then
+    begin
+      if g_GetTexture('PREVIEW', PID) then
+        e_DrawFill(PID, RenderPanel.Width-Width, RenderPanel.Height-Height, Width div 16 + 1, Height div 16 + 1, 0, True, False);
+    end else
+      e_DrawFillQuad(RenderPanel.Width-Width-2, RenderPanel.Height-Height-2,
+                     RenderPanel.Width-1, RenderPanel.Height-1,
+                     GetRValue(PreviewColor), GetGValue(PreviewColor), GetBValue(PreviewColor), 0);
     e_Draw(ID, RenderPanel.Width-Width, RenderPanel.Height-Height, 0, True, False);
   end;
 
@@ -2782,7 +3028,7 @@ begin
 
     e_DrawFillQuad(MousePos.X, MousePos.Y, MousePos.X+180, MousePos.Y+18, 192, 192, 192, 127);
     e_DrawQuad(MousePos.X, MousePos.Y, MousePos.X+180, MousePos.Y+18, 255, 255, 255);
-    PrintBlack(MousePos.X+2, MousePos.Y+2, _glc[I_HINT_TELEPORT], gEditorFont);
+    PrintBlack(MousePos.X+2, MousePos.Y+2, utf8to1251(MsgHintTeleport), gEditorFont);
   end;
 
 // Подсказка при выборе точки появления:
@@ -2793,7 +3039,7 @@ begin
                0, 0, 255);
     e_DrawFillQuad(MousePos.X, MousePos.Y, MousePos.X+180, MousePos.Y+18, 192, 192, 192, 127);
     e_DrawQuad(MousePos.X, MousePos.Y, MousePos.X+180, MousePos.Y+18, 255, 255, 255);
-    PrintBlack(MousePos.X+2, MousePos.Y+2, _glc[I_HINT_SPAWN], gEditorFont);
+    PrintBlack(MousePos.X+2, MousePos.Y+2, utf8to1251(MsgHintSpawn), gEditorFont);
   end;
 
 // Подсказка при выборе панели двери:
@@ -2801,7 +3047,7 @@ begin
   begin
     e_DrawFillQuad(MousePos.X, MousePos.Y, MousePos.X+180, MousePos.Y+18, 192, 192, 192, 127);
     e_DrawQuad(MousePos.X, MousePos.Y, MousePos.X+180, MousePos.Y+18, 255, 255, 255);
-    PrintBlack(MousePos.X+2, MousePos.Y+2, _glc[I_HINT_PANEL_DOOR], gEditorFont);
+    PrintBlack(MousePos.X+2, MousePos.Y+2, utf8to1251(MsgHintPanelDoor), gEditorFont);
   end;
 
 // Подсказка при выборе панели с текстурой:
@@ -2809,7 +3055,7 @@ begin
   begin
     e_DrawFillQuad(MousePos.X, MousePos.Y, MousePos.X+196, MousePos.Y+18, 192, 192, 192, 127);
     e_DrawQuad(MousePos.X, MousePos.Y, MousePos.X+196, MousePos.Y+18, 255, 255, 255);
-    PrintBlack(MousePos.X+2, MousePos.Y+2, _glc[I_HINT_PANEL_TEXTURE], gEditorFont);
+    PrintBlack(MousePos.X+2, MousePos.Y+2, utf8to1251(MsgHintPanelTexture), gEditorFont);
   end;
 
 // Подсказка при выборе панели индикации выстрела:
@@ -2817,7 +3063,7 @@ begin
   begin
     e_DrawFillQuad(MousePos.X, MousePos.Y, MousePos.X+316, MousePos.Y+18, 192, 192, 192, 127);
     e_DrawQuad(MousePos.X, MousePos.Y, MousePos.X+316, MousePos.Y+18, 255, 255, 255);
-    PrintBlack(MousePos.X+2, MousePos.Y+2, _glc[I_HINT_PANEL_SHOT], gEditorFont);
+    PrintBlack(MousePos.X+2, MousePos.Y+2, utf8to1251(MsgHintPanelShot), gEditorFont);
   end;
 
 // Подсказка при выборе панели лифта:
@@ -2825,7 +3071,7 @@ begin
   begin
     e_DrawFillQuad(MousePos.X, MousePos.Y, MousePos.X+180, MousePos.Y+18, 192, 192, 192, 127);
     e_DrawQuad(MousePos.X, MousePos.Y, MousePos.X+180, MousePos.Y+18, 255, 255, 255);
-    PrintBlack(MousePos.X+2, MousePos.Y+2, _glc[I_HINT_PANEL_LIFT], gEditorFont);
+    PrintBlack(MousePos.X+2, MousePos.Y+2, utf8to1251(MsgHintPanelLift), gEditorFont);
   end;
 
 // Подсказка при выборе монстра:
@@ -2833,7 +3079,7 @@ begin
   begin
     e_DrawFillQuad(MousePos.X, MousePos.Y, MousePos.X+120, MousePos.Y+18, 192, 192, 192, 127);
     e_DrawQuad(MousePos.X, MousePos.Y, MousePos.X+120, MousePos.Y+18, 255, 255, 255);
-    PrintBlack(MousePos.X+2, MousePos.Y+2, _glc[I_HINT_MONSTER], gEditorFont);
+    PrintBlack(MousePos.X+2, MousePos.Y+2, utf8to1251(MsgHintMonster), gEditorFont);
   end;
 
 // Подсказка при выборе области воздействия:
@@ -2841,7 +3087,7 @@ begin
   begin
     e_DrawFillQuad(MousePos.X, MousePos.Y, MousePos.X+204, MousePos.Y+18, 192, 192, 192, 127);
     e_DrawQuad(MousePos.X, MousePos.Y, MousePos.X+204, MousePos.Y+18, 255, 255, 255);
-    PrintBlack(MousePos.X+2, MousePos.Y+2, _glc[I_HINT_EXT_AREA], gEditorFont);
+    PrintBlack(MousePos.X+2, MousePos.Y+2, utf8to1251(MsgHintExtArea), gEditorFont);
   end;
 
 // Рисуем текстуры, если чертим панель:
@@ -2853,8 +3099,9 @@ begin
       g_GetTexture('NOTEXTURE', ID);
     g_GetTextureSizeByID(ID, Width, Height);
     with DrawRect^ do
-      e_DrawFill(ID, Min(Left, Right), Min(Top, Bottom), Abs(Right-Left) div Width,
-                 Abs(Bottom-Top) div Height, 0, True, False);
+      if (Abs(Right-Left) >= Width) and (Abs(Bottom-Top) >= Height) then
+        e_DrawFill(ID, Min(Left, Right), Min(Top, Bottom), Abs(Right-Left) div Width,
+                   Abs(Bottom-Top) div Height, 64, True, False);
   end;
 
 // Прямоугольник выделения:
@@ -2863,7 +3110,8 @@ begin
       e_DrawQuad(Left, Top, Right-1, Bottom-1, 255, 255, 255);
 
 // Чертим мышью панель/триггер или меняем мышью их размер:
-  if (MouseAction in [MOUSEACTION_DRAWPANEL, MOUSEACTION_DRAWTRIGGER, MOUSEACTION_RESIZE]) and
+  if (((MouseAction in [MOUSEACTION_DRAWPANEL, MOUSEACTION_DRAWTRIGGER]) and
+     not(ssCtrl in GetKeyShiftState())) or (MouseAction = MOUSEACTION_RESIZE)) and
      (DrawPanelSize) then
   begin
     e_DrawFillQuad(MousePos.X, MousePos.Y, MousePos.X+88, MousePos.Y+33, 192, 192, 192, 127);
@@ -2871,9 +3119,9 @@ begin
 
     if MouseAction in [MOUSEACTION_DRAWPANEL, MOUSEACTION_DRAWTRIGGER] then
       begin // Чертим новый
-        PrintBlack(MousePos.X+2, MousePos.Y+2, Format(_glc[I_HINT_WIDTH],
+        PrintBlack(MousePos.X+2, MousePos.Y+2, Format(utf8to1251(MsgHintWidth),
                           [Abs(MousePos.X-MouseLDownPos.X)]), gEditorFont);
-        PrintBlack(MousePos.X+2, MousePos.Y+14, Format(_glc[I_HINT_HEIGHT],
+        PrintBlack(MousePos.X+2, MousePos.Y+16, Format(utf8to1251(MsgHintHeight),
                           [Abs(MousePos.Y-MouseLDownPos.Y)]), gEditorFont);
       end
     else // Растягиваем существующий
@@ -2890,9 +3138,9 @@ begin
             Height := gTriggers[SelectedObjects[GetFirstSelected].ID].Height;
           end;
 
-        PrintBlack(MousePos.X+2, MousePos.Y+2, Format(_glc[I_HINT_WIDTH], [Width]),
+        PrintBlack(MousePos.X+2, MousePos.Y+2, Format(utf8to1251(MsgHintWidth), [Width]),
                           gEditorFont);
-        PrintBlack(MousePos.X+2, MousePos.Y+14, Format(_glc[I_HINT_HEIGHT], [Height]),
+        PrintBlack(MousePos.X+2, MousePos.Y+16, Format(utf8to1251(MsgHintHeight), [Height]),
                           gEditorFont);
       end;
   end;
@@ -2996,18 +3244,31 @@ procedure TMainForm.FormResize(Sender: TObject);
 begin
   e_SetViewPort(0, 0, RenderPanel.Width, RenderPanel.Height);
 
-  if gMapInfo.Width >= RenderPanel.Width then
-    sbHorizontal.Max := Normalize16(gMapInfo.Width-RenderPanel.Width+16)
-  else
-    sbHorizontal.Max := 0;
+  sbHorizontal.Min := Min(gMapInfo.Width - RenderPanel.Width, -RenderPanel.Width div 2);
+  sbHorizontal.Max := Max(0, gMapInfo.Width - RenderPanel.Width div 2);
+  sbVertical.Min := Min(gMapInfo.Height - RenderPanel.Height, -RenderPanel.Height div 2);
+  sbVertical.Max := Max(0, gMapInfo.Height - RenderPanel.Height div 2);
 
-  if gMapInfo.Height >= RenderPanel.Height then
-    sbVertical.Max := Normalize16(gMapInfo.Height-RenderPanel.Height+16)
-  else
-    sbVertical.Max := 0;
+  MapOffset.X := -sbHorizontal.Position;
+  MapOffset.Y := -sbVertical.Position;
+end;
 
-  MapOffset.X := -Normalize16(sbHorizontal.Position);
-  MapOffset.Y := -Normalize16(sbVertical.Position);
+procedure TMainForm.FormWindowStateChange(Sender: TObject);
+  {$IFDEF DARWIN}
+    var e: Boolean;
+  {$ENDIF}
+begin
+  {$IFDEF DARWIN}
+    // deactivate all menus when main window minimized
+    e := self.WindowState <> wsMinimized;
+    miMenuFile.Enabled := e;
+    miMenuEdit.Enabled := e;
+    miMenuView.Enabled := e;
+    miMenuService.Enabled := e;
+    miMenuWindow.Enabled := e;
+    miMenuHelp.Enabled := e;
+    miMenuHidden.Enabled := e;
+  {$ENDIF}
 end;
 
 procedure SelectNextObject(X, Y: Integer; ObjectType: Byte; ID: DWORD);
@@ -3166,7 +3427,7 @@ begin
           case pcObjects.ActivePageIndex of
             1:
               if lbItemList.ItemIndex = -1 then
-                ErrorMessageBox(_lc[I_MSG_CHOOSE_ITEM])
+                ErrorMessageBox(MsgMsgChooseItem)
               else
                 begin
                   item.ItemType := lbItemList.ItemIndex + ITEM_MEDKIT_SMALL;
@@ -3187,7 +3448,7 @@ begin
                 end;
             2:
               if lbMonsterList.ItemIndex = -1 then
-                ErrorMessageBox(_lc[I_MSG_CHOOSE_MONSTER])
+                ErrorMessageBox(MsgMsgChooseMonster)
               else
                 begin
                   monster.MonsterType := lbMonsterList.ItemIndex + MONSTER_DEMON;
@@ -3208,7 +3469,7 @@ begin
                 end;
             3:
               if lbAreasList.ItemIndex = -1 then
-                ErrorMessageBox(_lc[I_MSG_CHOOSE_AREA])
+                ErrorMessageBox(MsgMsgChooseArea)
               else
                 if (lbAreasList.ItemIndex + 1) <> AREA_DOMFLAG then
                   begin
@@ -3498,6 +3759,16 @@ begin
         end;
   end; // if Button = mbRight
 
+  if Button = mbMiddle then // Middle Mouse Button
+  begin
+    SetCapture(RenderPanel.Handle);
+    RenderPanel.Cursor := crSize;
+  end;
+
+  MouseMDown := Button = mbMiddle;
+  if MouseMDown then
+    MouseMDownPos := Mouse.CursorPos;
+
   MouseRDown := Button = mbRight;
   if MouseRDown then
     MouseRDownPos := MousePos;
@@ -3512,26 +3783,46 @@ procedure TMainForm.RenderPanelMouseUp(Sender: TObject;
 var
   panel: TPanel;
   trigger: TTrigger;
-  i: Integer;
-  IDArray: DWArray;
   rRect: TRectWH;
   rSelectRect: Boolean;
+  wWidth, wHeight: Word;
+  TextureID: DWORD;
+
+  procedure SelectObjects(ObjectType: Byte);
+  var
+    i: Integer;
+    IDArray: DWArray;
+  begin
+    IDArray := ObjectInRect(rRect.X, rRect.Y,
+                 rRect.Width, rRect.Height,
+                 ObjectType, rSelectRect);
+
+    if IDArray <> nil then
+      for i := 0 to High(IDArray) do
+        SelectObject(ObjectType, IDArray[i], (ssCtrl in Shift) or rSelectRect);
+  end;
 begin
   if Button = mbLeft then
     MouseLDown := False;
   if Button = mbRight then
     MouseRDown := False;
+  if Button = mbMiddle then
+    MouseMDown := False;
 
   DrawRect := nil;
   ResizeType := RESIZETYPE_NONE;
+  TextureID := 0;
 
   if Button = mbLeft then // Left Mouse Button
     begin
       if MouseAction <> MOUSEACTION_NONE then
       begin // Было действие мышью
-      // Мышь сдвинулась во время удержания клавиши:
-        if (MousePos.X <> MouseLDownPos.X) and
-           (MousePos.Y <> MouseLDownPos.Y) then
+      // Мышь сдвинулась во время удержания клавиши,
+      // либо активирован режим быстрого рисования:
+        if ((MousePos.X <> MouseLDownPos.X) and
+           (MousePos.Y <> MouseLDownPos.Y)) or
+           ((MouseAction in [MOUSEACTION_DRAWPANEL, MOUSEACTION_DRAWTRIGGER]) and
+           (ssCtrl in Shift)) then
           case MouseAction of
           // Рисовали панель:
             MOUSEACTION_DRAWPANEL:
@@ -3539,7 +3830,7 @@ begin
               // Фон или передний план без текстуры - ошибка:
                 if (lbPanelType.ItemIndex in [1, 2]) and
                    (lbTextureList.ItemIndex = -1) then
-                  ErrorMessageBox(_lc[I_MSG_CHOOSE_TEXTURE])
+                  ErrorMessageBox(MsgMsgChooseTexture)
                 else // Назначаем параметры панели:
                   begin
                     case lbPanelType.ItemIndex of
@@ -3561,8 +3852,25 @@ begin
 
                     Panel.X := Min(MousePos.X-MapOffset.X, MouseLDownPos.X-MapOffset.X);
                     Panel.Y := Min(MousePos.Y-MapOffset.Y, MouseLDownPos.Y-MapOffset.Y);
-                    Panel.Width := Abs(MousePos.X-MouseLDownPos.X);
-                    Panel.Height := Abs(MousePos.Y-MouseLDownPos.Y);
+                    if ssCtrl in Shift then
+                      begin
+                        wWidth := DotStep;
+                        wHeight := DotStep;
+                        if (lbTextureList.ItemIndex <> -1) and
+                           (not IsSpecialTextureSel()) then
+                        begin
+                          if not g_GetTexture(SelectedTexture(), TextureID) then
+                            g_GetTexture('NOTEXTURE', TextureID);
+                          g_GetTextureSizeByID(TextureID, wWidth, wHeight);
+                        end;
+                        Panel.Width := wWidth;
+                        Panel.Height := wHeight;
+                      end
+                    else
+                      begin
+                        Panel.Width := Abs(MousePos.X-MouseLDownPos.X);
+                        Panel.Height := Abs(MousePos.Y-MouseLDownPos.Y);
+                      end;
 
                   // Лифты, блокМон или отсутствие текстуры - пустая текстура:
                     if (lbPanelType.ItemIndex in [9, 10, 11, 12, 13]) or
@@ -3604,8 +3912,18 @@ begin
               begin
                 trigger.X := Min(MousePos.X-MapOffset.X, MouseLDownPos.X-MapOffset.X);
                 trigger.Y := Min(MousePos.Y-MapOffset.Y, MouseLDownPos.Y-MapOffset.Y);
-                trigger.Width := Abs(MousePos.X-MouseLDownPos.X);
-                trigger.Height := Abs(MousePos.Y-MouseLDownPos.Y);
+                if ssCtrl in Shift then
+                  begin
+                    wWidth := DotStep;
+                    wHeight := DotStep;
+                    trigger.Width := wWidth;
+                    trigger.Height := wHeight;
+                  end
+                else
+                  begin
+                    trigger.Width := Abs(MousePos.X-MouseLDownPos.X);
+                    trigger.Height := Abs(MousePos.Y-MouseLDownPos.Y);
+                  end;
 
                 trigger.Enabled := True;
                 trigger.TriggerType := lbTriggersList.ItemIndex+1;
@@ -3799,7 +4117,7 @@ begin
         MouseAction := MOUSEACTION_NONE;
       end;
     end // if Button = mbLeft...
-  else // Right Mouse Button:
+  else if Button = mbRight then // Right Mouse Button:
     begin
       if MouseAction = MOUSEACTION_NOACTION then
       begin
@@ -3810,6 +4128,7 @@ begin
     // Объект передвинут или изменен в размере:
       if MouseAction in [MOUSEACTION_MOVEOBJ, MOUSEACTION_RESIZE] then
       begin
+        RenderPanel.Cursor := crDefault;
         MouseAction := MOUSEACTION_NONE;
         FillProperty();
         Exit;
@@ -3850,16 +4169,24 @@ begin
         RemoveSelectFromObjects();
 
     // Выделяем всё в выбранном прямоугольнике:
-      IDArray := ObjectInRect(rRect.X, rRect.Y,
-                   rRect.Width, rRect.Height,
-                   pcObjects.ActivePageIndex+1, rSelectRect);
-
-      if IDArray <> nil then
-        for i := 0 to High(IDArray) do
-          SelectObject(pcObjects.ActivePageIndex+1, IDArray[i],
-            (ssCtrl in Shift) or rSelectRect);
+      if (ssCtrl in Shift) and (ssAlt in Shift) then
+        begin
+          SelectObjects(OBJECT_PANEL);
+          SelectObjects(OBJECT_ITEM);
+          SelectObjects(OBJECT_MONSTER);
+          SelectObjects(OBJECT_AREA);
+          SelectObjects(OBJECT_TRIGGER);
+        end
+      else
+        SelectObjects(pcObjects.ActivePageIndex+1);
 
       FillProperty();
+    end
+
+  else // Middle Mouse Button
+    begin
+      RenderPanel.Cursor := crDefault;
+      ReleaseCapture();
     end;
 end;
 
@@ -3868,14 +4195,30 @@ begin
   Draw();
 end;
 
+function TMainForm.RenderMousePos(): Types.TPoint;
+begin
+  Result := RenderPanel.ScreenToClient(Mouse.CursorPos);
+end;
+
+procedure TMainForm.RecountSelectedObjects();
+begin
+  if SelectedObjectCount() = 0 then
+    StatusBar.Panels[0].Text := ''
+  else
+    StatusBar.Panels[0].Text := Format(MsgCapStatSelected, [SelectedObjectCount()]);
+end;
+
 procedure TMainForm.RenderPanelMouseMove(Sender: TObject;
   Shift: TShiftState; X, Y: Integer);
 var
   sX, sY: Integer;
   dWidth, dHeight: Integer;
   _id: Integer;
+  TextureID: DWORD;
+  wWidth, wHeight: Word;
 begin
   _id := GetFirstSelected();
+  TextureID := 0;
 
 // Рисуем панель с текстурой, сетка - размеры текстуры:
   if (MouseAction = MOUSEACTION_DRAWPANEL) and
@@ -3924,16 +4267,12 @@ begin
       end
     else
       begin // Кнопки мыши не зажаты
-        MousePos.X := (Round(X/sX)*sX);
-        MousePos.Y := (Round(Y/sY)*sY);
+        MousePos.X := Round((-MapOffset.X + X) / sX) * sX + MapOffset.X;
+        MousePos.Y := Round((-MapOffset.Y + Y) / sY) * sY + MapOffset.Y;
       end;
 
-// Изменение размера закончилось - ставим обычный курсор:
-  if ResizeType = RESIZETYPE_NONE then
-    RenderPanel.Cursor := crDefault;
-
 // Зажата только правая кнопка мыши:
-  if (not MouseLDown) and (MouseRDown) then
+  if (not MouseLDown) and (MouseRDown) and (not MouseMDown) then
   begin
   // Рисуем прямоугольник выделения:
     if MouseAction = MOUSEACTION_NONE then
@@ -3973,17 +4312,16 @@ begin
               RESIZEDIR_LEFT: dWidth := -dWidth;
             end;
 
-            ResizeObject(SelectedObjects[GetFirstSelected].ObjectType,
-                         SelectedObjects[GetFirstSelected].ID,
-                         dWidth, dHeight, ResizeDirection);
-
-            LastMovePoint := MousePos;
+            if ResizeObject(SelectedObjects[GetFirstSelected].ObjectType,
+                           SelectedObjects[GetFirstSelected].ID,
+                           dWidth, dHeight, ResizeDirection) then
+              LastMovePoint := MousePos;
           end;
         end;
   end;
 
 // Зажата только левая кнопка мыши:
-  if (not MouseRDown) and (MouseLDown) then
+  if (not MouseRDown) and (MouseLDown) and (not MouseMDown) then
   begin
   // Рисуем прямоугольник планирования панели:
     if MouseAction in [MOUSEACTION_DRAWPANEL,
@@ -3992,10 +4330,29 @@ begin
       begin
         if DrawRect = nil then
           New(DrawRect);
-        DrawRect.Top := MouseLDownPos.y;
-        DrawRect.Left := MouseLDownPos.x;
-        DrawRect.Bottom := MousePos.y;
-        DrawRect.Right := MousePos.x;
+        if ssCtrl in Shift then
+          begin
+            wWidth := DotStep;
+            wHeight := DotStep;
+            if (lbTextureList.ItemIndex <> -1) and (not IsSpecialTextureSel()) and
+               (MouseAction = MOUSEACTION_DRAWPANEL) then
+            begin
+              if not g_GetTexture(SelectedTexture(), TextureID) then
+                g_GetTexture('NOTEXTURE', TextureID);
+              g_GetTextureSizeByID(TextureID, wWidth, wHeight);
+            end;
+            DrawRect.Top := MouseLDownPos.y;
+            DrawRect.Left := MouseLDownPos.x;
+            DrawRect.Bottom := DrawRect.Top + wHeight;
+            DrawRect.Right := DrawRect.Left + wWidth;
+          end
+        else
+          begin
+            DrawRect.Top := MouseLDownPos.y;
+            DrawRect.Left := MouseLDownPos.x;
+            DrawRect.Bottom := MousePos.y;
+            DrawRect.Right := MousePos.x;
+          end;
       end
     else // Двигаем карту:
       if MouseAction = MOUSEACTION_MOVEMAP then
@@ -4004,6 +4361,18 @@ begin
       end;
   end;
 
+// Only Middle Mouse Button is pressed
+  if (not MouseLDown) and (not MouseRDown) and (MouseMDown) then
+  begin
+    MapOffset.X := -EnsureRange(-MapOffset.X + MouseMDownPos.X - Mouse.CursorPos.X,
+                                sbHorizontal.Min, sbHorizontal.Max);
+    sbHorizontal.Position := -MapOffset.X;
+    MapOffset.Y := -EnsureRange(-MapOffset.Y + MouseMDownPos.Y - Mouse.CursorPos.Y,
+                                sbVertical.Min, sbVertical.Max);
+    sbVertical.Position := -MapOffset.Y;
+    MouseMDownPos := Mouse.CursorPos;
+  end;
+
 // Клавиши мыши не зажаты:
   if (not MouseRDown) and (not MouseLDown) then
     DrawRect := nil;
@@ -4015,8 +4384,8 @@ end;
 
 procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
 begin
-  CanClose := MessageBox(0, PChar(_lc[I_MSG_EXIT_PROMT]),
-                         PChar(_lc[I_MSG_EXIT]),
+  CanClose := Application.MessageBox(PChar(MsgMsgExitPromt),
+                         PChar(MsgMsgExit),
                          MB_ICONQUESTION or MB_YESNO or
                          MB_DEFBUTTON1) = idYes;
 end;
@@ -4029,9 +4398,10 @@ end;
 procedure TMainForm.FormDestroy(Sender: TObject);
 var
   config: TConfig;
+  s: AnsiString;
   i: Integer;
 begin
-  config := TConfig.CreateFile(EditorDir+'Editor.cfg');
+  config := TConfig.CreateFile(CfgFileName);
 
   if WindowState <> wsMaximized then
   begin
@@ -4055,31 +4425,68 @@ begin
   config.WriteInt('Editor', 'DotStep', DotStep);
   config.WriteStr('Editor', 'LastOpenDir', OpenDialog.InitialDir);
   config.WriteStr('Editor', 'LastSaveDir', SaveDialog.InitialDir);
+  config.WriteStr('Editor', 'Language', gLanguage);
   config.WriteBool('Editor', 'EdgeShow', drEdge[3] < 255);
   config.WriteInt('Editor', 'EdgeColor', gColorEdge);
   config.WriteInt('Editor', 'EdgeAlpha', gAlphaEdge);
   config.WriteInt('Editor', 'LineAlpha', gAlphaTriggerLine);
   config.WriteInt('Editor', 'TriggerAlpha', gAlphaTriggerArea);
+  config.WriteInt('Editor', 'MonsterRectAlpha', gAlphaMonsterRect);
+  config.WriteInt('Editor', 'AreaRectAlpha', gAlphaAreaRect);
 
-  for i := 0 to RecentCount-1 do
-    if i < RecentFiles.Count then
-      config.WriteStr('RecentFiles', IntToStr(i+1), RecentFiles[i])
-    else
-      config.WriteStr('RecentFiles', IntToStr(i+1), '');
+  for i := 0 to RecentCount - 1 do
+  begin
+    if i < RecentFiles.Count then s := RecentFiles[i] else s := '';
+    {$IFDEF WINDOWS}
+      config.WriteStr('RecentFilesWin', IntToStr(i), s);
+    {$ELSE}
+      config.WriteStr('RecentFilesUnix', IntToStr(i), s);
+    {$ENDIF}
+  end;
   RecentFiles.Free();
 
-  config.SaveFile(EditorDir+'Editor.cfg');
+  config.SaveFile(CfgFileName);
   config.Free();
 
   slInvalidTextures.Free;
 end;
 
+procedure TMainForm.FormDropFiles(Sender: TObject;
+  const FileNames: array of String);
+begin
+  if Length(FileNames) <> 1 then
+    Exit;
+
+  OpenMapFile(FileNames[0]);
+end;
+
 procedure TMainForm.RenderPanelResize(Sender: TObject);
 begin
   if MainForm.Visible then
     MainForm.Resize();
 end;
 
+procedure TMainForm.Splitter1Moved(Sender: TObject);
+begin
+  FormResize(Sender);
+end;
+
+procedure TMainForm.MapTestCheck(Sender: TObject);
+begin
+  if MapTestProcess <> nil then
+  begin
+    if MapTestProcess.Running = false then
+    begin
+      if MapTestProcess.ExitCode <> 0 then
+        Application.MessageBox(PChar(MsgMsgExecError), 'FIXME', MB_OK or MB_ICONERROR);
+      SysUtils.DeleteFile(MapTestFile);
+      MapTestFile := '';
+      FreeAndNil(MapTestProcess);
+      tbTestMap.Enabled := True;
+    end;
+  end;
+end;
+
 procedure TMainForm.aMapOptionsExecute(Sender: TObject);
 var
   ResName: String;
@@ -4090,7 +4497,7 @@ begin
   while (Pos(':\', ResName) > 0) do
     Delete(ResName, 1, Pos(':\', ResName) + 1);
 
-  UpdateCaption(win2utf(gMapInfo.Name), ExtractFileName(OpenedWAD), ResName);
+  UpdateCaption(gMapInfo.Name, ExtractFileName(OpenedWAD), ResName);
 end;
 
 procedure TMainForm.aAboutExecute(Sender: TObject);
@@ -4098,34 +4505,52 @@ begin
   AboutForm.ShowModal();
 end;
 
-procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
-  Shift: TShiftState);
+procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
 var
   dx, dy, i: Integer;
   FileName: String;
+  ok: Boolean;
 begin
   if (not EditingProperties) then
   begin
-    if Key = Ord('1') then
-      SwitchLayer(LAYER_BACK);
-    if Key = Ord('2') then
-      SwitchLayer(LAYER_WALLS);
-    if Key = Ord('3') then
-      SwitchLayer(LAYER_FOREGROUND);
-    if Key = Ord('4') then
-      SwitchLayer(LAYER_STEPS);
-    if Key = Ord('5') then
-      SwitchLayer(LAYER_WATER);
-    if Key = Ord('6') then
-      SwitchLayer(LAYER_ITEMS);
-    if Key = Ord('7') then
-      SwitchLayer(LAYER_MONSTERS);
-    if Key = Ord('8') then
-      SwitchLayer(LAYER_AREAS);
-    if Key = Ord('9') then
-      SwitchLayer(LAYER_TRIGGERS);
-    if Key = Ord('0') then
-      tbShowClick(tbShow);
+    if ssCtrl in Shift then
+    begin
+      case Chr(Key) of
+        '1': ContourEnabled[LAYER_BACK] := not ContourEnabled[LAYER_BACK];
+        '2': ContourEnabled[LAYER_WALLS] := not ContourEnabled[LAYER_WALLS];
+        '3': ContourEnabled[LAYER_FOREGROUND] := not ContourEnabled[LAYER_FOREGROUND];
+        '4': ContourEnabled[LAYER_STEPS] := not ContourEnabled[LAYER_STEPS];
+        '5': ContourEnabled[LAYER_WATER] := not ContourEnabled[LAYER_WATER];
+        '6': ContourEnabled[LAYER_ITEMS] := not ContourEnabled[LAYER_ITEMS];
+        '7': ContourEnabled[LAYER_MONSTERS] := not ContourEnabled[LAYER_MONSTERS];
+        '8': ContourEnabled[LAYER_AREAS] := not ContourEnabled[LAYER_AREAS];
+        '9': ContourEnabled[LAYER_TRIGGERS] := not ContourEnabled[LAYER_TRIGGERS];
+        '0':
+           begin
+             ok := False;
+             for i := Low(ContourEnabled) to High(ContourEnabled) do
+               if ContourEnabled[i] then
+                 ok := True;
+             for i := Low(ContourEnabled) to High(ContourEnabled) do
+               ContourEnabled[i] := not ok
+           end
+      end
+    end
+    else
+    begin
+      case Chr(key) of
+        '1': SwitchLayer(LAYER_BACK);
+        '2': SwitchLayer(LAYER_WALLS);
+        '3': SwitchLayer(LAYER_FOREGROUND);
+        '4': SwitchLayer(LAYER_STEPS);
+        '5': SwitchLayer(LAYER_WATER);
+        '6': SwitchLayer(LAYER_ITEMS);
+        '7': SwitchLayer(LAYER_MONSTERS);
+        '8': SwitchLayer(LAYER_AREAS);
+        '9': SwitchLayer(LAYER_TRIGGERS);
+        '0': tbShowClick(tbShow);
+      end
+    end;
 
     if Key = Ord('V') then
     begin // Поворот монстров и областей:
@@ -4166,19 +4591,54 @@ begin
 
     if not (ssCtrl in Shift) then
     begin
+    // Быстрое превью карты:
+      if Key = Ord('E') then
+      begin
+        if PreviewMode = 0 then
+          PreviewMode := 2;
+      end;
+
     // Вертикальный скролл карты:
       with sbVertical do
       begin
         if Key = Ord('W') then
         begin
-          Position := IfThen(Position > DotStep, Position-DotStep, 0);
-          MapOffset.Y := -Round(Position/16) * 16;
+          dy := Position;
+          if ssShift in Shift then Position := EnsureRange(Position - DotStep * 4, Min, Max)
+          else Position := EnsureRange(Position - DotStep, Min, Max);
+          MapOffset.Y := -Position;
+          dy -= Position;
+
+          if (MouseLDown or MouseRDown) then
+          begin
+            if DrawRect <> nil then
+            begin
+              Inc(MouseLDownPos.y, dy);
+              Inc(MouseRDownPos.y, dy);
+            end;
+            Inc(LastMovePoint.Y, dy);
+            RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
+          end;
         end;
 
         if Key = Ord('S') then
         begin
-          Position := IfThen(Position+DotStep < Max, Position+DotStep, Max);
-          MapOffset.Y := -Round(Position/16) * 16;
+          dy := Position;
+          if ssShift in Shift then Position := EnsureRange(Position + DotStep * 4, Min, Max)
+          else Position := EnsureRange(Position + DotStep, Min, Max);
+          MapOffset.Y := -Position;
+          dy -= Position;
+
+          if (MouseLDown or MouseRDown) then
+          begin
+            if DrawRect <> nil then
+            begin
+              Inc(MouseLDownPos.y, dy);
+              Inc(MouseRDownPos.y, dy);
+            end;
+            Inc(LastMovePoint.Y, dy);
+            RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
+          end;
         end;
       end;
 
@@ -4187,16 +4647,54 @@ begin
       begin
         if Key = Ord('A') then
         begin
-          Position := IfThen(Position > DotStep, Position-DotStep, 0);
-          MapOffset.X := -Round(Position/16) * 16;
+          dx := Position;
+          if ssShift in Shift then Position := EnsureRange(Position - DotStep * 4, Min, Max)
+          else Position := EnsureRange(Position - DotStep, Min, Max);
+          MapOffset.X := -Position;
+          dx -= Position;
+
+          if (MouseLDown or MouseRDown) then
+          begin
+            if DrawRect <> nil then
+            begin
+              Inc(MouseLDownPos.x, dx);
+              Inc(MouseRDownPos.x, dx);
+            end;
+            Inc(LastMovePoint.X, dx);
+            RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
+          end;
         end;
 
         if Key = Ord('D') then
         begin
-          Position := IfThen(Position+DotStep < Max, Position+DotStep, Max);
-          MapOffset.X := -Round(Position/16) * 16;
+          dx := Position;
+          if ssShift in Shift then Position := EnsureRange(Position + DotStep * 4, Min, Max)
+          else Position := EnsureRange(Position + DotStep, Min, Max);
+          MapOffset.X := -Position;
+          dx -= Position;
+
+          if (MouseLDown or MouseRDown) then
+          begin
+            if DrawRect <> nil then
+            begin
+              Inc(MouseLDownPos.x, dx);
+              Inc(MouseRDownPos.x, dx);
+            end;
+            Inc(LastMovePoint.X, dx);
+            RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
+          end;
         end;
       end;
+    end
+    else // ssCtrl in Shift
+    begin
+      if ssShift in Shift then
+      begin
+      // Вставка по абсолютному смещению:
+        if Key = Ord('V') then
+          aPasteObjectExecute(Sender);
+      end;
+      RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
     end;
   end;
 
@@ -4242,7 +4740,7 @@ begin
         SelectFlag := SELECTFLAG_NONE;
         Exit;
       end;
-      vleObjectProperty.FindRow(_lc[I_PROP_TR_TEXTURE_PANEL], i);
+      vleObjectProperty.FindRow(MsgPropTrTexturePanel, i);
       if i > 0 then
         SelectFlag := SELECTFLAG_TEXTURE;
     end;
@@ -4255,35 +4753,36 @@ begin
         DrawPressRect := False;
         Exit;
       end;
+      i := -1;
 
     // Выбор области воздействия, в зависимости от типа триггера
-      vleObjectProperty.FindRow(_lc[I_PROP_TR_EX_AREA], i);
+      vleObjectProperty.FindRow(MsgPropTrExArea, i);
       if i > 0 then
       begin
         DrawPressRect := True;
         Exit;
       end;
-      vleObjectProperty.FindRow(_lc[I_PROP_TR_DOOR_PANEL], i);
+      vleObjectProperty.FindRow(MsgPropTrDoorPanel, i);
       if i <= 0 then
-        vleObjectProperty.FindRow(_lc[I_PROP_TR_TRAP_PANEL], i);
+        vleObjectProperty.FindRow(MsgPropTrTrapPanel, i);
       if i > 0 then
       begin
         SelectFlag := SELECTFLAG_DOOR;
         Exit;
       end;
-      vleObjectProperty.FindRow(_lc[I_PROP_TR_LIFT_PANEL], i);
+      vleObjectProperty.FindRow(MsgPropTrLiftPanel, i);
       if i > 0 then
       begin
         SelectFlag := SELECTFLAG_LIFT;
         Exit;
       end;
-      vleObjectProperty.FindRow(_lc[I_PROP_TR_TELEPORT_TO], i);
+      vleObjectProperty.FindRow(MsgPropTrTeleportTo, i);
       if i > 0 then
       begin
         SelectFlag := SELECTFLAG_TELEPORT;
         Exit;
       end;
-      vleObjectProperty.FindRow(_lc[I_PROP_TR_SPAWN_TO], i);
+      vleObjectProperty.FindRow(MsgPropTrSpawnTo, i);
       if i > 0 then
       begin
         SelectFlag := SELECTFLAG_SPAWNPOINT;
@@ -4291,10 +4790,11 @@ begin
       end;
 
     // Выбор основного параметра, в зависимости от типа триггера
-      vleObjectProperty.FindRow(_lc[I_PROP_TR_NEXT_MAP], i);
+      vleObjectProperty.FindRow(MsgPropTrNextMap, i);
       if i > 0 then
       begin
         g_ProcessResourceStr(OpenedMap, @FileName, nil, nil);
+        SelectMapForm.Caption := MsgCapSelect;
         SelectMapForm.GetMaps(FileName);
 
         if SelectMapForm.ShowModal() = mrOK then
@@ -4304,25 +4804,25 @@ begin
         end;
         Exit;
       end;
-      vleObjectProperty.FindRow(_lc[I_PROP_TR_SOUND_NAME], i);
+      vleObjectProperty.FindRow(MsgPropTrSoundName, i);
       if i <= 0 then
-        vleObjectProperty.FindRow(_lc[I_PROP_TR_MUSIC_NAME], i);
+        vleObjectProperty.FindRow(MsgPropTrMusicName, i);
       if i > 0 then
       begin
         AddSoundForm.OKFunction := nil;
         AddSoundForm.lbResourcesList.MultiSelect := False;
-        AddSoundForm.SetResource := utf2win(vleObjectProperty.Cells[1, i]);
+        AddSoundForm.SetResource := vleObjectProperty.Cells[1, i];
 
         if (AddSoundForm.ShowModal() = mrOk) then
         begin
-          vleObjectProperty.Cells[1, i] := win2utf(AddSoundForm.ResourceName);
+          vleObjectProperty.Cells[1, i] := AddSoundForm.ResourceName;
           bApplyProperty.Click();
         end;
         Exit;
       end;
-      vleObjectProperty.FindRow(_lc[I_PROP_TR_PUSH_ANGLE], i);
+      vleObjectProperty.FindRow(MsgPropTrPushAngle, i);
       if i <= 0 then
-        vleObjectProperty.FindRow(_lc[I_PROP_TR_MESSAGE_TEXT], i);
+        vleObjectProperty.FindRow(MsgPropTrMessageText, i);
       if i > 0 then
       begin
         vleObjectProperty.Row := i;
@@ -4355,6 +4855,9 @@ var
   TextureID: DWORD;
   TextureWidth, TextureHeight: Word;
 begin
+  TextureID := 0;
+  TextureWidth := 0;
+  TextureHeight := 0;
   if (lbTextureList.ItemIndex <> -1) and
      (not IsSpecialTextureSel()) then
     begin
@@ -4366,8 +4869,8 @@ begin
         lTextureHeight.Caption := IntToStr(TextureHeight);
       end else
       begin
-        lTextureWidth.Caption := _lc[I_NOT_ACCESSIBLE];
-        lTextureHeight.Caption := _lc[I_NOT_ACCESSIBLE];
+        lTextureWidth.Caption := MsgNotAccessible;
+        lTextureHeight.Caption := MsgNotAccessible;
       end;
     end
   else
@@ -4398,101 +4901,141 @@ begin
   end;
 end;
 
+procedure TMainForm.miMacMinimizeClick(Sender: TObject);
+begin
+  self.WindowState := wsMinimized;
+  self.FormWindowStateChange(Sender);
+end;
+
+procedure TMainForm.miMacZoomClick(Sender: TObject);
+begin
+  if self.WindowState = wsMaximized then
+    self.WindowState := wsNormal
+  else
+    self.WindowState := wsMaximized;
+  self.FormWindowStateChange(Sender);
+end;
+
+procedure TMainForm.miReopenMapClick(Sender: TObject);
+var
+  FileName, Resource: String;
+begin
+  if OpenedMap = '' then
+    Exit;
+
+  if Application.MessageBox(PChar(MsgMsgReopenMapPromt),
+  PChar(MsgMenuFileReopen), MB_ICONQUESTION or MB_YESNO) <> idYes then
+    Exit;
+
+  g_ProcessResourceStr(OpenedMap, @FileName, nil, @Resource);
+  OpenMap(FileName, Resource);
+end;
+
 procedure TMainForm.vleObjectPropertyGetPickList(Sender: TObject;
   const KeyName: String; Values: TStrings);
 begin
   if vleObjectProperty.ItemProps[KeyName].EditStyle = esPickList then
   begin
-    if KeyName = _lc[I_PROP_DIRECTION] then
+    if KeyName = MsgPropDirection then
       begin
         Values.Add(DirNames[D_LEFT]);
         Values.Add(DirNames[D_RIGHT]);
       end
-    else if KeyName = _lc[I_PROP_TR_TELEPORT_DIR] then
+    else if KeyName = MsgPropTrTeleportDir then
       begin
         Values.Add(DirNamesAdv[0]);
         Values.Add(DirNamesAdv[1]);
         Values.Add(DirNamesAdv[2]);
         Values.Add(DirNamesAdv[3]);
       end
-    else if KeyName = _lc[I_PROP_TR_MUSIC_ACT] then
+    else if KeyName = MsgPropTrMusicAct then
+      begin
+        Values.Add(MsgPropTrMusicOn);
+        Values.Add(MsgPropTrMusicOff);
+      end
+    else if KeyName = MsgPropTrMonsterBehaviour then
       begin
-        Values.Add(_lc[I_PROP_TR_MUSIC_ON]);
-        Values.Add(_lc[I_PROP_TR_MUSIC_OFF]);
+        Values.Add(MsgPropTrMonsterBehaviour0);
+        Values.Add(MsgPropTrMonsterBehaviour1);
+        Values.Add(MsgPropTrMonsterBehaviour2);
+        Values.Add(MsgPropTrMonsterBehaviour3);
+        Values.Add(MsgPropTrMonsterBehaviour4);
+        Values.Add(MsgPropTrMonsterBehaviour5);
       end
-    else if KeyName = _lc[I_PROP_TR_MONSTER_BEHAVIOUR] then
+    else if KeyName = MsgPropTrScoreAct then
       begin
-        Values.Add(_lc[I_PROP_TR_MONSTER_BEHAVIOUR_0]);
-        Values.Add(_lc[I_PROP_TR_MONSTER_BEHAVIOUR_1]);
-        Values.Add(_lc[I_PROP_TR_MONSTER_BEHAVIOUR_2]);
-        Values.Add(_lc[I_PROP_TR_MONSTER_BEHAVIOUR_3]);
-        Values.Add(_lc[I_PROP_TR_MONSTER_BEHAVIOUR_4]);
-        Values.Add(_lc[I_PROP_TR_MONSTER_BEHAVIOUR_5]);
+        Values.Add(MsgPropTrScoreAct0);
+        Values.Add(MsgPropTrScoreAct1);
+        Values.Add(MsgPropTrScoreAct2);
+        Values.Add(MsgPropTrScoreAct3);
       end
-    else if KeyName = _lc[I_PROP_TR_SCORE_ACT] then
+    else if KeyName = MsgPropTrScoreTeam then
       begin
-        Values.Add(_lc[I_PROP_TR_SCORE_ACT_0]);
-        Values.Add(_lc[I_PROP_TR_SCORE_ACT_1]);
-        Values.Add(_lc[I_PROP_TR_SCORE_ACT_2]);
-        Values.Add(_lc[I_PROP_TR_SCORE_ACT_3]);
+        Values.Add(MsgPropTrScoreTeam0);
+        Values.Add(MsgPropTrScoreTeam1);
+        Values.Add(MsgPropTrScoreTeam2);
+        Values.Add(MsgPropTrScoreTeam3);
       end
-    else if KeyName = _lc[I_PROP_TR_SCORE_TEAM] then
+    else if KeyName = MsgPropTrMessageKind then
       begin
-        Values.Add(_lc[I_PROP_TR_SCORE_TEAM_0]);
-        Values.Add(_lc[I_PROP_TR_SCORE_TEAM_1]);
-        Values.Add(_lc[I_PROP_TR_SCORE_TEAM_2]);
-        Values.Add(_lc[I_PROP_TR_SCORE_TEAM_3]);
+        Values.Add(MsgPropTrMessageKind0);
+        Values.Add(MsgPropTrMessageKind1);
       end
-    else if KeyName = _lc[I_PROP_TR_MESSAGE_KIND] then
+    else if KeyName = MsgPropTrMessageTo then
       begin
-        Values.Add(_lc[I_PROP_TR_MESSAGE_KIND_0]);
-        Values.Add(_lc[I_PROP_TR_MESSAGE_KIND_1]);
+        Values.Add(MsgPropTrMessageTo0);
+        Values.Add(MsgPropTrMessageTo1);
+        Values.Add(MsgPropTrMessageTo2);
+        Values.Add(MsgPropTrMessageTo3);
+        Values.Add(MsgPropTrMessageTo4);
+        Values.Add(MsgPropTrMessageTo5);
       end
-    else if KeyName = _lc[I_PROP_TR_MESSAGE_TO] then
+    else if KeyName = MsgPropTrShotTo then
       begin
-        Values.Add(_lc[I_PROP_TR_MESSAGE_TO_0]);
-        Values.Add(_lc[I_PROP_TR_MESSAGE_TO_1]);
-        Values.Add(_lc[I_PROP_TR_MESSAGE_TO_2]);
-        Values.Add(_lc[I_PROP_TR_MESSAGE_TO_3]);
-        Values.Add(_lc[I_PROP_TR_MESSAGE_TO_4]);
-        Values.Add(_lc[I_PROP_TR_MESSAGE_TO_5]);
+        Values.Add(MsgPropTrShotTo0);
+        Values.Add(MsgPropTrShotTo1);
+        Values.Add(MsgPropTrShotTo2);
+        Values.Add(MsgPropTrShotTo3);
+        Values.Add(MsgPropTrShotTo4);
+        Values.Add(MsgPropTrShotTo5);
+        Values.Add(MsgPropTrShotTo6);
       end
-    else if KeyName = _lc[I_PROP_TR_SHOT_TO] then
+    else if KeyName = MsgPropTrShotAim then
       begin
-        Values.Add(_lc[I_PROP_TR_SHOT_TO_0]);
-        Values.Add(_lc[I_PROP_TR_SHOT_TO_1]);
-        Values.Add(_lc[I_PROP_TR_SHOT_TO_2]);
-        Values.Add(_lc[I_PROP_TR_SHOT_TO_3]);
-        Values.Add(_lc[I_PROP_TR_SHOT_TO_4]);
-        Values.Add(_lc[I_PROP_TR_SHOT_TO_5]);
-        Values.Add(_lc[I_PROP_TR_SHOT_TO_6]);
+        Values.Add(MsgPropTrShotAim0);
+        Values.Add(MsgPropTrShotAim1);
+        Values.Add(MsgPropTrShotAim2);
+        Values.Add(MsgPropTrShotAim3);
       end
-    else if KeyName = _lc[I_PROP_TR_SHOT_AIM] then
+    else if KeyName = MsgPropTrDamageKind then
       begin
-        Values.Add(_lc[I_PROP_TR_SHOT_AIM_0]);
-        Values.Add(_lc[I_PROP_TR_SHOT_AIM_1]);
-        Values.Add(_lc[I_PROP_TR_SHOT_AIM_2]);
-        Values.Add(_lc[I_PROP_TR_SHOT_AIM_3]);
+        Values.Add(MsgPropTrDamageKind0);
+        Values.Add(MsgPropTrDamageKind3);
+        Values.Add(MsgPropTrDamageKind4);
+        Values.Add(MsgPropTrDamageKind5);
+        Values.Add(MsgPropTrDamageKind6);
+        Values.Add(MsgPropTrDamageKind7);
+        Values.Add(MsgPropTrDamageKind8);
       end
-    else if (KeyName = _lc[I_PROP_PANEL_BLEND]) or
-            (KeyName = _lc[I_PROP_DM_ONLY]) or
-            (KeyName = _lc[I_PROP_ITEM_FALLS]) or
-            (KeyName = _lc[I_PROP_TR_ENABLED]) or
-            (KeyName = _lc[I_PROP_TR_D2D]) or
-            (KeyName = _lc[I_PROP_TR_SILENT]) or
-            (KeyName = _lc[I_PROP_TR_TELEPORT_SILENT]) or
-            (KeyName = _lc[I_PROP_TR_EX_RANDOM]) or
-            (KeyName = _lc[I_PROP_TR_TEXTURE_ONCE]) or
-            (KeyName = _lc[I_PROP_TR_TEXTURE_ANIM_ONCE]) or
-            (KeyName = _lc[I_PROP_TR_SOUND_LOCAL]) or
-            (KeyName = _lc[I_PROP_TR_SOUND_SWITCH]) or
-            (KeyName = _lc[I_PROP_TR_MONSTER_ACTIVE]) or
-            (KeyName = _lc[I_PROP_TR_PUSH_RESET]) or
-            (KeyName = _lc[I_PROP_TR_SCORE_CON]) or
-            (KeyName = _lc[I_PROP_TR_SCORE_MSG]) or
-            (KeyName = _lc[I_PROP_TR_HEALTH_MAX]) or
-            (KeyName = _lc[I_PROP_TR_SHOT_SOUND]) or
-            (KeyName = _lc[I_PROP_TR_EFFECT_CENTER]) then
+    else if (KeyName = MsgPropPanelBlend) or
+            (KeyName = MsgPropDmOnly) or
+            (KeyName = MsgPropItemFalls) or
+            (KeyName = MsgPropTrEnabled) or
+            (KeyName = MsgPropTrD2d) or
+            (KeyName = MsgPropTrSilent) or
+            (KeyName = MsgPropTrTeleportSilent) or
+            (KeyName = MsgPropTrExRandom) or
+            (KeyName = MsgPropTrTextureOnce) or
+            (KeyName = MsgPropTrTextureAnimOnce) or
+            (KeyName = MsgPropTrSoundLocal) or
+            (KeyName = MsgPropTrSoundSwitch) or
+            (KeyName = MsgPropTrMonsterActive) or
+            (KeyName = MsgPropTrPushReset) or
+            (KeyName = MsgPropTrScoreCon) or
+            (KeyName = MsgPropTrScoreMsg) or
+            (KeyName = MsgPropTrHealthMax) or
+            (KeyName = MsgPropTrShotSound) or
+            (KeyName = MsgPropTrEffectCenter) then
       begin
         Values.Add(BoolNames[True]);
         Values.Add(BoolNames[False]);
@@ -4508,6 +5051,10 @@ var
   NoTextureID: DWORD;
   NW, NH: Word;
 begin
+  NoTextureID := 0;
+  NW := 0;
+  NH := 0;
+
   if SelectedObjectCount() <> 1 then
     Exit;
   if not SelectedObjects[GetFirstSelected()].Live then
@@ -4530,12 +5077,12 @@ begin
       begin
         with gPanels[SelectedObjects[_id].ID] do
         begin
-          X := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_X]]));
-          Y := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_Y]]));
-          Width := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_WIDTH]]));
-          Height := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_HEIGHT]]));
+          X := StrToInt(Trim(vleObjectProperty.Values[MsgPropX]));
+          Y := StrToInt(Trim(vleObjectProperty.Values[MsgPropY]));
+          Width := StrToInt(Trim(vleObjectProperty.Values[MsgPropWidth]));
+          Height := StrToInt(Trim(vleObjectProperty.Values[MsgPropHeight]));
 
-          PanelType := GetPanelType(vleObjectProperty.Values[_lc[I_PROP_PANEL_TYPE]]);
+          PanelType := GetPanelType(vleObjectProperty.Values[MsgPropPanelType]);
 
         // Сброс ссылки на триггеры смены текстуры:
           if not WordBool(PanelType and (PANEL_WALL or PANEL_FORE or PANEL_BACK)) then
@@ -4571,8 +5118,8 @@ begin
             begin // Может быть текстура
               if TextureName <> '' then
                 begin // Была текстура
-                  Alpha := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_PANEL_ALPHA]]));
-                  Blending := NameToBool(vleObjectProperty.Values[_lc[I_PROP_PANEL_BLEND]]);
+                  Alpha := StrToInt(Trim(vleObjectProperty.Values[MsgPropPanelAlpha]));
+                  Blending := NameToBool(vleObjectProperty.Values[MsgPropPanelBlend]);
                 end
               else // Не было
                 begin
@@ -4581,7 +5128,7 @@ begin
                 end;
 
             // Новая текстура:
-              TextureName := vleObjectProperty.Values[_lc[I_PROP_PANEL_TEX]];
+              TextureName := vleObjectProperty.Values[MsgPropPanelTex];
 
               if TextureName <> '' then
                 begin // Есть текстура
@@ -4596,14 +5143,14 @@ begin
                       if TextureWidth <> 0 then
                         if gPanels[SelectedObjects[_id].ID].Width mod TextureWidth <> 0 then
                         begin
-                          ErrorMessageBox(Format(_lc[I_MSG_WRONG_TEXWIDTH],
+                          ErrorMessageBox(Format(MsgMsgWrongTexwidth,
                                           [TextureWidth]));
                           Res := False;
                         end;
                       if Res and (TextureHeight <> 0) then
                         if gPanels[SelectedObjects[_id].ID].Height mod TextureHeight <> 0 then
                         begin
-                          ErrorMessageBox(Format(_lc[I_MSG_WRONG_TEXHEIGHT],
+                          ErrorMessageBox(Format(MsgMsgWrongTexheight,
                                           [TextureHeight]));
                           Res := False;
                         end;
@@ -4663,10 +5210,10 @@ begin
       begin
         with gItems[SelectedObjects[_id].ID] do
         begin
-          X := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_X]]));
-          Y := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_Y]]));
-          OnlyDM := NameToBool(vleObjectProperty.Values[_lc[I_PROP_DM_ONLY]]);
-          Fall := NameToBool(vleObjectProperty.Values[_lc[I_PROP_ITEM_FALLS]]);
+          X := StrToInt(Trim(vleObjectProperty.Values[MsgPropX]));
+          Y := StrToInt(Trim(vleObjectProperty.Values[MsgPropY]));
+          OnlyDM := NameToBool(vleObjectProperty.Values[MsgPropDmOnly]);
+          Fall := NameToBool(vleObjectProperty.Values[MsgPropItemFalls]);
         end;
       end;
 
@@ -4674,9 +5221,9 @@ begin
       begin
         with gMonsters[SelectedObjects[_id].ID] do
         begin
-          X := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_X]]));
-          Y := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_Y]]));
-          Direction := NameToDir(vleObjectProperty.Values[_lc[I_PROP_DIRECTION]]);
+          X := StrToInt(Trim(vleObjectProperty.Values[MsgPropX]));
+          Y := StrToInt(Trim(vleObjectProperty.Values[MsgPropY]));
+          Direction := NameToDir(vleObjectProperty.Values[MsgPropDirection]);
         end;
       end;
 
@@ -4684,9 +5231,9 @@ begin
       begin
         with gAreas[SelectedObjects[_id].ID] do
         begin
-          X := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_X]]));
-          Y := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_Y]]));
-          Direction := NameToDir(vleObjectProperty.Values[_lc[I_PROP_DIRECTION]]);
+          X := StrToInt(Trim(vleObjectProperty.Values[MsgPropX]));
+          Y := StrToInt(Trim(vleObjectProperty.Values[MsgPropY]));
+          Direction := NameToDir(vleObjectProperty.Values[MsgPropDirection]);
         end;
       end;
 
@@ -4694,283 +5241,298 @@ begin
       begin
         with gTriggers[SelectedObjects[_id].ID] do
         begin
-          X := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_X]]));
-          Y := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_Y]]));
-          Width := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_WIDTH]]));
-          Height := StrToInt(Trim(vleObjectProperty.Values[_lc[I_PROP_HEIGHT]]));
-          Enabled := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_ENABLED]]);
-          ActivateType := StrToActivate(vleObjectProperty.Values[_lc[I_PROP_TR_ACTIVATION]]);
-          Key := StrToKey(vleObjectProperty.Values[_lc[I_PROP_TR_KEYS]]);
+          X := StrToInt(Trim(vleObjectProperty.Values[MsgPropX]));
+          Y := StrToInt(Trim(vleObjectProperty.Values[MsgPropY]));
+          Width := StrToInt(Trim(vleObjectProperty.Values[MsgPropWidth]));
+          Height := StrToInt(Trim(vleObjectProperty.Values[MsgPropHeight]));
+          Enabled := NameToBool(vleObjectProperty.Values[MsgPropTrEnabled]);
+          ActivateType := StrToActivate(vleObjectProperty.Values[MsgPropTrActivation]);
+          Key := StrToKey(vleObjectProperty.Values[MsgPropTrKeys]);
 
           case TriggerType of
             TRIGGER_EXIT:
               begin
-                s := vleObjectProperty.Values[_lc[I_PROP_TR_NEXT_MAP]];
+                s := utf2win(vleObjectProperty.Values[MsgPropTrNextMap]);
                 FillByte(Data.MapName[0], 16, 0);
                 if s <> '' then
-                  Move(Data.MapName[0], s[1], Min(Length(s), 16));
+                  Move(s[1], Data.MapName[0], Min(Length(s), 16));
               end;
 
             TRIGGER_TEXTURE:
               begin
-                Data.ActivateOnce := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_TEXTURE_ONCE]]);
-                Data.AnimOnce := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_TEXTURE_ANIM_ONCE]]);
+                Data.ActivateOnce := NameToBool(vleObjectProperty.Values[MsgPropTrTextureOnce]);
+                Data.AnimOnce := NameToBool(vleObjectProperty.Values[MsgPropTrTextureAnimOnce]);
               end;
 
             TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF:
               begin
-                Data.Wait := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EX_DELAY]], 0), 65535);
-                Data.Count := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EX_COUNT]], 0), 65535);
+                Data.Wait := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrExDelay], 0), 65535);
+                Data.Count := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrExCount], 0), 65535);
                 if Data.Count < 1 then
                   Data.Count := 1;
                 if TriggerType = TRIGGER_PRESS then
-                  Data.ExtRandom := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_EX_RANDOM]]);
+                  Data.ExtRandom := NameToBool(vleObjectProperty.Values[MsgPropTrExRandom]);
               end;
 
             TRIGGER_OPENDOOR, TRIGGER_CLOSEDOOR, TRIGGER_DOOR, TRIGGER_DOOR5,
             TRIGGER_CLOSETRAP, TRIGGER_TRAP, TRIGGER_LIFTUP, TRIGGER_LIFTDOWN,
             TRIGGER_LIFT:
               begin
-                Data.NoSound := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_SILENT]]);
-                Data.d2d_doors := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_D2D]]);
+                Data.NoSound := NameToBool(vleObjectProperty.Values[MsgPropTrSilent]);
+                Data.d2d_doors := NameToBool(vleObjectProperty.Values[MsgPropTrD2d]);
               end;
 
             TRIGGER_TELEPORT:
               begin
-                Data.d2d_teleport := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_D2D]]);
-                Data.silent_teleport := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_TELEPORT_SILENT]]);
-                Data.TlpDir := NameToDirAdv(vleObjectProperty.Values[_lc[I_PROP_TR_TELEPORT_DIR]]);
+                Data.d2d_teleport := NameToBool(vleObjectProperty.Values[MsgPropTrD2d]);
+                Data.silent_teleport := NameToBool(vleObjectProperty.Values[MsgPropTrTeleportSilent]);
+                Data.TlpDir := NameToDirAdv(vleObjectProperty.Values[MsgPropTrTeleportDir]);
               end;
 
             TRIGGER_SOUND:
               begin
-                s := vleObjectProperty.Values[_lc[I_PROP_TR_SOUND_NAME]];
+                s := utf2win(vleObjectProperty.Values[MsgPropTrSoundName]);
                 FillByte(Data.SoundName[0], 64, 0);
                 if s <> '' then
-                  Move(Data.SoundName[0], s[1], Min(Length(s), 64));
+                  Move(s[1], Data.SoundName[0], Min(Length(s), 64));
 
-                Data.Volume := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SOUND_VOLUME]], 0), 255);
-                Data.Pan := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SOUND_PAN]], 0), 255);
-                Data.PlayCount := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SOUND_COUNT]], 0), 255);
-                Data.Local := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_SOUND_LOCAL]]);
-                Data.SoundSwitch := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_SOUND_SWITCH]]);
+                Data.Volume := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrSoundVolume], 0), 255);
+                Data.Pan := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrSoundPan], 0), 255);
+                Data.PlayCount := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrSoundCount], 0), 255);
+                Data.Local := NameToBool(vleObjectProperty.Values[MsgPropTrSoundLocal]);
+                Data.SoundSwitch := NameToBool(vleObjectProperty.Values[MsgPropTrSoundSwitch]);
               end;
 
             TRIGGER_SPAWNMONSTER:
               begin
-                Data.MonType := StrToMonster(vleObjectProperty.Values[_lc[I_PROP_TR_MONSTER_TYPE]]);
-                Data.MonDir := Byte(NameToDir(vleObjectProperty.Values[_lc[I_PROP_DIRECTION]]));
-                Data.MonHealth := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_HEALTH]], 0), 1000000);
+                Data.MonType := StrToMonster(vleObjectProperty.Values[MsgPropTrMonsterType]);
+                Data.MonDir := Byte(NameToDir(vleObjectProperty.Values[MsgPropDirection]));
+                Data.MonHealth := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrHealth], 0), 1000000);
                 if Data.MonHealth < 0 then
                   Data.MonHealth := 0;
-                Data.MonActive := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_MONSTER_ACTIVE]]);
-                Data.MonCount := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_COUNT]], 0), 64);
+                Data.MonActive := NameToBool(vleObjectProperty.Values[MsgPropTrMonsterActive]);
+                Data.MonCount := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrCount], 0), 64);
                 if Data.MonCount < 1 then
                   Data.MonCount := 1;
-                Data.MonEffect := StrToEffect(vleObjectProperty.Values[_lc[I_PROP_TR_FX_TYPE]]);
-                Data.MonMax := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SPAWN_MAX]], 0), 65535);
-                Data.MonDelay := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SPAWN_DELAY]], 0), 65535);
+                Data.MonEffect := StrToEffect(vleObjectProperty.Values[MsgPropTrFxType]);
+                Data.MonMax := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrSpawnMax], 0), 65535);
+                Data.MonDelay := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrSpawnDelay], 0), 65535);
                 Data.MonBehav := 0;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_MONSTER_BEHAVIOUR]] = _lc[I_PROP_TR_MONSTER_BEHAVIOUR_1] then
+                if vleObjectProperty.Values[MsgPropTrMonsterBehaviour] = MsgPropTrMonsterBehaviour1 then
                   Data.MonBehav := 1;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_MONSTER_BEHAVIOUR]] = _lc[I_PROP_TR_MONSTER_BEHAVIOUR_2] then
+                if vleObjectProperty.Values[MsgPropTrMonsterBehaviour] = MsgPropTrMonsterBehaviour2 then
                   Data.MonBehav := 2;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_MONSTER_BEHAVIOUR]] = _lc[I_PROP_TR_MONSTER_BEHAVIOUR_3] then
+                if vleObjectProperty.Values[MsgPropTrMonsterBehaviour] = MsgPropTrMonsterBehaviour3 then
                   Data.MonBehav := 3;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_MONSTER_BEHAVIOUR]] = _lc[I_PROP_TR_MONSTER_BEHAVIOUR_4] then
+                if vleObjectProperty.Values[MsgPropTrMonsterBehaviour] = MsgPropTrMonsterBehaviour4 then
                   Data.MonBehav := 4;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_MONSTER_BEHAVIOUR]] = _lc[I_PROP_TR_MONSTER_BEHAVIOUR_5] then
+                if vleObjectProperty.Values[MsgPropTrMonsterBehaviour] = MsgPropTrMonsterBehaviour5 then
                   Data.MonBehav := 5;
               end;
 
             TRIGGER_SPAWNITEM:
               begin
-                Data.ItemType := StrToItem(vleObjectProperty.Values[_lc[I_PROP_TR_ITEM_TYPE]]);
-                Data.ItemOnlyDM := NameToBool(vleObjectProperty.Values[_lc[I_PROP_DM_ONLY]]);
-                Data.ItemFalls := NameToBool(vleObjectProperty.Values[_lc[I_PROP_ITEM_FALLS]]);
-                Data.ItemCount := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_COUNT]], 0), 64);
+                Data.ItemType := StrToItem(vleObjectProperty.Values[MsgPropTrItemType]);
+                Data.ItemOnlyDM := NameToBool(vleObjectProperty.Values[MsgPropDmOnly]);
+                Data.ItemFalls := NameToBool(vleObjectProperty.Values[MsgPropItemFalls]);
+                Data.ItemCount := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrCount], 0), 64);
                 if Data.ItemCount < 1 then
                   Data.ItemCount := 1;
-                Data.ItemEffect := StrToEffect(vleObjectProperty.Values[_lc[I_PROP_TR_FX_TYPE]]);
-                Data.ItemMax := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SPAWN_MAX]], 0), 65535);
-                Data.ItemDelay := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SPAWN_DELAY]], 0), 65535);
+                Data.ItemEffect := StrToEffect(vleObjectProperty.Values[MsgPropTrFxType]);
+                Data.ItemMax := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrSpawnMax], 0), 65535);
+                Data.ItemDelay := Min(StrToIntDef(vleObjectProperty.Values[MsgPropTrSpawnDelay], 0), 65535);
               end;
 
             TRIGGER_MUSIC:
               begin
-                s := vleObjectProperty.Values[_lc[I_PROP_TR_MUSIC_NAME]];
+                s := utf2win(vleObjectProperty.Values[MsgPropTrMusicName]);
                 FillByte(Data.MusicName[0], 64, 0);
                 if s <> '' then
-                  Move(Data.MusicName[0], s[1], Min(Length(s), 64));
+                  Move(s[1], Data.MusicName[0], Min(Length(s), 64));
 
-                if vleObjectProperty.Values[_lc[I_PROP_TR_MUSIC_ACT]] = _lc[I_PROP_TR_MUSIC_ON] then
+                if vleObjectProperty.Values[MsgPropTrMusicAct] = MsgPropTrMusicOn then
                   Data.MusicAction := 1
                 else
-                  Data.MusicAction := 2;
+                  Data.MusicAction := 0;
               end;
 
             TRIGGER_PUSH:
               begin
                 Data.PushAngle := Min(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_PUSH_ANGLE]], 0), 360);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrPushAngle], 0), 360);
                 Data.PushForce := Min(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_PUSH_FORCE]], 0), 255);
-                Data.ResetVel := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_PUSH_RESET]]);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrPushForce], 0), 255);
+                Data.ResetVel := NameToBool(vleObjectProperty.Values[MsgPropTrPushReset]);
               end;
 
             TRIGGER_SCORE:
               begin
                 Data.ScoreAction := 0;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_SCORE_ACT]] = _lc[I_PROP_TR_SCORE_ACT_1] then
+                if vleObjectProperty.Values[MsgPropTrScoreAct] = MsgPropTrScoreAct1 then
                   Data.ScoreAction := 1
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SCORE_ACT]] = _lc[I_PROP_TR_SCORE_ACT_2] then
+                else if vleObjectProperty.Values[MsgPropTrScoreAct] = MsgPropTrScoreAct2 then
                   Data.ScoreAction := 2
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SCORE_ACT]] = _lc[I_PROP_TR_SCORE_ACT_3] then
+                else if vleObjectProperty.Values[MsgPropTrScoreAct] = MsgPropTrScoreAct3 then
                   Data.ScoreAction := 3;
                 Data.ScoreCount := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_COUNT]], 0), 0), 255);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrCount], 0), 0), 255);
                 Data.ScoreTeam := 0;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_SCORE_TEAM]] = _lc[I_PROP_TR_SCORE_TEAM_1] then
+                if vleObjectProperty.Values[MsgPropTrScoreTeam] = MsgPropTrScoreTeam1 then
                   Data.ScoreTeam := 1
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SCORE_TEAM]] = _lc[I_PROP_TR_SCORE_TEAM_2] then
+                else if vleObjectProperty.Values[MsgPropTrScoreTeam] = MsgPropTrScoreTeam2 then
                   Data.ScoreTeam := 2
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SCORE_TEAM]] = _lc[I_PROP_TR_SCORE_TEAM_3] then
+                else if vleObjectProperty.Values[MsgPropTrScoreTeam] = MsgPropTrScoreTeam3 then
                   Data.ScoreTeam := 3;
-                Data.ScoreCon := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_SCORE_CON]]);
-                Data.ScoreMsg := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_SCORE_MSG]]);
+                Data.ScoreCon := NameToBool(vleObjectProperty.Values[MsgPropTrScoreCon]);
+                Data.ScoreMsg := NameToBool(vleObjectProperty.Values[MsgPropTrScoreMsg]);
               end;
 
             TRIGGER_MESSAGE:
               begin
                 Data.MessageKind := 0;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_KIND]] = _lc[I_PROP_TR_MESSAGE_KIND_1] then
+                if vleObjectProperty.Values[MsgPropTrMessageKind] = MsgPropTrMessageKind1 then
                   Data.MessageKind := 1;
 
                 Data.MessageSendTo := 0;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TO]] = _lc[I_PROP_TR_MESSAGE_TO_1] then
+                if vleObjectProperty.Values[MsgPropTrMessageTo] = MsgPropTrMessageTo1 then
                   Data.MessageSendTo := 1
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TO]] = _lc[I_PROP_TR_MESSAGE_TO_2] then
+                else if vleObjectProperty.Values[MsgPropTrMessageTo] = MsgPropTrMessageTo2 then
                   Data.MessageSendTo := 2
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TO]] = _lc[I_PROP_TR_MESSAGE_TO_3] then
+                else if vleObjectProperty.Values[MsgPropTrMessageTo] = MsgPropTrMessageTo3 then
                   Data.MessageSendTo := 3
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TO]] = _lc[I_PROP_TR_MESSAGE_TO_4] then
+                else if vleObjectProperty.Values[MsgPropTrMessageTo] = MsgPropTrMessageTo4 then
                   Data.MessageSendTo := 4
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TO]] = _lc[I_PROP_TR_MESSAGE_TO_5] then
+                else if vleObjectProperty.Values[MsgPropTrMessageTo] = MsgPropTrMessageTo5 then
                   Data.MessageSendTo := 5;
 
-                s := vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TEXT]];
+                s := utf2win(vleObjectProperty.Values[MsgPropTrMessageText]);
                 FillByte(Data.MessageText[0], 100, 0);
                 if s <> '' then
-                  Move(Data.MessageText[0], s[1], Min(Length(s), 100));
+                  Move(s[1], Data.MessageText[0], Min(Length(s), 100));
 
                 Data.MessageTime := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TIME]], 0), 0), 65535);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrMessageTime], 0), 0), 65535);
               end;
 
             TRIGGER_DAMAGE:
               begin
                 Data.DamageValue := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_DAMAGE_VALUE]], 0), 0), 65535);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrDamageValue], 0), 0), 65535);
                 Data.DamageInterval := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_INTERVAL]], 0), 0), 65535);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrInterval], 0), 0), 65535);
+                s := vleObjectProperty.Values[MsgPropTrDamageKind];
+                if s = MsgPropTrDamageKind3 then
+                  Data.DamageKind := 3
+                else if s = MsgPropTrDamageKind4 then
+                  Data.DamageKind := 4
+                else if s = MsgPropTrDamageKind5 then
+                  Data.DamageKind := 5
+                else if s = MsgPropTrDamageKind6 then
+                  Data.DamageKind := 6
+                else if s = MsgPropTrDamageKind7 then
+                  Data.DamageKind := 7
+                else if s = MsgPropTrDamageKind8 then
+                  Data.DamageKind := 8
+                else
+                  Data.DamageKind := 0;
               end;
 
             TRIGGER_HEALTH:
               begin
                 Data.HealValue := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_HEALTH]], 0), 0), 65535);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrHealth], 0), 0), 65535);
                 Data.HealInterval := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_INTERVAL]], 0), 0), 65535);
-                Data.HealMax := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_HEALTH_MAX]]);
-                Data.HealSilent := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_SILENT]]);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrInterval], 0), 0), 65535);
+                Data.HealMax := NameToBool(vleObjectProperty.Values[MsgPropTrHealthMax]);
+                Data.HealSilent := NameToBool(vleObjectProperty.Values[MsgPropTrSilent]);
               end;
 
             TRIGGER_SHOT:
               begin
-                Data.ShotType := StrToShot(vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_TYPE]]);
-                Data.ShotSound := NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_SOUND]]);
+                Data.ShotType := StrToShot(vleObjectProperty.Values[MsgPropTrShotType]);
+                Data.ShotSound := NameToBool(vleObjectProperty.Values[MsgPropTrShotSound]);
                 Data.ShotTarget := 0;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_TO]] = _lc[I_PROP_TR_SHOT_TO_1] then
+                if vleObjectProperty.Values[MsgPropTrShotTo] = MsgPropTrShotTo1 then
                   Data.ShotTarget := 1
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_TO]] = _lc[I_PROP_TR_SHOT_TO_2] then
+                else if vleObjectProperty.Values[MsgPropTrShotTo] = MsgPropTrShotTo2 then
                   Data.ShotTarget := 2
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_TO]] = _lc[I_PROP_TR_SHOT_TO_3] then
+                else if vleObjectProperty.Values[MsgPropTrShotTo] = MsgPropTrShotTo3 then
                   Data.ShotTarget := 3
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_TO]] = _lc[I_PROP_TR_SHOT_TO_4] then
+                else if vleObjectProperty.Values[MsgPropTrShotTo] = MsgPropTrShotTo4 then
                   Data.ShotTarget := 4
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_TO]] = _lc[I_PROP_TR_SHOT_TO_5] then
+                else if vleObjectProperty.Values[MsgPropTrShotTo] = MsgPropTrShotTo5 then
                   Data.ShotTarget := 5
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_TO]] = _lc[I_PROP_TR_SHOT_TO_6] then
+                else if vleObjectProperty.Values[MsgPropTrShotTo] = MsgPropTrShotTo6 then
                   Data.ShotTarget := 6;
                 Data.ShotIntSight := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_SIGHT]], 0), 0), 65535);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrShotSight], 0), 0), 65535);
                 Data.ShotAim := 0;
-                if vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_AIM]] = _lc[I_PROP_TR_SHOT_AIM_1] then
+                if vleObjectProperty.Values[MsgPropTrShotAim] = MsgPropTrShotAim1 then
                   Data.ShotAim := 1
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_AIM]] = _lc[I_PROP_TR_SHOT_AIM_2] then
+                else if vleObjectProperty.Values[MsgPropTrShotAim] = MsgPropTrShotAim2 then
                   Data.ShotAim := 2
-                else if vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_AIM]] = _lc[I_PROP_TR_SHOT_AIM_3] then
+                else if vleObjectProperty.Values[MsgPropTrShotAim] = MsgPropTrShotAim3 then
                   Data.ShotAim := 3;
                 Data.ShotAngle := Min(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_ANGLE]], 0), 360);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrShotAngle], 0), 360);
                 Data.ShotWait := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EX_DELAY]], 0), 0), 65535);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrExDelay], 0), 0), 65535);
                 Data.ShotAccuracy := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_ACC]], 0), 0), 65535);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrShotAcc], 0), 0), 65535);
                 Data.ShotAmmo := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_AMMO]], 0), 0), 65535);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrShotAmmo], 0), 0), 65535);
                 Data.ShotIntReload := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SHOT_RELOAD]], 0), 0), 65535);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrShotReload], 0), 0), 65535);
               end;
 
             TRIGGER_EFFECT:
               begin
                 Data.FXCount := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_COUNT]], 0), 0), 255);
-                if vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_TYPE]] = _lc[I_PROP_TR_EFFECT_PARTICLE] then
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrCount], 0), 0), 255);
+                if vleObjectProperty.Values[MsgPropTrEffectType] = MsgPropTrEffectParticle then
                 begin
                   Data.FXType := TRIGGER_EFFECT_PARTICLE;
                   Data.FXSubType := TRIGGER_EFFECT_SLIQUID;
-                  if vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SUBTYPE]] = _lc[I_PROP_TR_EFFECT_SLIQUID] then
+                  if vleObjectProperty.Values[MsgPropTrEffectSubtype] = MsgPropTrEffectSliquid then
                     Data.FXSubType := TRIGGER_EFFECT_SLIQUID
-                  else if vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SUBTYPE]] = _lc[I_PROP_TR_EFFECT_LLIQUID] then
+                  else if vleObjectProperty.Values[MsgPropTrEffectSubtype] = MsgPropTrEffectLliquid then
                     Data.FXSubType := TRIGGER_EFFECT_LLIQUID
-                  else if vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SUBTYPE]] = _lc[I_PROP_TR_EFFECT_DLIQUID] then
+                  else if vleObjectProperty.Values[MsgPropTrEffectSubtype] = MsgPropTrEffectDliquid then
                     Data.FXSubType := TRIGGER_EFFECT_DLIQUID
-                  else if vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SUBTYPE]] = _lc[I_PROP_TR_EFFECT_BLOOD] then
+                  else if vleObjectProperty.Values[MsgPropTrEffectSubtype] = MsgPropTrEffectBlood then
                     Data.FXSubType := TRIGGER_EFFECT_BLOOD
-                  else if vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SUBTYPE]] = _lc[I_PROP_TR_EFFECT_SPARK] then
+                  else if vleObjectProperty.Values[MsgPropTrEffectSubtype] = MsgPropTrEffectSpark then
                     Data.FXSubType := TRIGGER_EFFECT_SPARK
-                  else if vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SUBTYPE]] = _lc[I_PROP_TR_EFFECT_BUBBLE] then
+                  else if vleObjectProperty.Values[MsgPropTrEffectSubtype] = MsgPropTrEffectBubble then
                     Data.FXSubType := TRIGGER_EFFECT_BUBBLE;
                 end else
                 begin
                   Data.FXType := TRIGGER_EFFECT_ANIMATION;
-                  Data.FXSubType := StrToEffect(vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SUBTYPE]]);
+                  Data.FXSubType := StrToEffect(vleObjectProperty.Values[MsgPropTrEffectSubtype]);
                 end;
                 a := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_COLOR]], 0), 0), $FFFFFF);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrEffectColor], 0), 0), $FFFFFF);
                 Data.FXColorR := a and $FF;
                 Data.FXColorG := (a shr 8) and $FF;
                 Data.FXColorB := (a shr 16) and $FF;
-                if NameToBool(vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_CENTER]]) then
+                if NameToBool(vleObjectProperty.Values[MsgPropTrEffectCenter]) then
                   Data.FXPos := 0
                 else
                   Data.FXPos := 1;
                 Data.FXWait := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EX_DELAY]], 0), 0), 65535);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrExDelay], 0), 0), 65535);
                 Data.FXVelX := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_VELX]], 0), -128), 127);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrEffectVelx], 0), -128), 127);
                 Data.FXVelY := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_VELY]], 0), -128), 127);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrEffectVely], 0), -128), 127);
                 Data.FXSpreadL := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SPL]], 0), 0), 255);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrEffectSpl], 0), 0), 255);
                 Data.FXSpreadR := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SPR]], 0), 0), 255);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrEffectSpr], 0), 0), 255);
                 Data.FXSpreadU := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SPU]], 0), 0), 255);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrEffectSpu], 0), 0), 255);
                 Data.FXSpreadD := Min(Max(
-                  StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_EFFECT_SPD]], 0), 0), 255);
+                  StrToIntDef(vleObjectProperty.Values[MsgPropTrEffectSpd], 0), 0), 255);
               end;
           end;
         end;
@@ -4991,9 +5553,9 @@ begin
   if i = -1 then
     Exit;
 
-  if MessageBox(0, PChar(Format(_lc[I_MSG_DEL_TEXTURE_PROMT],
+  if Application.MessageBox(PChar(Format(MsgMsgDelTexturePromt,
                                 [SelectedTexture()])),
-                PChar(_lc[I_MSG_DEL_TEXTURE]),
+                PChar(MsgMsgDelTexture),
                 MB_ICONQUESTION or MB_YESNO or
                 MB_DEFBUTTON1) <> idYes then
     Exit;
@@ -5003,7 +5565,7 @@ begin
       if (gPanels[a].PanelType <> 0) and
          (gPanels[a].TextureName = SelectedTexture()) then
       begin
-        ErrorMessageBox(_lc[I_MSG_DEL_TEXTURE_CANT]);
+        ErrorMessageBox(MsgMsgDelTextureCant);
         Exit;
       end;
 
@@ -5017,10 +5579,7 @@ end;
 
 procedure TMainForm.aNewMapExecute(Sender: TObject);
 begin
-  if (MessageBox(0, PChar(_lc[I_MSG_CLEAR_MAP_PROMT]),
-                 PChar(_lc[I_MSG_CLEAR_MAP]),
-                 MB_ICONQUESTION or MB_YESNO or
-                 MB_DEFBUTTON1) = mrYes) then
+  if Application.MessageBox(PChar(MsgMsgClearMapPromt), PChar(MsgMsgClearMap), MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON1) = mrYes then
     FullClear();
 end;
 
@@ -5268,12 +5827,18 @@ procedure TMainForm.aPasteObjectExecute(Sender: TObject);
 var
   a, h: Integer;
   CopyBuffer: TCopyRecArray;
-  res: Boolean;
+  res, rel: Boolean;
   swad, ssec, sres: String;
+  NoTextureID: DWORD;
+  pmin: TPoint;
 begin
   CopyBuffer := nil;
+  NoTextureID := 0;
+  pmin.X := High(pmin.X);
+  pmin.Y := High(pmin.Y);
 
-  StringToCopyBuffer(ClipBoard.AsText, CopyBuffer);
+  StringToCopyBuffer(ClipBoard.AsText, CopyBuffer, pmin);
+  rel := not(ssShift in GetKeyShiftState());
 
   if CopyBuffer = nil then
     Exit;
@@ -5288,8 +5853,11 @@ begin
         OBJECT_PANEL:
           if Panel <> nil then
           begin
-            Panel^.X := Panel^.X + 16;
-            Panel^.Y := Panel^.Y + 16;
+            if rel then
+            begin
+              Panel^.X := Panel^.X - pmin.X - MapOffset.X + 32;
+              Panel^.Y := Panel^.Y - pmin.Y - MapOffset.Y + 32;
+            end;
 
             Panel^.TextureID := TEXTURE_SPECIAL_NONE;
             Panel^.TextureWidth := 1;
@@ -5321,14 +5889,18 @@ begin
                       g_GetTextureSizeByName(Panel^.TextureName,
                         Panel^.TextureWidth, Panel^.TextureHeight)
                     else
-                      Panel^.TextureName := '';
+                      if g_GetTexture('NOTEXTURE', NoTextureID) then
+                      begin
+                        Panel^.TextureID := TEXTURE_SPECIAL_NOTEXTURE;
+                        g_GetTextureSizeByID(NoTextureID, Panel^.TextureWidth, Panel^.TextureHeight);
+                      end;
                   end
                 else // Спец.текстура:
                   begin
                     Panel^.TextureID := SpecialTextureID(Panel^.TextureName);
                     with MainForm.lbTextureList.Items do
-                      if IndexOf(win2utf(Panel^.TextureName)) = -1 then
-                        Add(win2utf(Panel^.TextureName));
+                      if IndexOf(Panel^.TextureName) = -1 then
+                        Add(Panel^.TextureName);
                   end;
               end;
 
@@ -5340,8 +5912,11 @@ begin
 
         OBJECT_ITEM:
           begin
-            Item.X := Item.X + 16;
-            Item.Y := Item.Y + 16;
+            if rel then
+            begin
+              Item.X := Item.X - pmin.X - MapOffset.X + 32;
+              Item.Y := Item.Y - pmin.Y - MapOffset.Y + 32;
+            end;
 
             ID := AddItem(Item);
             Undo_Add(OBJECT_ITEM, ID, a > 0);
@@ -5350,8 +5925,11 @@ begin
 
         OBJECT_MONSTER:
           begin
-            Monster.X := Monster.X + 16;
-            Monster.Y := Monster.Y + 16;
+            if rel then
+            begin
+              Monster.X := Monster.X - pmin.X - MapOffset.X + 32;
+              Monster.Y := Monster.Y - pmin.Y - MapOffset.Y + 32;
+            end;
 
             ID := AddMonster(Monster);
             Undo_Add(OBJECT_MONSTER, ID, a > 0);
@@ -5360,8 +5938,11 @@ begin
 
         OBJECT_AREA:
           begin
-            Area.X := Area.X + 16;
-            Area.Y := Area.Y + 16;
+            if rel then
+            begin
+              Area.X := Area.X - pmin.X - MapOffset.X + 32;
+              Area.Y := Area.Y - pmin.Y - MapOffset.Y + 32;
+            end;
 
             ID := AddArea(Area);
             Undo_Add(OBJECT_AREA, ID, a > 0);
@@ -5370,8 +5951,48 @@ begin
 
         OBJECT_TRIGGER:
           begin
-            Trigger.X := Trigger.X + 16;
-            Trigger.Y := Trigger.Y + 16;
+            if rel then
+              with Trigger do
+              begin
+                X := X - pmin.X - MapOffset.X + 32;
+                Y := Y - pmin.Y - MapOffset.Y + 32;
+
+                case TriggerType of
+                  TRIGGER_TELEPORT:
+                    begin
+                      Data.TargetPoint.X :=
+                      Data.TargetPoint.X - pmin.X - MapOffset.X + 32;
+                      Data.TargetPoint.Y :=
+                      Data.TargetPoint.Y - pmin.Y - MapOffset.Y + 32;
+                    end;
+                  TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF:
+                    begin
+                      Data.tX := Data.tX - pmin.X - MapOffset.X + 32;
+                      Data.tY := Data.tY - pmin.Y - MapOffset.Y + 32;
+                    end;
+                  TRIGGER_SPAWNMONSTER:
+                    begin
+                      Data.MonPos.X :=
+                      Data.MonPos.X - pmin.X - MapOffset.X + 32;
+                      Data.MonPos.Y :=
+                      Data.MonPos.Y - pmin.Y - MapOffset.Y + 32;
+                    end;
+                  TRIGGER_SPAWNITEM:
+                    begin
+                      Data.ItemPos.X :=
+                      Data.ItemPos.X - pmin.X - MapOffset.X + 32;
+                      Data.ItemPos.Y :=
+                      Data.ItemPos.Y - pmin.Y - MapOffset.Y + 32;
+                    end;
+                  TRIGGER_SHOT:
+                    begin
+                      Data.ShotPos.X :=
+                      Data.ShotPos.X - pmin.X - MapOffset.X + 32;
+                      Data.ShotPos.Y :=
+                      Data.ShotPos.Y - pmin.Y - MapOffset.Y + 32;
+                    end;
+                end;
+              end;
 
             ID := AddTrigger(Trigger);
             Undo_Add(OBJECT_TRIGGER, ID, a > 0);
@@ -5428,11 +6049,11 @@ var
 begin
   Key := vleObjectProperty.Keys[vleObjectProperty.Row];
 
-  if Key = _lc[I_PROP_PANEL_TYPE] then
+  if Key = MsgPropPanelType then
     begin
       with ChooseTypeForm, vleObjectProperty do
       begin // Выбор типа панели:
-        Caption := _lc[I_PROP_PANEL_TYPE];
+        Caption := MsgPropPanelType;
         lbTypeSelect.Items.Clear();
 
         for b := 0 to High(PANELNAMES) do
@@ -5446,58 +6067,59 @@ begin
         begin
           b := lbTypeSelect.ItemIndex;
           Values[Key] := PANELNAMES[b];
-          bApplyProperty.Click();
+          vleObjectPropertyApply(Sender);
         end;
       end
     end
-  else if Key = _lc[I_PROP_TR_TELEPORT_TO] then
+  else if Key = MsgPropTrTeleportTo then
     SelectFlag := SELECTFLAG_TELEPORT
-  else if Key = _lc[I_PROP_TR_SPAWN_TO] then
+  else if Key = MsgPropTrSpawnTo then
     SelectFlag := SELECTFLAG_SPAWNPOINT
-  else if (Key = _lc[I_PROP_TR_DOOR_PANEL]) or
-          (Key = _lc[I_PROP_TR_TRAP_PANEL]) then
+  else if (Key = MsgPropTrDoorPanel) or
+          (Key = MsgPropTrTrapPanel) then
     SelectFlag := SELECTFLAG_DOOR
-  else if Key = _lc[I_PROP_TR_TEXTURE_PANEL] then
+  else if Key = MsgPropTrTexturePanel then
   begin
     DrawPressRect := False;
     SelectFlag := SELECTFLAG_TEXTURE;
   end
-  else if Key = _lc[I_PROP_TR_SHOT_PANEL] then
+  else if Key = MsgPropTrShotPanel then
     SelectFlag := SELECTFLAG_SHOTPANEL
-  else if Key = _lc[I_PROP_TR_LIFT_PANEL] then
+  else if Key = MsgPropTrLiftPanel then
     SelectFlag := SELECTFLAG_LIFT
-  else if key = _lc[I_PROP_TR_EX_MONSTER] then
+  else if key = MsgPropTrExMonster then
     SelectFlag := SELECTFLAG_MONSTER
-  else if Key = _lc[I_PROP_TR_EX_AREA] then
+  else if Key = MsgPropTrExArea then
   begin
     SelectFlag := SELECTFLAG_NONE;
     DrawPressRect := True;
   end
-  else if Key = _lc[I_PROP_TR_NEXT_MAP] then
+  else if Key = MsgPropTrNextMap then
     begin // Выбор следующей карты:
       g_ProcessResourceStr(OpenedMap, @FileName, nil, nil);
+      SelectMapForm.Caption := MsgCapSelect;
       SelectMapForm.GetMaps(FileName);
 
       if SelectMapForm.ShowModal() = mrOK then
       begin
         vleObjectProperty.Values[Key] := SelectMapForm.lbMapList.Items[SelectMapForm.lbMapList.ItemIndex];
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if (Key = _lc[I_PROP_TR_SOUND_NAME]) or
-          (Key = _lc[I_PROP_TR_MUSIC_NAME]) then
+  else if (Key = MsgPropTrSoundName) or
+          (Key = MsgPropTrMusicName) then
     begin // Выбор файла звука/музыки:
       AddSoundForm.OKFunction := nil;
       AddSoundForm.lbResourcesList.MultiSelect := False;
-      AddSoundForm.SetResource := utf2win(vleObjectProperty.Values[Key]);
+      AddSoundForm.SetResource := vleObjectProperty.Values[Key];
 
       if (AddSoundForm.ShowModal() = mrOk) then
       begin
-        vleObjectProperty.Values[Key] := utf2win(AddSoundForm.ResourceName);
-        bApplyProperty.Click();
+        vleObjectProperty.Values[Key] := AddSoundForm.ResourceName;
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if Key = _lc[I_PROP_TR_ACTIVATION] then
+  else if Key = MsgPropTrActivation then
     with ActivationTypeForm, vleObjectProperty do
     begin // Выбор типов активации:
       cbPlayerCollide.Checked := Pos('PC', Values[Key]) > 0;
@@ -5524,10 +6146,10 @@ begin
           b := b or ACTIVATE_NOMONSTER;
 
         Values[Key] := ActivateToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if Key = _lc[I_PROP_TR_KEYS] then
+  else if Key = MsgPropTrKeys then
     with KeysForm, vleObjectProperty do
     begin // Выбор необходимых ключей:
       cbRedKey.Checked := Pos('RK', Values[Key]) > 0;
@@ -5551,13 +6173,13 @@ begin
           b := b or KEY_BLUETEAM;
 
         Values[Key] := KeyToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if Key = _lc[I_PROP_TR_FX_TYPE] then
+  else if Key = MsgPropTrFxType then
     with ChooseTypeForm, vleObjectProperty do
     begin // Выбор типа эффекта:
-      Caption := _lc[I_CAP_FX_TYPE];
+      Caption := MsgCapFxType;
       lbTypeSelect.Items.Clear();
 
       for b := EFFECT_NONE to EFFECT_FIRE do
@@ -5569,13 +6191,13 @@ begin
       begin
         b := lbTypeSelect.ItemIndex;
         Values[Key] := EffectToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if Key = _lc[I_PROP_TR_MONSTER_TYPE] then
+  else if Key = MsgPropTrMonsterType then
     with ChooseTypeForm, vleObjectProperty do
     begin // Выбор типа монстра:
-      Caption := _lc[I_CAP_MONSTER_TYPE];
+      Caption := MsgCapMonsterType;
       lbTypeSelect.Items.Clear();
 
       for b := MONSTER_DEMON to MONSTER_MAN do
@@ -5587,13 +6209,13 @@ begin
       begin
         b := lbTypeSelect.ItemIndex + MONSTER_DEMON;
         Values[Key] := MonsterToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if Key = _lc[I_PROP_TR_ITEM_TYPE] then
+  else if Key = MsgPropTrItemType then
     with ChooseTypeForm, vleObjectProperty do
     begin // Выбор типа предмета:
-      Caption := _lc[I_CAP_ITEM_TYPE];
+      Caption := MsgCapItemType;
       lbTypeSelect.Items.Clear();
 
       for b := ITEM_MEDKIT_SMALL to ITEM_KEY_BLUE do
@@ -5616,13 +6238,13 @@ begin
         if b >= ITEM_WEAPON_KASTET then
           b := b + 2;
         Values[Key] := ItemToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if Key = _lc[I_PROP_TR_SHOT_TYPE] then
+  else if Key = MsgPropTrShotType then
     with ChooseTypeForm, vleObjectProperty do
     begin // Выбор типа предмета:
-      Caption := _lc[I_PROP_TR_SHOT_TYPE];
+      Caption := MsgPropTrShotType;
       lbTypeSelect.Items.Clear();
 
       for b := TRIGGER_SHOT_PISTOL to TRIGGER_SHOT_MAX do
@@ -5634,18 +6256,18 @@ begin
       begin
         b := lbTypeSelect.ItemIndex;
         Values[Key] := ShotToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if Key = _lc[I_PROP_TR_EFFECT_TYPE] then
+  else if Key = MsgPropTrEffectType then
     with ChooseTypeForm, vleObjectProperty do
     begin // Выбор типа эффекта:
-      Caption := _lc[I_CAP_FX_TYPE];
+      Caption := MsgCapFxType;
       lbTypeSelect.Items.Clear();
 
-      lbTypeSelect.Items.Add(_lc[I_PROP_TR_EFFECT_PARTICLE]);
-      lbTypeSelect.Items.Add(_lc[I_PROP_TR_EFFECT_ANIMATION]);
-      if Values[Key] = _lc[I_PROP_TR_EFFECT_ANIMATION] then
+      lbTypeSelect.Items.Add(MsgPropTrEffectParticle);
+      lbTypeSelect.Items.Add(MsgPropTrEffectAnimation);
+      if Values[Key] = MsgPropTrEffectAnimation then
         lbTypeSelect.ItemIndex := 1
       else
         lbTypeSelect.ItemIndex := 0;
@@ -5654,19 +6276,19 @@ begin
       begin
         b := lbTypeSelect.ItemIndex;
         if b = 0 then
-          Values[Key] := _lc[I_PROP_TR_EFFECT_PARTICLE]
+          Values[Key] := MsgPropTrEffectParticle
         else
-          Values[Key] := _lc[I_PROP_TR_EFFECT_ANIMATION];
-        bApplyProperty.Click();
+          Values[Key] := MsgPropTrEffectAnimation;
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if Key = _lc[I_PROP_TR_EFFECT_SUBTYPE] then
+  else if Key = MsgPropTrEffectSubtype then
     with ChooseTypeForm, vleObjectProperty do
     begin // Выбор подтипа эффекта:
-      Caption := _lc[I_CAP_FX_TYPE];
+      Caption := MsgCapFxType;
       lbTypeSelect.Items.Clear();
 
-      if Values[_lc[I_PROP_TR_EFFECT_TYPE]] = _lc[I_PROP_TR_EFFECT_ANIMATION] then
+      if Values[MsgPropTrEffectType] = MsgPropTrEffectAnimation then
       begin
         for b := EFFECT_TELEPORT to EFFECT_FIRE do
           lbTypeSelect.Items.Add(EffectToStr(b));
@@ -5674,22 +6296,22 @@ begin
         lbTypeSelect.ItemIndex := StrToEffect(Values[Key]) - 1;
       end else
       begin
-        lbTypeSelect.Items.Add(_lc[I_PROP_TR_EFFECT_SLIQUID]);
-        lbTypeSelect.Items.Add(_lc[I_PROP_TR_EFFECT_LLIQUID]);
-        lbTypeSelect.Items.Add(_lc[I_PROP_TR_EFFECT_DLIQUID]);
-        lbTypeSelect.Items.Add(_lc[I_PROP_TR_EFFECT_BLOOD]);
-        lbTypeSelect.Items.Add(_lc[I_PROP_TR_EFFECT_SPARK]);
-        lbTypeSelect.Items.Add(_lc[I_PROP_TR_EFFECT_BUBBLE]);
+        lbTypeSelect.Items.Add(MsgPropTrEffectSliquid);
+        lbTypeSelect.Items.Add(MsgPropTrEffectLliquid);
+        lbTypeSelect.Items.Add(MsgPropTrEffectDliquid);
+        lbTypeSelect.Items.Add(MsgPropTrEffectBlood);
+        lbTypeSelect.Items.Add(MsgPropTrEffectSpark);
+        lbTypeSelect.Items.Add(MsgPropTrEffectBubble);
         lbTypeSelect.ItemIndex := TRIGGER_EFFECT_SLIQUID;
-        if Values[Key] = _lc[I_PROP_TR_EFFECT_LLIQUID] then
+        if Values[Key] = MsgPropTrEffectLliquid then
           lbTypeSelect.ItemIndex := TRIGGER_EFFECT_LLIQUID;
-        if Values[Key] = _lc[I_PROP_TR_EFFECT_DLIQUID] then
+        if Values[Key] = MsgPropTrEffectDliquid then
           lbTypeSelect.ItemIndex := TRIGGER_EFFECT_DLIQUID;
-        if Values[Key] = _lc[I_PROP_TR_EFFECT_BLOOD] then
+        if Values[Key] = MsgPropTrEffectBlood then
           lbTypeSelect.ItemIndex := TRIGGER_EFFECT_BLOOD;
-        if Values[Key] = _lc[I_PROP_TR_EFFECT_SPARK] then
+        if Values[Key] = MsgPropTrEffectSpark then
           lbTypeSelect.ItemIndex := TRIGGER_EFFECT_SPARK;
-        if Values[Key] = _lc[I_PROP_TR_EFFECT_BUBBLE] then
+        if Values[Key] = MsgPropTrEffectBubble then
           lbTypeSelect.ItemIndex := TRIGGER_EFFECT_BUBBLE;
       end;
 
@@ -5697,42 +6319,50 @@ begin
       begin
         b := lbTypeSelect.ItemIndex;
 
-        if Values[_lc[I_PROP_TR_EFFECT_TYPE]] = _lc[I_PROP_TR_EFFECT_ANIMATION] then
+        if Values[MsgPropTrEffectType] = MsgPropTrEffectAnimation then
           Values[Key] := EffectToStr(b + 1)
         else begin
-          Values[Key] := _lc[I_PROP_TR_EFFECT_SLIQUID];
+          Values[Key] := MsgPropTrEffectSliquid;
           if b = TRIGGER_EFFECT_LLIQUID then
-            Values[Key] := _lc[I_PROP_TR_EFFECT_LLIQUID];
+            Values[Key] := MsgPropTrEffectLliquid;
           if b = TRIGGER_EFFECT_DLIQUID then
-            Values[Key] := _lc[I_PROP_TR_EFFECT_DLIQUID];
+            Values[Key] := MsgPropTrEffectDliquid;
           if b = TRIGGER_EFFECT_BLOOD then
-            Values[Key] := _lc[I_PROP_TR_EFFECT_BLOOD];
+            Values[Key] := MsgPropTrEffectBlood;
           if b = TRIGGER_EFFECT_SPARK then
-            Values[Key] := _lc[I_PROP_TR_EFFECT_SPARK];
+            Values[Key] := MsgPropTrEffectSpark;
           if b = TRIGGER_EFFECT_BUBBLE then
-            Values[Key] := _lc[I_PROP_TR_EFFECT_BUBBLE];
+            Values[Key] := MsgPropTrEffectBubble;
         end;
 
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if Key = _lc[I_PROP_TR_EFFECT_COLOR] then
+  else if Key = MsgPropTrEffectColor then
     with vleObjectProperty do
     begin // Выбор цвета эффекта:
       ColorDialog.Color := StrToIntDef(Values[Key], 0);
       if ColorDialog.Execute then
       begin
         Values[Key] := IntToStr(ColorDialog.Color);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
-  else if Key = _lc[I_PROP_PANEL_TEX] then
+  else if Key = MsgPropPanelTex then
     begin // Смена текстуры:
       vleObjectProperty.Values[Key] := SelectedTexture();
-      bApplyProperty.Click();
+      vleObjectPropertyApply(Sender);
     end;
 end;
 
+procedure TMainForm.vleObjectPropertyApply(Sender: TObject);
+begin
+  // hack to prevent empty ID in list
+  RenderPanel.SetFocus();
+  bApplyProperty.Click();
+  vleObjectProperty.SetFocus();
+end;
+
 procedure TMainForm.aSaveMapExecute(Sender: TObject);
 var
   FileName, Section, Res: String;
@@ -5750,135 +6380,96 @@ end;
 
 procedure TMainForm.aOpenMapExecute(Sender: TObject);
 begin
-  OpenDialog.Filter := _lc[I_FILE_FILTER_ALL];
+  OpenDialog.Filter := MsgFileFilterAll;
 
   if OpenDialog.Execute() then
   begin
-    if (Pos('.ini', LowerCase(ExtractFileName(OpenDialog.FileName))) > 0) then
-      begin // INI карты:
-        FullClear();
+    OpenMapFile(OpenDialog.FileName);
+    OpenDialog.InitialDir := ExtractFileDir(OpenDialog.FileName);
+  end;
+end;
 
-        pLoadProgress.Left := (RenderPanel.Width div 2)-(pLoadProgress.Width div 2);
-        pLoadProgress.Top := (RenderPanel.Height div 2)-(pLoadProgress.Height div 2);
-        pLoadProgress.Show();
+procedure TMainForm.OpenMapFile(FileName: String);
+begin
+  if (Pos('.ini', LowerCase(ExtractFileName(FileName))) > 0) then
+    begin // INI карты:
+      FullClear();
 
-        OpenedMap := '';
-        OpenedWAD := '';
+      pLoadProgress.Left := (RenderPanel.Width div 2)-(pLoadProgress.Width div 2);
+      pLoadProgress.Top := (RenderPanel.Height div 2)-(pLoadProgress.Height div 2);
+      pLoadProgress.Show();
 
-        LoadMapOld(OpenDialog.FileName);
+      OpenedMap := '';
+      OpenedWAD := '';
 
-        MainForm.Caption := Format('%s - %s', [FormCaption, ExtractFileName(OpenDialog.FileName)]);
+      LoadMapOld(FileName);
 
-        pLoadProgress.Hide();
-        MainForm.FormResize(Self);
-      end
-    else // Карты из WAD:
-      begin
-        OpenMap(OpenDialog.FileName, '');
-      end;
+      MainForm.Caption := Format('%s - %s', [FormCaption, ExtractFileName(FileName)]);
 
-    OpenDialog.InitialDir := ExtractFileDir(OpenDialog.FileName);
-  end;
+      pLoadProgress.Hide();
+      MainForm.FormResize(Self);
+    end
+  else // Карты из WAD:
+    begin
+      OpenMap(FileName, '');
+    end;
 end;
 
 procedure TMainForm.FormActivate(Sender: TObject);
-var
-  lang: Integer;
-  config: TConfig;
 begin
   MainForm.ActiveControl := RenderPanel;
-
-// Язык:
-  if gLanguage = '' then
-  begin
-    lang := SelectLanguageForm.ShowModal();
-    case lang of
-      1:   gLanguage := LANGUAGE_ENGLISH;
-      else gLanguage := LANGUAGE_RUSSIAN;
-    end;
-
-    config := TConfig.CreateFile(EditorDir+'Editor.cfg');
-    config.WriteStr('Editor', 'Language', gLanguage);
-    config.SaveFile(EditorDir+'Editor.cfg');
-    config.Free();
-  end;
-
-  //e_WriteLog('Read language file', MSG_NOTIFY);
-  //g_Language_Load(EditorDir+'\data\'+gLanguage+LANGUAGE_FILE_NAME);
-  g_Language_Set(gLanguage);
 end;
 
 procedure TMainForm.aDeleteMap(Sender: TObject);
 var
-  WAD: TWADEditor_1;
-  MapList: SArray;
-  MapName: Char16;
-  a: Integer;
-  str: String;
+  res: Integer;
+  FileName: String;
+  MapName: String;
 begin
-  OpenDialog.Filter := _lc[I_FILE_FILTER_WAD];
+  OpenDialog.Filter := MsgFileFilterWad;
 
   if not OpenDialog.Execute() then
     Exit;
 
-  WAD := TWADEditor_1.Create();
+  FileName := OpenDialog.FileName;
+  SelectMapForm.Caption := MsgCapRemove;
+  SelectMapForm.lbMapList.Items.Clear();
+  SelectMapForm.GetMaps(FileName);
 
-  if not WAD.ReadFile(OpenDialog.FileName) then
-  begin
-    WAD.Free();
+  if SelectMapForm.ShowModal() <> mrOK then
     Exit;
-  end;
-
-  WAD.CreateImage();
-
-  MapList := WAD.GetResourcesList('');
 
-  SelectMapForm.lbMapList.Items.Clear();
-
-  if MapList <> nil then
-    for a := 0 to High(MapList) do
-      SelectMapForm.lbMapList.Items.Add(MapList[a]);
+  MapName := SelectMapForm.lbMapList.Items[SelectMapForm.lbMapList.ItemIndex];
+  if Application.MessageBox(PChar(Format(MsgMsgDeleteMapPromt, [MapName, OpenDialog.FileName])), PChar(MsgMsgDeleteMap), MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON2) <> mrYes then
+    Exit;
 
-  if (SelectMapForm.ShowModal() = mrOK) then
+  g_DeleteResource(FileName, '', MapName, res);
+  if res <> 0 then
   begin
-    str := SelectMapForm.lbMapList.Items[SelectMapForm.lbMapList.ItemIndex];
-    MapName := '';
-    Move(MapName[0], str[1], Min(16, Length(str)));
-
-    if MessageBox(0, PChar(Format(_lc[I_MSG_DELETE_MAP_PROMT],
-                           [MapName, OpenDialog.FileName])),
-                  PChar(_lc[I_MSG_DELETE_MAP]),
-                  MB_ICONQUESTION or MB_YESNO or
-                  MB_DEFBUTTON2) <> mrYes then
-      Exit;
-
-    WAD.RemoveResource('', MapName);
-    
-    MessageBox(0, PChar(Format(_lc[I_MSG_MAP_DELETED_PROMT],
-                               [MapName])),
-               PChar(_lc[I_MSG_MAP_DELETED]),
-               MB_ICONINFORMATION or MB_OK or
-               MB_DEFBUTTON1);
+    Application.MessageBox(PChar('Cant delete map res=' + IntToStr(res)), PChar('Map not deleted!'), MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1);
+    Exit
+  end;
 
-    WAD.SaveTo(OpenDialog.FileName);
+  Application.MessageBox(
+    PChar(Format(MsgMsgMapDeletedPromt, [MapName])),
+    PChar(MsgMsgMapDeleted),
+    MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1
+  );
 
   // Удалили текущую карту - сохранять по старому ее нельзя:
-    if OpenedMap = (OpenDialog.FileName+':\'+MapName) then
-    begin
-      OpenedMap := '';
-      OpenedWAD := '';
-      MainForm.Caption := FormCaption;
-    end;
-  end;
-
-  WAD.Free();
+  if OpenedMap = (FileName + ':\' + MapName) then
+  begin
+    OpenedMap := '';
+    OpenedWAD := '';
+    MainForm.Caption := FormCaption
+  end
 end;
 
 procedure TMainForm.vleObjectPropertyKeyDown(Sender: TObject;
             var Key: Word; Shift: TShiftState);
 begin
   if Key = VK_RETURN then
-    bApplyProperty.Click();
+    vleObjectPropertyApply(Sender);
 end;
 
 procedure MovePanel(var ID: DWORD; MoveType: Byte);
@@ -6025,7 +6616,7 @@ procedure TMainForm.aSaveMapAsExecute(Sender: TObject);
 var
   idx: Integer;
 begin
-  SaveDialog.Filter := _lc[I_FILE_FILTER_WAD];
+  SaveDialog.Filter := MsgFileFilterWad;
 
   if not SaveDialog.Execute() then
     Exit;
@@ -6050,7 +6641,7 @@ begin
 
   gMapInfo.FileName := SaveDialog.FileName;
   gMapInfo.MapName := SaveMapForm.eMapName.Text;
-  UpdateCaption(win2utf(gMapInfo.Name), ExtractFileName(gMapInfo.FileName), gMapInfo.MapName);
+  UpdateCaption(gMapInfo.Name, ExtractFileName(gMapInfo.FileName), gMapInfo.MapName);
 end;
 
 procedure TMainForm.aSelectAllExecute(Sender: TObject);
@@ -6086,6 +6677,8 @@ begin
           if gTriggers[a].TriggerType <> TRIGGER_NONE then
             SelectObject(OBJECT_TRIGGER, a, True);
   end;
+
+  RecountSelectedObjects();
 end;
 
 procedure TMainForm.tbGridOnClick(Sender: TObject);
@@ -6095,6 +6688,7 @@ begin
 end;
 
 procedure TMainForm.OnIdle(Sender: TObject; var Done: Boolean);
+  var f: AnsiString;
 begin
   // FIXME: this is a shitty hack
   if not gDataLoaded then
@@ -6110,11 +6704,20 @@ begin
     MainForm.FormResize(nil);
   end;
   Draw();
+  if StartMap <> '' then
+  begin
+    f := StartMap;
+    StartMap := '';
+    OpenMap(f, '');
+  end;
 end;
 
 procedure TMainForm.miMapPreviewClick(Sender: TObject);
 begin
-  if not PreviewMode then
+  if PreviewMode = 2 then
+    Exit;
+
+  if PreviewMode = 0 then
     begin
       Splitter2.Visible := False;
       Splitter1.Visible := False;
@@ -6137,8 +6740,8 @@ begin
       sbVertical.Visible := True;
     end;
 
-  PreviewMode := not PreviewMode;
-  (Sender as TMenuItem).Checked := PreviewMode;
+  PreviewMode := PreviewMode xor 1;
+  (Sender as TMenuItem).Checked := PreviewMode > 0;
 
   FormResize(Self);
 end;
@@ -6266,40 +6869,73 @@ begin
   PackMapForm.ShowModal();
 end;
 
-procedure TMainForm.miMapTestSettingsClick(Sender: TObject);
+type SSArray = array of String;
+
+function ParseString (Str: AnsiString): SSArray;
+  function GetStr (var Str: AnsiString): AnsiString;
+    var a, b: Integer;
+  begin
+    Result := '';
+    if Str[1] = '"' then
+      for b := 1 to Length(Str) do
+        if (b = Length(Str)) or (Str[b + 1] = '"') then
+        begin
+          Result := Copy(Str, 2, b - 1);
+          Delete(Str, 1, b + 1);
+          Str := Trim(Str);
+          Exit;
+        end;
+    for a := 1 to Length(Str) do
+      if (a = Length(Str)) or (Str[a + 1] = ' ') then
+      begin
+        Result := Copy(Str, 1, a);
+        Delete(Str, 1, a + 1);
+        Str := Trim(Str);
+        Exit;
+      end;
+  end;
 begin
-  MapTestForm.ShowModal();
+  Result := nil;
+  Str := Trim(Str);
+  while Str <> '' do
+  begin
+    SetLength(Result, Length(Result)+1);
+    Result[High(Result)] := GetStr(Str);
+  end;
 end;
 
 procedure TMainForm.miTestMapClick(Sender: TObject);
 var
-  cmd, mapWAD, mapToRun, tempWAD: String;
+  newWAD, oldWAD, tempMap, ext: String;
+  args: SSArray;
   opt: LongWord;
-  time: Integer;
+  time, i: Integer;
   proc: TProcessUTF8;
   res: Boolean;
 begin
-  mapToRun := '';
-  if OpenedMap <> '' then
-  begin
-    // Указываем текущую карту для теста:
-    g_ProcessResourceStr(OpenedMap, @mapWAD, nil, @mapToRun);
-    mapToRun := mapWAD + ':\' + mapToRun;
-    mapToRun := ExtractRelativePath(ExtractFilePath(TestD2dExe) + 'maps/', mapToRun);
-  end;
+  // Ignore while map testing in progress
+  if MapTestProcess <> nil then
+    Exit;
+
   // Сохраняем временную карту:
   time := 0;
   repeat
-    mapWAD := ExtractFilePath(TestD2dExe) + Format('maps/temp%.4d.wad', [time]);
+    newWAD := Format('%s/temp%.4d', [MapsDir, time]);
     Inc(time);
-  until not FileExists(mapWAD);
-  tempWAD := mapWAD + ':\' + TEST_MAP_NAME;
-  SaveMap(tempWAD);
-
-  tempWAD := ExtractRelativePath(ExtractFilePath(TestD2dExe) + 'maps/', tempWAD);
-// Если карта не была открыта, указываем временную в качестве текущей:
-  if mapToRun = '' then
-    mapToRun := tempWAD;
+  until not FileExists(newWAD);
+  if OpenedMap <> '' then
+  begin
+    oldWad := g_ExtractWadName(OpenedMap);
+    newWad := newWad + ExtractFileExt(oldWad);
+    if CopyFile(oldWad, newWad) = false then
+      e_WriteLog('MapTest: unable to copy [' + oldWad + '] to [' + newWad + ']', MSG_WARNING)
+  end
+  else
+  begin
+    newWad := newWad + '.wad'
+  end;
+  tempMap := newWAD + ':\' + TEST_MAP_NAME;
+  SaveMap(tempMap);
 
 // Опции игры:
   opt := 32 + 64;
@@ -6314,23 +6950,32 @@ begin
   if TestOptionsMonstersDM then
     opt := opt + 16;
 
-// Составляем командную строку:
-  cmd := '-map "' + mapToRun + '"';
-  cmd := cmd + ' -testmap "' + tempWAD + '"';
-  cmd := cmd + ' -gm ' + TestGameMode;
-  cmd := cmd + ' -limt ' + TestLimTime;
-  cmd := cmd + ' -lims ' + TestLimScore;
-  cmd := cmd + ' -opt ' + IntToStr(opt);
-
-  if TestMapOnce then
-    cmd := cmd + ' --close';
-
-  cmd := cmd + ' --debug';
-
 // Запускаем:
   proc := TProcessUTF8.Create(nil);
   proc.Executable := TestD2dExe;
-  proc.Parameters.Add(cmd);
+  {$IFDEF DARWIN}
+    // TODO: get real executable name from Info.plist
+    if LowerCase(ExtractFileExt(TestD2dExe)) = '.app' then
+      proc.Executable := TestD2dExe + DirectorySeparator + 'Contents' + DirectorySeparator + 'MacOS' + DirectorySeparator + 'Doom2DF';
+  {$ENDIF}
+  proc.Parameters.Add('-map');
+  proc.Parameters.Add(tempMap);
+  proc.Parameters.Add('-gm');
+  proc.Parameters.Add(TestGameMode);
+  proc.Parameters.Add('-limt');
+  proc.Parameters.Add(TestLimTime);
+  proc.Parameters.Add('-lims');
+  proc.Parameters.Add(TestLimScore);
+  proc.Parameters.Add('-opt');
+  proc.Parameters.Add(IntToStr(opt));
+  proc.Parameters.Add('--debug');
+  if TestMapOnce then
+    proc.Parameters.Add('--close');
+
+  args := ParseString(TestD2DArgs);
+  for i := 0 to High(args) do
+    proc.Parameters.Add(args[i]);
+
   res := True;
   try
     proc.Execute();
@@ -6339,31 +6984,28 @@ begin
   end;
   if res then
   begin
-    Application.Minimize();
-    proc.WaitOnExit();
-  end;
-  if (not res) or (proc.ExitCode < 0) then
+    tbTestMap.Enabled := False;
+    MapTestFile := newWAD;
+    MapTestProcess := proc;
+  end
+  else
   begin
-    MessageBox(0, 'FIXME',
-               PChar(_lc[I_MSG_EXEC_ERROR]),
-               MB_OK or MB_ICONERROR);
+    Application.MessageBox(PChar(MsgMsgExecError), 'FIXME', MB_OK or MB_ICONERROR);
+    SysUtils.DeleteFile(newWAD);
+    proc.Free();
   end;
-  proc.Free();
-
-  SysUtils.DeleteFile(mapWAD);
-  Application.Restore();
 end;
 
 procedure TMainForm.sbVerticalScroll(Sender: TObject;
   ScrollCode: TScrollCode; var ScrollPos: Integer);
 begin
-  MapOffset.Y := -Normalize16(sbVertical.Position);
+  MapOffset.Y := -sbVertical.Position;
 end;
 
 procedure TMainForm.sbHorizontalScroll(Sender: TObject;
   ScrollCode: TScrollCode; var ScrollPos: Integer);
 begin
-  MapOffset.X := -Normalize16(sbHorizontal.Position);
+  MapOffset.X := -sbHorizontal.Position;
 end;
 
 procedure TMainForm.miOpenWadMapClick(Sender: TObject);
@@ -6404,6 +7046,8 @@ begin
     for a := 0 to High(gTriggers) do
       if gTriggers[a].TriggerType <> TRIGGER_NONE then
         SelectObject(OBJECT_TRIGGER, a, True);
+
+  RecountSelectedObjects();
 end;
 
 procedure TMainForm.Splitter1CanResize(Sender: TObject;
@@ -6428,8 +7072,7 @@ begin
   EditingProperties := False;
 end;
 
-procedure TMainForm.FormKeyUp(Sender: TObject; var Key: Word;
-  Shift: TShiftState);
+procedure TMainForm.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
 begin
 // Объекты передвигались:
   if MainForm.ActiveControl = RenderPanel then
@@ -6441,6 +7084,13 @@ begin
        (Key = Ord('V')) then
       FillProperty();
   end;
+// Быстрое превью карты:
+  if Key = Ord('E') then
+  begin
+    if PreviewMode = 2 then
+      PreviewMode := 0;
+  end;
+  RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
 end;
 
 end.