DEADSOFTWARE

some fixes for recursive grid queries (grid doesn't support recursive queries, but...
[d2df-sdl.git] / src / game / g_map.pas
index 0543abe30aa620cb0506c08d8e2c480dc06a0356..4f86e9b0d3f14ddc3f21e097756080f4996e2e12 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
@@ -67,7 +68,7 @@ procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer);
 
 procedure g_Map_DrawBack(dx, dy: Integer);
 function  g_Map_CollidePanel(X, Y: Integer; Width, Height: Word;
-                             PanelType: Word; b1x3: Boolean): Boolean;
+                             PanelType: Word; b1x3: Boolean=false): Boolean;
 function  g_Map_CollideLiquid_Texture(X, Y: Integer; Width, Height: Word): DWORD;
 procedure g_Map_EnableWall(ID: DWORD);
 procedure g_Map_DisableWall(ID: DWORD);
@@ -91,9 +92,29 @@ procedure g_Map_LoadState(Var Mem: TBinMemoryReader);
 
 procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer);
 
+// 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_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
+
+
 procedure g_Map_ProfilersBegin ();
 procedure g_Map_ProfilersEnd ();
 
+
 const
   RESPAWNPOINT_PLAYER1 = 1;
   RESPAWNPOINT_PLAYER2 = 2;
@@ -143,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;
@@ -164,14 +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
@@ -189,26 +218,6 @@ const
   FLAG_SIGNATURE = $47414C46; // 'FLAG'
 
 
-type
-  TPanelGrid = specialize TBodyGridBase<TPanel>;
-
-  TDynAABBTreePanelBase = specialize TDynAABBTreeBase<TPanel>;
-
-  TDynAABBTreeMap = class(TDynAABBTreePanelBase)
-    function getFleshAABB (out aabb: AABB2D; pan: TPanel; tag: Integer): Boolean; override;
-  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;
-end;
-
-
 function panelTypeToTag (panelType: Word): Integer;
 begin
   case panelType of
@@ -231,12 +240,11 @@ function dplLess (a, b: TObject): Boolean;
 var
   pa, pb: TPanel;
 begin
-  //result := ((a as TPanel).ArrIdx < (b as TPanel).ArrIdx);
   pa := TPanel(a);
   pb := TPanel(b);
   if (pa.tag < pb.tag) then begin result := true; exit; end;
   if (pa.tag > pb.tag) then begin result := false; exit; end;
-  result := (pa.ArrIdx < pb.ArrIdx);
+  result := (pa.arrIdx < pb.arrIdx);
 end;
 
 procedure dplClear ();
@@ -257,8 +265,6 @@ var
   RespawnPoints: Array of TRespawnPoint;
   FlagPoints:    Array [FLAG_RED..FLAG_BLUE] of PFlagPoint;
   //DOMFlagPoints: Array of TFlagPoint;
-  gMapGrid: TPanelGrid = nil;
-  mapTree: TDynAABBTreeMap = nil;
 
 
 procedure g_Map_ProfilersBegin ();
@@ -281,6 +287,104 @@ begin
 end;
 
 
+// wall index in `gWalls` or -1
+function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
+var
+  ex, ey: Integer;
+begin
+  result := mapGrid.traceRay(ex, ey, x0, y0, x1, y1, nil, (GridTagWall or GridTagDoor));
+  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;
+
+// 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 := 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;
+
+
+function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
+
+  function checker (pan: TPanel; tag: Integer): Boolean;
+  begin
+    {
+    if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
+    begin
+      result := pan.Enabled; // stop if wall is enabled
+      exit;
+    end;
+    }
+
+    if ((tag and GridTagLift) <> 0) then
+    begin
+      // stop if the lift of the right type
+      result :=
+        ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = 0)) or
+         (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = 1)) or
+         (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = 2)) or
+         (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = 3)));
+      exit;
+    end;
+
+    result := true; // otherwise, stop anyway, 'cause `forEachAtPoint()` is guaranteed to call this only for correct panels
+  end;
+
+var
+  tagmask: Integer = 0;
+begin
+  result := false;
+
+  if WordBool(PanelType and (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR)) then tagmask := tagmask or (GridTagWall or GridTagDoor);
+  if WordBool(PanelType and PANEL_WATER) then tagmask := tagmask or GridTagWater;
+  if WordBool(PanelType and PANEL_ACID1) then tagmask := tagmask or GridTagAcid1;
+  if WordBool(PanelType and PANEL_ACID2) then tagmask := tagmask or GridTagAcid2;
+  if WordBool(PanelType and PANEL_STEP) then tagmask := tagmask or GridTagStep;
+  if WordBool(PanelType and (PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT)) then tagmask := tagmask or GridTagLift;
+  if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
+
+  if (tagmask = 0) then exit;// just in case
+  if ((tagmask and GridTagLift) <> 0) then
+  begin
+    // slow
+    result := (mapGrid.forEachAtPoint(x, y, checker, tagmask) <> nil);
+  end
+  else
+  begin
+    // fast
+    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
@@ -489,7 +593,8 @@ begin
   SetLength(panels^, len + 1);
 
   panels^[len] := TPanel.Create(PanelRec, AddTextures, CurTex, Textures);
-  panels^[len].ArrIdx := len;
+  panels^[len].arrIdx := len;
+  panels^[len].proxyId := -1;
   panels^[len].tag := panelTypeToTag(PanelRec.PanelType);
   if sav then
     panels^[len].SaveIt := True;
@@ -841,7 +946,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,
@@ -935,45 +1040,52 @@ end;
 
 procedure CreateMonster(monster: TMonsterRec_1);
 var
-  a, i: Integer;
+  a: Integer;
+  mon: TMonster;
 begin
   if g_Game_IsClient then Exit;
 
   if (gGameSettings.GameType = GT_SINGLE)
   or LongBool(gGameSettings.Options and GAME_OPTION_MONSTERS) then
   begin
-    i := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y,
-                           TDirection(monster.Direction));
+    mon := g_Monsters_Create(monster.MonsterType, monster.X, monster.Y, TDirection(monster.Direction));
 
     if gTriggers <> nil then
+    begin
       for a := 0 to High(gTriggers) do
-        if gTriggers[a].TriggerType in [TRIGGER_PRESS,
-             TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
-          if (gTriggers[a].Data.MonsterID-1) = gMonsters[i].StartID then
-            gMonsters[i].AddTrigger(a);
+      begin
+        if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
+        begin
+          if (gTriggers[a].Data.MonsterID-1) = mon.StartID then mon.AddTrigger(a);
+        end;
+      end;
+    end;
 
-    if monster.MonsterType <> MONSTER_BARREL then
-      Inc(gTotalMonsters);
+    if monster.MonsterType <> MONSTER_BARREL then Inc(gTotalMonsters);
   end;
 end;
 
 procedure g_Map_ReAdd_DieTriggers();
-var
-  i, a: Integer;
-begin
-  if g_Game_IsClient then Exit;
 
-  for i := 0 to High(gMonsters) do
-    if gMonsters[i] <> nil then
+  function monsDieTrig (mon: TMonster): Boolean;
+  var
+    a: Integer;
+  begin
+    result := false; // don't stop
+    mon.ClearTriggers();
+    for a := 0 to High(gTriggers) do
+    begin
+      if gTriggers[a].TriggerType in [TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
       begin
-        gMonsters[i].ClearTriggers();
-
-        for a := 0 to High(gTriggers) do
-          if gTriggers[a].TriggerType in [TRIGGER_PRESS,
-               TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF] then
-            if (gTriggers[a].Data.MonsterID-1) = gMonsters[i].StartID then
-              gMonsters[i].AddTrigger(a);
+        if (gTriggers[a].Data.MonsterID-1) = mon.StartID then mon.AddTrigger(a);
       end;
+    end;
+  end;
+
+begin
+  if g_Game_IsClient then Exit;
+
+  g_Mons_ForEach(monsDieTrig);
 end;
 
 function extractWadName(resourceName: string): string;
@@ -1019,6 +1131,7 @@ begin
   addResToExternalResList(mapHeader.SkyName);
 end;
 
+
 procedure mapCreateGrid ();
 var
   mapX0: Integer = $3fffffff;
@@ -1026,7 +1139,7 @@ var
   mapX1: Integer = -$3fffffff;
   mapY1: Integer = -$3fffffff;
 
-  procedure calcBoundingBox (var panels: TPanelArray);
+  procedure calcBoundingBox (constref panels: TPanelArray);
   var
     idx: Integer;
     pan: TPanel;
@@ -1043,27 +1156,56 @@ var
     end;
   end;
 
-  procedure addPanelsToGrid (var 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;
-      gMapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, tag);
-      mapTree.insertObject(pan, tag, true); // as static object
+      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;
+      {$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
-  gMapGrid.Free();
-  gMapGrid := nil;
-  mapTree.Free();
-  mapTree := nil;
+  mapGrid.Free();
+  mapGrid := nil;
 
   calcBoundingBox(gWalls);
   calcBoundingBox(gRenderBackgrounds);
@@ -1075,28 +1217,33 @@ begin
   calcBoundingBox(gLifts);
   calcBoundingBox(gBlockMon);
 
-  e_WriteLog(Format('map dimensions: (%d,%d)-(%d,%d)', [mapX0, mapY0, mapX1, mapY1]), MSG_WARNING);
-
-  gMapGrid := TPanelGrid.Create(mapX0, mapY0, mapX1-mapX0+1, mapY1-mapY0+1);
-  mapTree := TDynAABBTreeMap.Create();
-
-  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);
-
-  gMapGrid.dumpStats();
-  e_WriteLog(Format('tree depth: %d; %d nodes used, %d nodes allocated', [mapTree.computeTreeHeight, mapTree.nodeCount, mapTree.nodeAlloced]), MSG_NOTIFY);
-  mapTree.forEachLeaf(nil);
+  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-128, mapY0-128, mapX1-mapX0+1+128*2, mapY1-mapY0+1+128*2);
+  //mapGrid := TPanelGrid.Create(0, 0, gMapInfo.Width, gMapInfo.Height);
+
+  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();
+
+  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';
@@ -1129,10 +1276,10 @@ var
   CurTex, ntn: Integer;
 
 begin
-  gMapGrid.Free();
-  gMapGrid := nil;
-  mapTree.Free();
-  mapTree := nil;
+  mapGrid.Free();
+  mapGrid := nil;
+  //mapTree.Free();
+  //mapTree := nil;
 
   Result := False;
   gMapInfo.Map := Res;
@@ -1184,6 +1331,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
@@ -1196,12 +1359,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
@@ -1439,6 +1606,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
@@ -1504,24 +1675,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
@@ -1600,9 +1755,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;
@@ -1890,7 +2042,7 @@ end;
 // old algo
 procedure g_Map_DrawPanels (PanelType: Word);
 
-  procedure DrawPanels (var panels: TPanelArray; drawDoors: Boolean=False);
+  procedure DrawPanels (constref panels: TPanelArray; drawDoors: Boolean=False);
   var
     idx: Integer;
   begin
@@ -1920,6 +2072,7 @@ end;
 
 // new algo
 procedure g_Map_CollectDrawPanels (x0, y0, wdt, hgt: Integer);
+
   function checker (pan: TPanel; tag: Integer): Boolean;
   begin
     result := false; // don't stop, ever
@@ -1931,19 +2084,20 @@ begin
   dplClear();
   //tagmask := panelTypeToTag(PanelType);
 
-  if gdbg_map_use_tree_draw then
+  {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
+  else}
   begin
-    gMapGrid.forEachInAABB(x0, y0, wdt, hgt, checker, (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore));
+    mapGrid.forEachInAABB(x0, y0, wdt, hgt, checker, GridDrawableMask);
   end;
   // list will be rendered in `g_game.DrawPlayer()`
 end;
 
 
 procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer);
+
   function checker (pan: TPanel; tag: Integer): Boolean;
   begin
     result := false; // don't stop, ever
@@ -1951,13 +2105,13 @@ procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius:
   end;
 
 begin
-  if gdbg_map_use_tree_draw then
+  {if gdbg_map_use_tree_draw then
   begin
     mapTree.aabbQuery(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor));
   end
-  else
+  else}
   begin
-    gMapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor));
+    mapGrid.forEachInAABB(lightX-radius, lightY-radius, radius*2, radius*2, checker, (GridTagWall or GridTagDoor));
   end;
 end;
 
@@ -1971,7 +2125,7 @@ begin
 end;
 
 function g_Map_CollidePanelOld(X, Y: Integer; Width, Height: Word;
-                            PanelType: Word; b1x3: Boolean): Boolean;
+                            PanelType: Word; b1x3: Boolean=false): Boolean;
 var
   a, h: Integer;
 begin
@@ -2094,7 +2248,7 @@ function g_Map_CollideLiquid_TextureOld(X, Y: Integer; Width, Height: Word): DWO
 var
   texid: DWORD;
 
-  function checkPanels (var panels: TPanelArray): Boolean;
+  function checkPanels (constref panels: TPanelArray): Boolean;
   var
     a: Integer;
   begin
@@ -2122,14 +2276,17 @@ 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
-      if not pan.Enabled then exit;
+      result := pan.Enabled;
+      exit;
     end;
+    }
 
     if ((tag and GridTagLift) <> 0) then
     begin
@@ -2137,19 +2294,20 @@ function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word;
         ((WordBool(PanelType and PANEL_LIFTUP) and (pan.LiftType = 0)) or
          (WordBool(PanelType and PANEL_LIFTDOWN) and (pan.LiftType = 1)) or
          (WordBool(PanelType and PANEL_LIFTLEFT) and (pan.LiftType = 2)) or
-         (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = 3))) and
-         g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
+         (WordBool(PanelType and PANEL_LIFTRIGHT) and (pan.LiftType = 3))) {and
+         g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height)};
       exit;
     end;
 
     if ((tag and GridTagBlockMon) <> 0) then
     begin
-      result := ((not b1x3) or (pan.Width+pan.Height >= 64)) and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
+      result := ((not b1x3) or (pan.Width+pan.Height >= 64)); //and g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
       exit;
     end;
 
     // other shit
-    result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
+    //result := g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height);
+    result := true; // i found her!
   end;
 
 var
@@ -2168,7 +2326,7 @@ begin
   if (profMapCollision <> nil) then profMapCollision.sectionBeginAccum('*solids');
   if gdbg_map_use_accel_coldet then
   begin
-    if gdbg_map_use_tree_coldet then
+    {if gdbg_map_use_tree_coldet 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);
@@ -2178,9 +2336,34 @@ begin
         g_Console_Add(Format('map collision: %d nodes visited (%d deep)', [mapTree.nodesVisited, mapTree.nodesDeepVisited]));
       end;
     end
-    else
+    else}
     begin
-      result := gMapGrid.forEachInAABB(X, Y, Width, Height, checker, tagmask);
+      if (Width = 1) and (Height = 1) then
+      begin
+        if ((tagmask and SlowMask) <> 0) then
+        begin
+          // slow
+          result := (mapGrid.forEachAtPoint(X, Y, checker, tagmask) <> nil);
+        end
+        else
+        begin
+          // fast
+          result := (mapGrid.forEachAtPoint(X, Y, nil, tagmask) <> nil);
+        end;
+      end
+      else
+      begin
+        if ((tagmask and SlowMask) <> 0) then
+        begin
+          // slow
+          result := (mapGrid.forEachInAABB(X, Y, Width, Height, checker, tagmask) <> nil);
+        end
+        else
+        begin
+          // fast
+          result := (mapGrid.forEachInAABB(X, Y, Width, Height, nil, tagmask) <> nil);
+        end;
+      end;
     end;
   end
   else
@@ -2208,7 +2391,7 @@ var
       //2: if ((tag and (GridTagWater or GridTagAcid1 or GridTagAcid2) = 0) then exit; // allowed: water, acid1, acid2
     end;
     // collision?
-    if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
+    //if not g_Collide(X, Y, Width, Height, pan.X, pan.Y, pan.Width, pan.Height) then exit;
     // yeah
     texid := pan.GetTextureID();
     // water? water has the highest priority, so stop right here
@@ -2224,13 +2407,20 @@ begin
   if gdbg_map_use_accel_coldet then
   begin
     texid := TEXTURE_NONE;
-    if gdbg_map_use_tree_coldet then
+    {if gdbg_map_use_tree_coldet then
     begin
       mapTree.aabbQuery(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
     end
-    else
+    else}
     begin
-      gMapGrid.forEachInAABB(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
+      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;
     end;
     result := texid;
   end
@@ -2241,26 +2431,42 @@ begin
   if (profMapCollision <> nil) then profMapCollision.sectionEnd();
 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);
+  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);
+  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);
@@ -2301,6 +2507,7 @@ begin
     LiftType := t;
 
     g_Mark(X, Y, Width, Height, MARK_LIFT, False);
+    //TODO: make separate lift tags, and change tag here
 
     if LiftType = 0 then
       g_Mark(X, Y, Width, Height, MARK_LIFTUP, True)
@@ -2554,6 +2761,7 @@ var
     PAMem.LoadFromMemory(Mem);
 
     for i := 0 to Length(panels)-1 do
+    begin
       if panels[i].SaveIt then
       begin
       // ID ïàíåëè:
@@ -2564,7 +2772,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();
@@ -2617,7 +2828,7 @@ begin
 
 // Îáíîâëÿåì êàðòó ñòîëêíîâåíèé è ñåòêó:
   g_GFX_Init();
-  mapCreateGrid();
+  //mapCreateGrid();
 
 ///// Çàãðóæàåì ìóçûêó: /////
 // Ñèãíàòóðà ìóçûêè:
@@ -2677,4 +2888,28 @@ begin
   Result := Addr(Arr[PanelByID[PanelID].PArrID]);
 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_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 (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 (mapGrid.forEachAtPoint(x, y, nil, MaskLiquid) = nil) then exit; // out of the water, just exit
+    topx := x;
+    topy := y;
+  end;
+end;
+
+
 end.