DEADSOFTWARE

allow to open any wad type from recent files
[d2df-editor.git] / src / editor / f_main.pas
index 292bcdafb9e7f3faf9e276b0e03ab08803bd2901..ca14ebe46ef777a30433bdacd9c2f93eaf09b5fb 100644 (file)
@@ -8,7 +8,7 @@ uses
   LCLIntf, LCLType, SysUtils, Variants, Classes, Graphics,
   Controls, Forms, Dialogs, StdCtrls, Buttons,
   ComCtrls, ValEdit, Types, Menus, ExtCtrls,
-  CheckLst, Grids, OpenGLContext, utils, UTF8Process;
+  CheckLst, Grids, OpenGLContext, Utils, UTF8Process;
 
 type
 
@@ -26,6 +26,7 @@ type
     miSaveMapAs: TMenuItem;
     miOpenWadMap: TMenuItem;
     miLine1: TMenuItem;
+    miReopenMap: TMenuItem;
     miSaveMiniMap: TMenuItem;
     miDeleteMap: TMenuItem;
     miPackMap: TMenuItem;
@@ -210,6 +211,7 @@ type
     procedure lbTextureListClick(Sender: TObject);
     procedure lbTextureListDrawItem(Control: TWinControl; Index: Integer;
       ARect: TRect; State: TOwnerDrawState);
+    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);
@@ -264,6 +266,8 @@ type
   public
     procedure RefreshRecentMenu();
     procedure OpenMapFile(FileName: String);
+    function RenderMousePos(): TPoint;
+    procedure RecountSelectedObjects();
   end;
 
 const
@@ -288,8 +292,8 @@ var
 
   DotColor: TColor;
   DotEnable: Boolean;
-  DotStep: Byte;
-  DotStepOne, DotStepTwo: Byte;
+  DotStep: Word;
+  DotStepOne, DotStepTwo: Word;
   DotSize: Byte;
   DrawTexturePanel: Boolean;
   DrawPanelSize: Boolean;
@@ -309,11 +313,13 @@ 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);
+  ContourEnabled: Array [LAYER_BACK..LAYER_TRIGGERS] of Boolean =
+    (False, False, False, False, False, False, False, False, False);
   PreviewMode: Byte = 0;
   gLanguage: String;
 
@@ -331,11 +337,11 @@ uses
   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,
-  MAPREADER, f_selectmap, f_savemap, WADEDITOR, WADSTRUCT, MAPDEF,
+  f_activationtype, f_keys, wadreader,
+  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;
+  g_language, f_selectlang, ClipBrd, g_resources;
 
 const
   UNDO_DELETE_PANEL   = 1;
@@ -385,7 +391,7 @@ const
   SELECTFLAG_SHOTPANEL  = 7;
   SELECTFLAG_SELECTED   = 8;
 
-  RECENT_FILES_MENU_START = 11;
+  RECENT_FILES_MENU_START = 12;
 
   CLIPBOARD_SIG         = 'DF:ED';
 
@@ -438,7 +444,6 @@ var
   MouseRDown: Boolean;
   MouseLDownPos: Types.TPoint;
   MouseRDownPos: Types.TPoint;
-  WASDOffset: TPoint;
 
   SelectFlag: Byte = SELECTFLAG_NONE;
   MouseAction: Byte = MOUSEACTION_NONE;
@@ -689,10 +694,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;
@@ -701,17 +706,17 @@ 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.Min then
+      MapOffset.X := MainForm.sbHorizontal.Min;
+    if MapOffset.Y < MainForm.sbVertical.Min then
+      MapOffset.Y := MainForm.sbVertical.Min;
     if MapOffset.X > MainForm.sbHorizontal.Max then
       MapOffset.X := MainForm.sbHorizontal.Max;
     if MapOffset.Y > MainForm.sbVertical.Max then
       MapOffset.Y := 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;
@@ -736,6 +741,7 @@ var
   str: String;
 begin
   MainForm.vleObjectProperty.Strings.Clear();
+  MainForm.RecountSelectedObjects();
 
 // Отображаем свойства если выделен только один объект:
   if SelectedObjectCount() <> 1 then
@@ -1404,6 +1410,20 @@ begin
                   EditStyle := esSimple;
                   MaxLength := 5;
                 end;
+                case Data.DamageKind of
+                  3: str := _lc[I_PROP_TR_DAMAGE_KIND_3];
+                  4: str := _lc[I_PROP_TR_DAMAGE_KIND_4];
+                  5: str := _lc[I_PROP_TR_DAMAGE_KIND_5];
+                  6: str := _lc[I_PROP_TR_DAMAGE_KIND_6];
+                  7: str := _lc[I_PROP_TR_DAMAGE_KIND_7];
+                  8: str := _lc[I_PROP_TR_DAMAGE_KIND_8];
+                  else str := _lc[I_PROP_TR_DAMAGE_KIND_0];
+                end;
+                with ItemProps[InsertRow(_lc[I_PROP_TR_DAMAGE_KIND], str, True)] do
+                begin
+                  EditStyle := esPickList;
+                  ReadOnly := True;
+                end;
               end;
 
             TRIGGER_HEALTH:
@@ -1477,7 +1497,7 @@ begin
                   3: str := _lc[I_PROP_TR_SHOT_AIM_3];
                   else str := _lc[I_PROP_TR_SHOT_AIM_0];
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_AIM], str, True)-1] do
+                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_AIM], str, True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1771,6 +1791,7 @@ begin
   RemoveSelectFromObjects();
 
   MainForm.miUndo.Enabled := UndoBuffer <> nil;
+  MainForm.RecountSelectedObjects();
 end;
 
 procedure Undo_Add(ObjectType: Byte; ID: DWORD; Group: Boolean = False);
@@ -2562,45 +2583,27 @@ end;
 
 procedure TMainForm.aRecentFileExecute(Sender: TObject);
 var
-  n, pw: Integer;
-  s, fn: String;
-  b: Boolean;
+  n: Integer;
+  fn, s: String;
 begin
   s := LowerCase((Sender as TMenuItem).Caption);
   Delete(s, Pos('&', s), 1);
   s := Trim(Copy(s, 1, 2));
   n := StrToIntDef(s, 0) - 1;
-
-  if (n < 0) or (n >= RecentFiles.Count) then
-    Exit;
-
-  s := RecentFiles[n];
-  pw := Pos('.wad:\', LowerCase(s));
-  b := False;
-  
-  if pw > 0 then
-    begin // Map name included
-      fn := Copy(s, 1, pw + 3);
-      Delete(s, 1, pw + 5);
-      if (FileExists(fn)) then
-      begin
-        OpenMap(fn, s);
-        b := True;
-      end;
+  if (n >= 0) and (n <= RecentFiles.Count) then
+  begin
+    fn := g_ExtractWadName(RecentFiles[n]);
+    if FileExists(fn) then
+    begin
+      s := g_ExtractFilePathName(RecentFiles[n]);
+      OpenMap(fn, s)
     end
-  else // Only wad name
-    if (FileExists(s)) then
+    else if MessageBox(0, PChar(_lc[I_MSG_DEL_RECENT_PROMT]), PChar(_lc[I_MSG_DEL_RECENT]), MB_ICONQUESTION or MB_YESNO) = idYes then
     begin
-      OpenMap(s, '');
-      b := True;
-    end;
-
-  if (not b) and (MessageBox(0, PChar(_lc[I_MSG_DEL_RECENT_PROMT]),
-    PChar(_lc[I_MSG_DEL_RECENT]), MB_ICONQUESTION or MB_YESNO) = idYes) then
-  begin
-    RecentFiles.Delete(n);
-    RefreshRecentMenu();
-  end;
+      RecentFiles.Delete(n);
+      RefreshRecentMenu();
+    end
+  end
 end;
 
 procedure TMainForm.aEditorOptionsExecute(Sender: TObject);
@@ -2613,23 +2616,15 @@ var
   cwdt, chgt: Byte;
   spc: ShortInt;
   ID: DWORD;
-  wad: TWADEditor_1;
   cfgdata: Pointer;
   cfglen: Integer;
   config: TConfig;
 begin
-  cfgdata := nil;
-  cfglen := 0;
   ID := 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
+  g_ReadResource(EditorDir + 'data/game.wad', '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', EditorDir + 'data/game.wad:FONTS\' + texture) then
       e_WriteLog('ERROR ERROR ERROR', MSG_WARNING);
 
     config := TConfig.CreateMem(cfgdata, cfglen);
@@ -2638,14 +2633,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);
@@ -2723,6 +2719,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
@@ -2737,6 +2735,9 @@ begin
   s := config.ReadStr('Editor', 'Language', '');
   gLanguage := s;
 
+  Compress := config.ReadBool('Editor', 'Compress', True);
+  Backup := config.ReadBool('Editor', 'Backup', True);
+
   RecentCount := config.ReadInt('Editor', 'RecentCount', 5);
   if RecentCount > 10 then
     RecentCount := 10;
@@ -2834,12 +2835,22 @@ begin
     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;
 
 // Превью текстуры:
@@ -2945,8 +2956,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;
 
 // Прямоугольник выделения:
@@ -2955,7 +2967,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);
@@ -2965,7 +2978,7 @@ begin
       begin // Чертим новый
         PrintBlack(MousePos.X+2, MousePos.Y+2, Format(_glc[I_HINT_WIDTH],
                           [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(_glc[I_HINT_HEIGHT],
                           [Abs(MousePos.Y-MouseLDownPos.Y)]), gEditorFont);
       end
     else // Растягиваем существующий
@@ -2984,7 +2997,7 @@ begin
 
         PrintBlack(MousePos.X+2, MousePos.Y+2, Format(_glc[I_HINT_WIDTH], [Width]),
                           gEditorFont);
-        PrintBlack(MousePos.X+2, MousePos.Y+14, Format(_glc[I_HINT_HEIGHT], [Height]),
+        PrintBlack(MousePos.X+2, MousePos.Y+16, Format(_glc[I_HINT_HEIGHT], [Height]),
                           gEditorFont);
       end;
   end;
@@ -3088,18 +3101,13 @@ 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 := -Normalize16(sbHorizontal.Position);
-  MapOffset.Y := -Normalize16(sbVertical.Position);
+  MapOffset.X := -sbHorizontal.Position;
+  MapOffset.Y := -sbVertical.Position;
 end;
 
 procedure SelectNextObject(X, Y: Integer; ObjectType: Byte; ID: DWORD);
@@ -3604,10 +3612,24 @@ 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;
@@ -3616,14 +3638,18 @@ begin
 
   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:
@@ -3653,8 +3679,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
@@ -3696,8 +3739,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;
@@ -3942,14 +3995,16 @@ 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;
@@ -3960,14 +4015,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(_lc[I_CAP_STAT_SELECTED], [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
@@ -4016,8 +4087,8 @@ 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;
 
 // Изменение размера закончилось - ставим обычный курсор:
@@ -4042,10 +4113,8 @@ begin
       if MouseAction = MOUSEACTION_MOVEOBJ then
         begin
           MoveSelectedObjects(ssShift in Shift, ssCtrl in Shift,
-                              MousePos.X-LastMovePoint.X+WASDOffset.X,
-                              MousePos.Y-LastMovePoint.Y+WASDOffset.Y);
-          WASDOffset.X := 0;
-          WASDOffset.Y := 0;
+                              MousePos.X-LastMovePoint.X,
+                              MousePos.Y-LastMovePoint.Y);
         end
       else
       // Меняем размер выделенного объекта:
@@ -4054,10 +4123,8 @@ begin
           if (SelectedObjectCount = 1) and
              (SelectedObjects[GetFirstSelected].Live) then
           begin
-            dWidth := MousePos.X-LastMovePoint.X+WASDOffset.X;
-            dHeight := MousePos.Y-LastMovePoint.Y+WASDOffset.Y;
-            WASDOffset.X := 0;
-            WASDOffset.Y := 0;
+            dWidth := MousePos.X-LastMovePoint.X;
+            dHeight := MousePos.Y-LastMovePoint.Y;
 
             case ResizeType of
               RESIZETYPE_VERTICAL: dWidth := 0;
@@ -4069,11 +4136,10 @@ 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;
@@ -4088,10 +4154,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
@@ -4156,6 +4241,8 @@ begin
   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
@@ -4208,34 +4295,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 // Поворот монстров и областей:
@@ -4288,24 +4393,42 @@ begin
       begin
         if Key = Ord('W') then
         begin
-          if (MouseLDown or MouseRDown) and (Position >= DotStep) then
+          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
-            Dec(WASDOffset.Y, DotStep);
-            RenderPanelMouseMove(Sender, Shift, LastMovePoint.X, LastMovePoint.Y);
+            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;
-          Position := IfThen(Position > DotStep, Position-DotStep, 0);
-          MapOffset.Y := -Round(Position/16) * 16;
         end;
 
         if Key = Ord('S') then
         begin
-          if (MouseLDown or MouseRDown) and (Position+DotStep <= Max) then
+          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
-            Inc(WASDOffset.Y, DotStep);
-            RenderPanelMouseMove(Sender, Shift, LastMovePoint.X, LastMovePoint.Y);
+            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;
-          Position := IfThen(Position+DotStep < Max, Position+DotStep, Max);
-          MapOffset.Y := -Round(Position/16) * 16;
         end;
       end;
 
@@ -4314,24 +4437,42 @@ begin
       begin
         if Key = Ord('A') then
         begin
-          if (MouseLDown or MouseRDown) and (Position >= DotStep) then
+          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
-            Dec(WASDOffset.X, DotStep);
-            RenderPanelMouseMove(Sender, Shift, LastMovePoint.X, LastMovePoint.Y);
+            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;
-          Position := IfThen(Position > DotStep, Position-DotStep, 0);
-          MapOffset.X := -Round(Position/16) * 16;
         end;
 
         if Key = Ord('D') then
         begin
-          if (MouseLDown or MouseRDown) and (Position+DotStep <= Max) then
+          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
-            Inc(WASDOffset.X, DotStep);
-            RenderPanelMouseMove(Sender, Shift, LastMovePoint.X, LastMovePoint.Y);
+            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;
-          Position := IfThen(Position+DotStep < Max, Position+DotStep, Max);
-          MapOffset.X := -Round(Position/16) * 16;
         end;
       end;
     end
@@ -4343,6 +4484,7 @@ begin
         if Key = Ord('V') then
           aPasteObjectExecute(Sender);
       end;
+      RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
     end;
   end;
 
@@ -4549,6 +4691,21 @@ begin
   end;
 end;
 
+procedure TMainForm.miReopenMapClick(Sender: TObject);
+var
+  FileName, Resource: String;
+begin
+  if OpenedMap = '' then
+    Exit;
+
+  if MessageBox(0, PChar(_lc[I_MSG_REOPEN_MAP_PROMT]),
+  PChar(_lc[I_MENU_FILE_REOPEN]), 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
@@ -4625,6 +4782,16 @@ begin
         Values.Add(_lc[I_PROP_TR_SHOT_AIM_2]);
         Values.Add(_lc[I_PROP_TR_SHOT_AIM_3]);
       end
+    else if KeyName = _lc[I_PROP_TR_DAMAGE_KIND] then
+      begin
+        Values.Add(_lc[I_PROP_TR_DAMAGE_KIND_0]);
+        Values.Add(_lc[I_PROP_TR_DAMAGE_KIND_3]);
+        Values.Add(_lc[I_PROP_TR_DAMAGE_KIND_4]);
+        Values.Add(_lc[I_PROP_TR_DAMAGE_KIND_5]);
+        Values.Add(_lc[I_PROP_TR_DAMAGE_KIND_6]);
+        Values.Add(_lc[I_PROP_TR_DAMAGE_KIND_7]);
+        Values.Add(_lc[I_PROP_TR_DAMAGE_KIND_8]);
+      end
     else if (KeyName = _lc[I_PROP_PANEL_BLEND]) or
             (KeyName = _lc[I_PROP_DM_ONLY]) or
             (KeyName = _lc[I_PROP_ITEM_FALLS]) or
@@ -5028,6 +5195,21 @@ begin
                   StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_DAMAGE_VALUE]], 0), 0), 65535);
                 Data.DamageInterval := Min(Max(
                   StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_INTERVAL]], 0), 0), 65535);
+                s := vleObjectProperty.Values[_lc[I_PROP_TR_DAMAGE_KIND]];
+                if s = _lc[I_PROP_TR_DAMAGE_KIND_3] then
+                  Data.DamageKind := 3
+                else if s = _lc[I_PROP_TR_DAMAGE_KIND_4] then
+                  Data.DamageKind := 4
+                else if s = _lc[I_PROP_TR_DAMAGE_KIND_5] then
+                  Data.DamageKind := 5
+                else if s = _lc[I_PROP_TR_DAMAGE_KIND_6] then
+                  Data.DamageKind := 6
+                else if s = _lc[I_PROP_TR_DAMAGE_KIND_7] then
+                  Data.DamageKind := 7
+                else if s = _lc[I_PROP_TR_DAMAGE_KIND_8] then
+                  Data.DamageKind := 8
+                else
+                  Data.DamageKind := 0;
               end;
 
             TRIGGER_HEALTH:
@@ -6040,69 +6222,48 @@ 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];
 
   if not OpenDialog.Execute() then
     Exit;
 
-  WAD := TWADEditor_1.Create();
-
-  if not WAD.ReadFile(OpenDialog.FileName) then
-  begin
-    WAD.Free();
-    Exit;
-  end;
-
-  WAD.CreateImage();
-
-  MapList := WAD.GetResourcesList('');
-
+  FileName := OpenDialog.FileName;
   SelectMapForm.Caption := _lc[I_CAP_REMOVE];
   SelectMapForm.lbMapList.Items.Clear();
+  SelectMapForm.GetMaps(FileName);
 
-  if MapList <> nil then
-    for a := 0 to High(MapList) do
-      SelectMapForm.lbMapList.Items.Add(win2utf(MapList[a]));
+  if SelectMapForm.ShowModal() <> mrOK then
+    Exit;
 
-  if (SelectMapForm.ShowModal() = mrOK) then
-  begin
-    str := SelectMapForm.lbMapList.Items[SelectMapForm.lbMapList.ItemIndex];
-    MapName := '';
-    Move(str[1], MapName[0], 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;
+  MapName := SelectMapForm.lbMapList.Items[SelectMapForm.lbMapList.ItemIndex];
+  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('', utf2win(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);
+  g_DeleteResource(FileName, '', MapName, res);
+  if res <> 0 then
+  begin
+    MessageBox(0, 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);
+  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
+  );
 
   // Удалили текущую карту - сохранять по старому ее нельзя:
-    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;
@@ -6317,6 +6478,8 @@ begin
           if gTriggers[a].TriggerType <> TRIGGER_NONE then
             SelectObject(OBJECT_TRIGGER, a, True);
   end;
+
+  RecountSelectedObjects();
 end;
 
 procedure TMainForm.tbGridOnClick(Sender: TObject);
@@ -6505,11 +6668,47 @@ begin
   MapTestForm.ShowModal();
 end;
 
+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
+  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;
+  mapWAD, mapToRun, tempWAD: String;
+  args: SSArray;
   opt: LongWord;
-  time: Integer;
+  time, i: Integer;
   proc: TProcessUTF8;
   res: Boolean;
 begin
@@ -6548,23 +6747,29 @@ 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);
+  proc.Parameters.Add('-map');
+  proc.Parameters.Add(mapToRun);
+  proc.Parameters.Add('-testmap');
+  proc.Parameters.Add(tempWAD);
+  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();
@@ -6591,13 +6796,13 @@ 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);
@@ -6638,6 +6843,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;
@@ -6662,8 +6869,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
@@ -6681,6 +6887,7 @@ begin
     if PreviewMode = 2 then
       PreviewMode := 0;
   end;
+  RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
 end;
 
 end.