DEADSOFTWARE

the game is able to read text maps now (WARNING! the feature is still experimental!)
[d2df-sdl.git] / src / game / g_map.pas
index d1d824a9396883a9f2a5e3f2038a6bd7cf5dff4c..4bb340d1026a97f86893f6843e0f0f3dfb93a61a 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *)
 {$INCLUDE ../shared/a_modes.inc}
+{$DEFINE MAP_DEBUG_ENABLED_FLAG}
 unit g_map;
 
 interface
 
 uses
   e_graphics, g_basic, MAPSTRUCT, g_textures, Classes,
-  g_phys, wadreader, BinEditor, g_panel, g_grid, z_aabbtree, md5, binheap, xprofiler;
+  g_phys, wadreader, BinEditor, g_panel, g_grid, md5, binheap, xprofiler;
 
 type
   TMapInfo = record
@@ -91,17 +92,23 @@ procedure g_Map_LoadState(Var Mem: TBinMemoryReader);
 
 procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer);
 
-// returns wall index in `gWalls` or -1
-function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): Boolean;
+// returns panel or nil
+// sets `ex` and `ey` to `x1` and `y1` when no hit was detected
+function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
+
+// returns panel or nil
+// sets `ex` and `ey` to `x1` and `y1` when no hit was detected
+function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
 
 type
   TForEachPanelCB = function (pan: TPanel): Boolean; // return `true` to stop
 
 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
+function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
 
 // trace liquid, stepping by `dx` and `dy`
 // return last seen liquid coords, and `false` if we're started outside of the liquid
-function g_Map_TraceLiquid (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
+function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
 
 
 procedure g_Map_ProfilersBegin ();
@@ -157,6 +164,8 @@ const
   GridTagLift = 1 shl 8;
   GridTagBlockMon = 1 shl 9;
 
+  GridDrawableMask = (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore);
+
 
 var
   gWalls: TPanelArray;
@@ -178,15 +187,20 @@ var
 
   gdbg_map_use_accel_render: Boolean = true;
   gdbg_map_use_accel_coldet: Boolean = true;
-  //gdbg_map_use_tree_draw: Boolean = false;
-  //gdbg_map_use_tree_coldet: Boolean = false;
-  //gdbg_map_dump_coldet_tree_queries: Boolean = false;
   profMapCollision: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY!
   gDrawPanelList: TBinaryHeapObj = nil; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()`
 
+
 function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX
 
 
+type
+  TPanelGrid = specialize TBodyGridBase<TPanel>;
+
+var
+  mapGrid: TPanelGrid = nil; // DO NOT USE! public for debugging only!
+
+
 implementation
 
 uses
@@ -194,7 +208,7 @@ uses
   GL, GLExt, g_weapons, g_game, g_sound, e_sound, CONFIG,
   g_options, MAPREADER, g_triggers, g_player, MAPDEF,
   Math, g_monsters, g_saveload, g_language, g_netmsg,
-  utils, sfs,
+  utils, sfs, xparser, xdynrec, xstreams,
   ImagingTypes, Imaging, ImagingUtility,
   ImagingGif, ImagingNetworkGraphics;
 
@@ -204,28 +218,45 @@ const
   FLAG_SIGNATURE = $47414C46; // 'FLAG'
 
 
-type
-  TPanelGrid = specialize TBodyGridBase<TPanel>;
+{$IF DEFINED(D2D_NEW_MAP_READER)}
+var
+  dfmapdef: TDynMapDef = nil;
 
-  {
-  TDynAABBTreePanelBase = specialize TDynAABBTreeBase<TPanel>;
 
-  TDynAABBTreeMap = class(TDynAABBTreePanelBase)
-    function getFleshAABB (out aabb: AABB2D; pan: TPanel; tag: Integer): Boolean; override;
+procedure loadMapDefinition ();
+var
+  pr: TTextParser = nil;
+  st: TStream = nil;
+  WAD: TWADFile = nil;
+begin
+  if (dfmapdef <> nil) then exit;
+  try
+    e_LogWritefln('parsing "mapdef.txt"...', []);
+    st := openDiskFileRO(DataDir+'mapdef.txt');
+  except
+    st := nil;
+    e_LogWritefln('local "%smapdef.txt" not found', [DataDir]);
+  end;
+  if (st = nil) then
+  begin
+    WAD := TWADFile.Create();
+    if not WAD.ReadFile(GameWAD) then raise Exception.Create('cannot load "game.wad"');
+    st := WAD.openFileStream('mapdef.txt');
   end;
-  {
 
-{
-function TDynAABBTreeMap.getFleshAABB (out aabb: AABB2D; pan: TPanel; tag: Integer): Boolean;
-begin
-  result := false;
-  if (pan = nil) then begin aabb := AABB2D.Create(0, 0, 0, 0); exit; end;
-  aabb := AABB2D.Create(pan.X, pan.Y, pan.X+pan.Width-1, pan.Y+pan.Height-1);
-  if (pan.Width < 1) or (pan.Height < 1) then exit;
-  if not aabb.valid then raise Exception.Create('wutafuuuuuuu?!');
-  result := true;
+  if (st = nil) then raise Exception.Create('cannot open "mapdef.txt"');
+  pr := TFileTextParser.Create(st);
+
+  try
+    dfmapdef := TDynMapDef.Create(pr);
+  except on e: Exception do
+    raise Exception.Create(Format('ERROR in "mapdef.txt" at (%s,%s): %s', [pr.line, pr.col, e.message]));
+  end;
+
+  st.Free();
+  WAD.Free();
 end;
-}
+{$ENDIF}
 
 
 function panelTypeToTag (panelType: Word): Integer;
@@ -275,8 +306,6 @@ var
   RespawnPoints: Array of TRespawnPoint;
   FlagPoints:    Array [FLAG_RED..FLAG_BLUE] of PFlagPoint;
   //DOMFlagPoints: Array of TFlagPoint;
-  mapGrid: TPanelGrid = nil;
-  //mapTree: TDynAABBTreeMap = nil;
 
 
 procedure g_Map_ProfilersBegin ();
@@ -300,123 +329,39 @@ end;
 
 
 // wall index in `gWalls` or -1
-(*
-function g_Map_traceToNearestWallOld (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): Integer;
-
-  function sqchecker (pan: TPanel; var ray: Ray2D): Single;
-  var
-    aabb: AABB2D;
-    tmin: Single;
-  begin
-    result := -666.0; // invalid
-    if not pan.Enabled then exit;
-    aabb := AABB2D.CreateWH(pan.X, pan.Y, pan.Width, pan.Height);
-    if not aabb.valid then exit;
-    if aabb.intersects(ray, @tmin) then
-    begin
-      //if (tmin*tmin > maxDistSq) then exit;
-      if (tmin >= 0.0) then
-      begin
-        //e_WriteLog(Format('sqchecker(%d,%d,%d,%d): panel #%d (%d,%d)-(%d,%d); tmin=%f', [x0, y0, x1, y1, pan.arrIdx, pan.X, pan.Y, pan.Width, pan.Height, tmin]), MSG_NOTIFY);
-        //if (tmin < 0.0) then tmin := 0.0;
-        result := tmin;
-      end;
-    end;
-  end;
-
+function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
 var
-  qr: TDynAABBTreeMap.TSegmentQueryResult;
-  ray: Ray2D;
-  hxf, hyf: Single;
-  hx, hy: Integer;
-  maxDistSq: Single;
+  ex, ey: Integer;
 begin
-  result := -1;
-  if (mapTree = nil) then exit;
-  if mapTree.segmentQuery(qr, x0, y0, x1, y1, sqchecker, (GridTagWall or GridTagDoor)) then
+  result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, nil, (GridTagWall or GridTagDoor));
+  if (result <> nil) then
   begin
-    maxDistSq := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0);
-    if (qr.flesh <> nil) and (qr.time*qr.time <= maxDistSq) then
-    begin
-      result := qr.flesh.arrIdx;
-      if (hitx <> nil) or (hity <> nil) then
-      begin
-        ray := Ray2D.Create(x0, y0, x1, y1);
-        hxf := ray.origX+ray.dirX*qr.time;
-        hyf := ray.origY+ray.dirY*qr.time;
-        while true do
-        begin
-          hx := trunc(hxf);
-          hy := trunc(hyf);
-          if (hx >= qr.flesh.X) and (hy >= qr.flesh.Y) and (hx < qr.flesh.X+qr.flesh.Width) and (hy < qr.flesh.Y+qr.flesh.Height) then
-          begin
-            // go back a little
-            hxf -= ray.dirX;
-            hyf -= ray.dirY;
-          end
-          else
-          begin
-            break;
-          end;
-        end;
-        if (hitx <> nil) then hitx^ := hx;
-        if (hity <> nil) then hity^ := hy;
-      end;
-    end;
+    if (hitx <> nil) then hitx^ := ex;
+    if (hity <> nil) then hity^ := ey;
+  end
+  else
+  begin
+    if (hitx <> nil) then hitx^ := x1;
+    if (hity <> nil) then hity^ := y1;
   end;
 end;
-*)
-
 
-// wall index in `gWalls` or -1
-function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): Boolean;
-(*
-var
-  lastX, lastY, lastDistSq: Integer;
-  wasHit: Boolean = false;
-
-  // pan=nil: before processing new tile
-  function sqchecker (pan: TPanel; tag: Integer; x, y, prevx, prevy: Integer): Boolean;
-  var
-    distSq: Integer;
-  begin
-    if (pan = nil) then
-    begin
-      // stop if something was hit at the previous tile
-      result := wasHit;
-    end
-    else
-    begin
-      result := false;
-      if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
-      begin
-        if not pan.Enabled then exit;
-      end;
-      distSq := (prevx-x0)*(prevx-x0)+(prevy-y0)*(prevy-y0);
-      if (distSq < lastDistSq) then
-      begin
-        wasHit := true;
-        lastDistSq := distSq;
-        lastX := prevx;
-        lastY := prevy;
-      end;
-    end;
-  end;
-*)
+// returns panel or nil
+function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
 var
   ex, ey: Integer;
 begin
-  (*
-  result := false;
-  if (mapGrid = nil) then exit;
-  lastDistSq := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)+1;
-  lastX := 0;
-  lastY := 0;
-  result := mapGrid.traceRay(x0, y0, x1, y1, sqchecker, (GridTagWall or GridTagDoor));
-  if (hitx <> nil) then hitx^ := lastX;
-  if (hity <> nil) then hity^ := lastY;
-  *)
-  result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, nil, (GridTagWall or GridTagDoor));
+  result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, nil, tag);
+  if (result <> nil) then
+  begin
+    if (hitx <> nil) then hitx^ := ex;
+    if (hity <> nil) then hity^ := ey;
+  end
+  else
+  begin
+    if (hitx <> nil) then hitx^ := x1;
+    if (hity <> nil) then hity^ := y1;
+  end;
 end;
 
 
@@ -463,16 +408,24 @@ begin
   if ((tagmask and GridTagLift) <> 0) then
   begin
     // slow
-    result := mapGrid.forEachAtPoint(x, y, checker, tagmask);
+    result := (mapGrid.forEachAtPoint(x, y, checker, tagmask) <> nil);
   end
   else
   begin
     // fast
-    result := mapGrid.forEachAtPoint(x, y, nil, tagmask);
+    result := (mapGrid.forEachAtPoint(x, y, nil, tagmask) <> nil);
   end;
 end;
 
 
+function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
+begin
+  result := nil;
+  if (tagmask = 0) then exit;
+  result := mapGrid.forEachAtPoint(x, y, nil, tagmask);
+end;
+
+
 function g_Map_IsSpecialTexture(Texture: String): Boolean;
 begin
   Result := (Texture = TEXTURE_NAME_WATER) or
@@ -1034,7 +987,7 @@ end;
 procedure CreateArea(Area: TAreaRec_1);
 var
   a: Integer;
-  id: DWORD;
+  id: DWORD = 0;
 begin
   case Area.AreaType of
     AREA_DMPOINT, AREA_PLAYERPOINT1, AREA_PLAYERPOINT2,
@@ -1219,6 +1172,7 @@ begin
   addResToExternalResList(mapHeader.SkyName);
 end;
 
+
 procedure mapCreateGrid ();
 var
   mapX0: Integer = $3fffffff;
@@ -1243,28 +1197,56 @@ var
     end;
   end;
 
-  procedure addPanelsToGrid (constref panels: TPanelArray; tag: Integer);
+  procedure addPanelsToGrid (constref panels: TPanelArray);
   var
     idx: Integer;
     pan: TPanel;
+    newtag: Integer;
   begin
-    tag := panelTypeToTag(tag);
-    for idx := High(panels) downto 0 do
+    //tag := panelTypeToTag(tag);
+    for idx := 0 to High(panels) do
     begin
       pan := panels[idx];
-      pan.tag := tag;
       if not pan.visvalid then continue;
-      pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, tag);
+      if (pan.proxyId <> -1) then
+      begin
+        {$IF DEFINED(D2F_DEBUG)}
+        e_WriteLog(Format('DUPLICATE wall #%d(%d) enabled (%d); type:%08x', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.PanelType]), MSG_NOTIFY);
+        {$ENDIF}
+        continue;
+      end;
+      case pan.PanelType of
+        PANEL_WALL: newtag := GridTagWall;
+        PANEL_OPENDOOR, PANEL_CLOSEDOOR: newtag := GridTagDoor;
+        PANEL_BACK: newtag := GridTagBack;
+        PANEL_FORE: newtag := GridTagFore;
+        PANEL_WATER: newtag := GridTagWater;
+        PANEL_ACID1: newtag := GridTagAcid1;
+        PANEL_ACID2: newtag := GridTagAcid2;
+        PANEL_STEP: newtag := GridTagStep;
+        PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT: newtag := GridTagLift;
+        PANEL_BLOCKMON: newtag := GridTagBlockMon;
+        else continue; // oops
+      end;
+      pan.tag := newtag;
+
+      pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, newtag);
+      // "enabled" flag has meaning only for doors and walls (engine assumes it); but meh...
       mapGrid.proxyEnabled[pan.proxyId] := pan.Enabled;
-      //mapTree.insertObject(pan, tag, true); // as static object
+      {$IFDEF MAP_DEBUG_ENABLED_FLAG}
+      {
+      if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
+      begin
+        e_WriteLog(Format('INSERTED wall #%d(%d) enabled (%d)', [Integer(idx), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId])]), MSG_NOTIFY);
+      end;
+      }
+      {$ENDIF}
     end;
   end;
 
 begin
   mapGrid.Free();
   mapGrid := nil;
-  //mapTree.Free();
-  //mapTree := nil;
 
   calcBoundingBox(gWalls);
   calcBoundingBox(gRenderBackgrounds);
@@ -1276,28 +1258,33 @@ begin
   calcBoundingBox(gLifts);
   calcBoundingBox(gBlockMon);
 
-  e_WriteLog(Format('map dimensions: (%d,%d)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1]), MSG_WARNING);
+  e_LogWritefln('map dimensions: (%d,%d)-(%d,%d); editor size:(0,0)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1, gMapInfo.Width, gMapInfo.Height]);
+
+  if (mapX0 > 0) then mapX0 := 0;
+  if (mapY0 > 0) then mapY0 := 0;
+
+  if (mapX1 < gMapInfo.Width-1) then mapX1 := gMapInfo.Width-1;
+  if (mapY1 < gMapInfo.Height-1) then mapY1 := gMapInfo.Height-1;
 
-  mapGrid := TPanelGrid.Create(mapX0-512, mapY0-512, mapX1-mapX0+1+512*2, mapY1-mapY0+1+512*2);
-  //mapTree := TDynAABBTreeMap.Create();
+  mapGrid := TPanelGrid.Create(mapX0-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
+  //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
 
-  addPanelsToGrid(gWalls, PANEL_WALL);
-  addPanelsToGrid(gWalls, PANEL_CLOSEDOOR);
-  addPanelsToGrid(gWalls, PANEL_OPENDOOR);
-  addPanelsToGrid(gRenderBackgrounds, PANEL_BACK);
-  addPanelsToGrid(gRenderForegrounds, PANEL_FORE);
-  addPanelsToGrid(gWater, PANEL_WATER);
-  addPanelsToGrid(gAcid1, PANEL_ACID1);
-  addPanelsToGrid(gAcid2, PANEL_ACID2);
-  addPanelsToGrid(gSteps, PANEL_STEP);
-  addPanelsToGrid(gLifts, PANEL_LIFTUP); // it doesn't matter which LIFT type is used here
-  addPanelsToGrid(gBlockMon, PANEL_BLOCKMON);
+  addPanelsToGrid(gWalls);
+  addPanelsToGrid(gRenderBackgrounds);
+  addPanelsToGrid(gRenderForegrounds);
+  addPanelsToGrid(gWater);
+  addPanelsToGrid(gAcid1);
+  addPanelsToGrid(gAcid2);
+  addPanelsToGrid(gSteps);
+  addPanelsToGrid(gLifts); // it doesn't matter which LIFT type is used here
+  addPanelsToGrid(gBlockMon);
 
   mapGrid.dumpStats();
-  //e_WriteLog(Format('tree depth: %d; %d nodes used, %d nodes allocated', [mapTree.computeTreeHeight, mapTree.nodeCount, mapTree.nodeAlloced]), MSG_NOTIFY);
-  //mapTree.forEachLeaf(nil);
+
+  g_Mons_InitTree(mapGrid.gridX0, mapGrid.gridY0, mapGrid.gridWidth, mapGrid.gridHeight);
 end;
 
+
 function g_Map_Load(Res: String): Boolean;
 const
   DefaultMusRes = 'Standart.wad:STDMUS\MUS1';
@@ -1328,12 +1315,15 @@ var
   Len: Integer;
   ok, isAnim, trigRef: Boolean;
   CurTex, ntn: Integer;
-
+  {$IF DEFINED(D2D_NEW_MAP_READER)}
+  pr: TTextParser = nil;
+  st: TStream = nil;
+  wst: TSFSMemoryChunkStream = nil;
+  rec: TDynRecord;
+  {$ENDIF}
 begin
   mapGrid.Free();
   mapGrid := nil;
-  //mapTree.Free();
-  //mapTree := nil;
 
   Result := False;
   gMapInfo.Map := Res;
@@ -1354,6 +1344,7 @@ begin
       WAD.Free();
       Exit;
     end;
+
     //k8: why loader ignores path here?
     mapResName := g_ExtractFileName(Res);
     if not WAD.GetMapResource(mapResName, Data, Len) then
@@ -1365,9 +1356,67 @@ begin
 
     WAD.Free();
 
-  // Çàãðóçêà êàðòû:
-    e_WriteLog('Loading map: '+mapResName, MSG_NOTIFY);
+    if (Len < 4) then
+    begin
+      e_LogWritefln('invalid map file: ''%s''', [mapResName]);
+      FreeMem(Data);
+      exit;
+    end;
+
+    // Çàãðóçêà êàðòû:
+    e_LogWritefln('Loading map: %s', [mapResName], MSG_NOTIFY);
     g_Game_SetLoadingText(_lc[I_LOAD_MAP], 0, False);
+
+    {$IF DEFINED(D2D_NEW_MAP_READER)}
+      if (PChar(Data)[0] = 'M') and (PChar(Data)[1] = 'A') and (PChar(Data)[2] = 'P') and (PByte(Data)[3] = 1) then
+      begin
+        // nothing
+      end
+      else
+      begin
+        e_LogWritefln('Loading text map: %s', [mapResName]);
+        loadMapDefinition();
+        if (dfmapdef = nil) then raise Exception.Create('internal map loader error');
+        //e_LogWritefln('***'#10'%s'#10'***', [dfmapdef.headerType.definition]);
+        wst := TSFSMemoryChunkStream.Create(Data, Len);
+        try
+          pr := TFileTextParser.Create(wst);
+          e_LogWritefln('parsing text map: %s', [mapResName]);
+          rec := dfmapdef.parseMap(pr);
+        except on e: Exception do
+          begin
+            if (pr <> nil) then e_LogWritefln('ERROR at (%s,%s): %s', [pr.line, pr.col, e.message])
+            else e_LogWritefln('ERROR: %s', [e.message]);
+            pr.Free();
+            wst.Free();
+            FreeMem(Data);
+            exit;
+          end;
+        end;
+        pr.Free();
+        //wst.Free(); // pr will do it
+        e_LogWritefln('writing text map to temporary bin storage...', []);
+        st := TMemoryStream.Create();
+        try
+          rec.writeBinTo(st);
+          Len := Integer(st.position);
+          st.position := 0;
+          FreeMem(Data);
+          GetMem(Data, Len);
+          st.ReadBuffer(Data^, Len);
+        except on e: Exception do
+          begin
+            rec.Free();
+            st.Free();
+            e_LogWritefln('ERROR: %s', [e.message]);
+            FreeMem(Data);
+            exit;
+          end;
+        end;
+        st.Free();
+      end;
+    {$ENDIF}
+
     MapReader := TMapReader_1.Create();
 
     if not MapReader.LoadMap(Data) then
@@ -1385,6 +1434,22 @@ begin
     _textures := MapReader.GetTextures();
     _texnummap := nil;
 
+  // Çàãðóçêà îïèñàíèÿ êàðòû:
+    e_WriteLog('  Reading map info...', MSG_NOTIFY);
+    g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
+    Header := MapReader.GetMapHeader();
+
+    with gMapInfo do
+    begin
+      Name := Header.MapName;
+      Description := Header.MapDescription;
+      Author := Header.MapAuthor;
+      MusicName := Header.MusicName;
+      SkyName := Header.SkyName;
+      Height := Header.Height;
+      Width := Header.Width;
+    end;
+
   // Äîáàâëåíèå òåêñòóð â Textures[]:
     if _textures <> nil then
     begin
@@ -1397,12 +1462,16 @@ begin
         SetLength(s, 64);
         CopyMemory(@s[1], @_textures[a].Resource[0], 64);
         for b := 1 to Length(s) do
+        begin
           if s[b] = #0 then
           begin
             SetLength(s, b-1);
             Break;
           end;
+        end;
+        {$IF DEFINED(D2F_DEBUG)}
         e_WriteLog(Format('    Loading texture #%d: %s', [a, s]), MSG_NOTIFY);
+        {$ENDIF}
         //if g_Map_IsSpecialTexture(s) then e_WriteLog('      SPECIAL!', MSG_NOTIFY);
       // Àíèìèðîâàííàÿ òåêñòóðà:
         if ByteBool(_textures[a].Anim) then
@@ -1640,6 +1709,10 @@ begin
       end;
     end;
 
+    // create map grid, init other grids (for monsters, for example)
+    e_WriteLog('Creating map grid', MSG_NOTIFY);
+    mapCreateGrid();
+
   // Åñëè íå LoadState, òî ñîçäàåì òðèããåðû:
     if (triggers <> nil) and not gLoadGameMode then
     begin
@@ -1705,24 +1778,8 @@ begin
         CreateMonster(monsters[a]);
     end;
 
-  // Çàãðóçêà îïèñàíèÿ êàðòû:
-    e_WriteLog('  Reading map info...', MSG_NOTIFY);
-    g_Game_SetLoadingText(_lc[I_LOAD_MAP_HEADER], 0, False);
-    Header := MapReader.GetMapHeader();
-
     MapReader.Free();
 
-    with gMapInfo do
-    begin
-      Name := Header.MapName;
-      Description := Header.MapDescription;
-      Author := Header.MapAuthor;
-      MusicName := Header.MusicName;
-      SkyName := Header.SkyName;
-      Height := Header.Height;
-      Width := Header.Width;
-    end;
-
   // Çàãðóçêà íåáà:
     if gMapInfo.SkyName <> '' then
     begin
@@ -1801,9 +1858,6 @@ begin
     sfsGCEnable(); // enable releasing unused volumes
   end;
 
-  e_WriteLog('Creating map grid', MSG_NOTIFY);
-  mapCreateGrid();
-
   e_WriteLog('Done loading map.', MSG_NOTIFY);
   Result := True;
 end;
@@ -2132,15 +2186,7 @@ procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer);
 begin
   dplClear();
   //tagmask := panelTypeToTag(PanelType);
-
-  {if gdbg_map_use_tree_draw then
-  begin
-    mapTree.aabbQuery(x0, y0, wdt, hgt, checker, (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore));
-  end
-  else}
-  begin
-    mapGrid.forEachInAABB(x0, y0, wdt, hgt, checker, (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore));
-  end;
+  mapGrid.forEachInAABB(x0, y0, wdt, hgt, checker, GridDrawableMask);
   // list will be rendered in `g_game.DrawPlayer()`
 end;
 
@@ -2154,14 +2200,7 @@ procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius:
   end;
 
 begin
-  {if gdbg_map_use_tree_draw then
-  begin
-    mapTree.aabbQuery(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor));
-  end
-  else}
-  begin
-    mapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor));
-  end;
+  mapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor));
 end;
 
 
@@ -2325,10 +2364,10 @@ end;
 
 
 function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word; b1x3: Boolean): Boolean;
+const
+  SlowMask = GridTagLift or GridTagBlockMon;
   function checker (pan: TPanel; tag: Integer): Boolean;
   begin
-    result := false; // don't stop, ever
-
     {
     if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
     begin
@@ -2356,7 +2395,7 @@ function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word;
 
     // other shit
     //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
-    result := true;
+    result := true; // i found her!
   end;
 
 var
@@ -2375,43 +2414,30 @@ begin
   if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
   if gdbg_map_use_accel_coldet then
   begin
-    {if gdbg_map_use_tree_coldet then
+    if (Width = 1) and (Height = 1) then
     begin
-      //e_WriteLog(Format('coldet query: x=%d; y=%d; w=%d; h=%d', [X, Y, Width, Height]), MSG_NOTIFY);
-      result := (mapTree.aabbQuery(X, Y, Width, Height, checker, tagmask) <> nil);
-      if (gdbg_map_dump_coldet_tree_queries) and (mapTree.nodesVisited <> 0) then
+      if ((tagmask and SlowMask) <> 0) then
+      begin
+        // slow
+        result := (mapGrid.forEachAtPoint(X, Y, checker, tagmask) <> nil);
+      end
+      else
       begin
-        //e_WriteLog(Format('map collision: %d nodes visited (%d deep)', [mapTree.nodesVisited, mapTree.nodesDeepVisited]), MSG_NOTIFY);
-        g_Console_Add(Format('map collision: %d nodes visited (%d deep)', [mapTree.nodesVisited, mapTree.nodesDeepVisited]));
+        // fast
+        result := (mapGrid.forEachAtPoint(X, Y, nil, tagmask) <> nil);
       end;
     end
-    else}
+    else
     begin
-      if (Width = 1) and (Height = 1) then
+      if ((tagmask and SlowMask) <> 0) then
       begin
-        if ((tagmask and (GridTagLift or GridTagBlockMon)) <> 0) then
-        begin
-          // slow
-          result := mapGrid.forEachAtPoint(X, Y, checker, tagmask);
-        end
-        else
-        begin
-          // fast
-          result := mapGrid.forEachAtPoint(X, Y, nil, tagmask);
-        end;
+        // slow
+        result := (mapGrid.forEachInAABB(X, Y, Width, Height, checker, tagmask) <> nil);
       end
       else
       begin
-        if ((tagmask and (GridTagLift or GridTagBlockMon)) <> 0) then
-        begin
-          // slow
-          result := mapGrid.forEachInAABB(X, Y, Width, Height, checker, tagmask);
-        end
-        else
-        begin
-          // fast
-          result := mapGrid.forEachInAABB(X, Y, Width, Height, nil, tagmask);
-        end;
+        // fast
+        result := (mapGrid.forEachInAABB(X, Y, Width, Height, nil, tagmask) <> nil);
       end;
     end;
   end
@@ -2456,20 +2482,13 @@ begin
   if gdbg_map_use_accel_coldet then
   begin
     texid := TEXTURE_NONE;
-    {if gdbg_map_use_tree_coldet then
+    if (Width = 1) and (Height = 1) then
     begin
-      mapTree.aabbQuery(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
+      mapGrid.forEachAtPoint(X, Y, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
     end
-    else}
+    else
     begin
-      if (Width = 1) and (Height = 1) then
-      begin
-        mapGrid.forEachAtPoint(X, Y, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
-      end
-      else
-      begin
-        mapGrid.forEachInAABB(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
-      end;
+      mapGrid.forEachInAABB(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
     end;
     result := texid;
   end
@@ -2482,27 +2501,40 @@ end;
 
 
 procedure g_Map_EnableWall(ID: DWORD);
+var
+  pan: TPanel;
 begin
-  with gWalls[ID] do
-  begin
-    Enabled := True;
-    g_Mark(X, Y, Width, Height, MARK_DOOR, True);
-    mapGrid.proxyEnabled[proxyId] := true;
+  pan := gWalls[ID];
+  pan.Enabled := True;
+  g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, True);
 
-    if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(PanelType, ID);
-  end;
+  mapGrid.proxyEnabled[pan.proxyId] := true;
+  //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
+  //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
+
+  if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(gWalls[ID].PanelType, ID);
+
+  {$IFDEF MAP_DEBUG_ENABLED_FLAG}
+  //e_WriteLog(Format('ENABLE: wall #%d(%d) enabled (%d)  (%d,%d)-(%d,%d)', [Integer(ID), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.x, pan.y, pan.width, pan.height]), MSG_NOTIFY);
+  {$ENDIF}
 end;
 
 procedure g_Map_DisableWall(ID: DWORD);
+var
+  pan: TPanel;
 begin
-  with gWalls[ID] do
-  begin
-    Enabled := False;
-    g_Mark(X, Y, Width, Height, MARK_DOOR, False);
-    mapGrid.proxyEnabled[proxyId] := false;
+  pan := gWalls[ID];
+  pan.Enabled := False;
+  g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, False);
 
-    if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(PanelType, ID);
-  end;
+  mapGrid.proxyEnabled[pan.proxyId] := false;
+  //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
+
+  if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pan.PanelType, ID);
+
+  {$IFDEF MAP_DEBUG_ENABLED_FLAG}
+  //e_WriteLog(Format('DISABLE: wall #%d(%d) disabled (%d)  (%d,%d)-(%d,%d)', [Integer(ID), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.x, pan.y, pan.width, pan.height]), MSG_NOTIFY);
+  {$ENDIF}
 end;
 
 procedure g_Map_SwitchTexture(PanelType: Word; ID: DWORD; AnimLoop: Byte = 0);
@@ -2797,6 +2829,7 @@ var
     PAMem.LoadFromMemory(Mem);
 
     for i := 0 to Length(panels)-1 do
+    begin
       if panels[i].SaveIt then
       begin
       // ID ïàíåëè:
@@ -2807,7 +2840,10 @@ var
         end;
       // Çàãðóæàåì ïàíåëü:
         panels[i].LoadState(PAMem);
+        if (panels[i].arrIdx <> i) then raise Exception.Create('g_Map_LoadState: LoadPanelArray: Wrong Panel arrIdx');
+        if (panels[i].proxyId >= 0) then mapGrid.proxyEnabled[panels[i].proxyId] := panels[i].Enabled;
       end;
+    end;
 
   // Ýòîò ñïèñîê ïàíåëåé çàãðóæåí:
     PAMem.Free();
@@ -2860,7 +2896,7 @@ begin
 
 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó:
   g_GFX_Init();
-  mapCreateGrid();
+  //mapCreateGrid();
 
 ///// Çàãðóæàåì ìóçûêó: /////
 // Ñèãíàòóðà ìóçûêè:
@@ -2923,21 +2959,21 @@ end;
 
 // trace liquid, stepping by `dx` and `dy`
 // return last seen liquid coords, and `false` if we're started outside of the liquid
-function g_Map_TraceLiquid (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
+function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
 const
   MaskLiquid = GridTagWater or GridTagAcid1 or GridTagAcid2;
 begin
   topx := x;
   topy := y;
   // started outside of the liquid?
-  if not mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) then begin result := false; exit; end;
+  if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then begin result := false; exit; end;
   if (dx = 0) and (dy = 0) then begin result := false; exit; end; // sanity check
   result := true;
   while true do
   begin
     Inc(x, dx);
     Inc(y, dy);
-    if not mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) then exit; // out of the water, just exit
+    if (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
     topx := x;
     topy := y;
   end;