DEADSOFTWARE

removed all mentions of dynaabb tree from the sources; WARNING! EVERYTHING IS BROKEN!
[d2df-sdl.git] / src / game / g_map.pas
index 45a3fa3ccfb5b5c0e48acc5281dd295b55d782a3..7d13205d096c4f0e259fb3411d137dab0f01e917 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
@@ -99,10 +100,15 @@ type
 
 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
 
+// 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;
+
 
 procedure g_Map_ProfilersBegin ();
 procedure g_Map_ProfilersEnd ();
 
+
 const
   RESPAWNPOINT_PLAYER1 = 1;
   RESPAWNPOINT_PLAYER2 = 2;
@@ -152,6 +158,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;
@@ -173,9 +181,6 @@ 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()`
 
@@ -202,26 +207,6 @@ const
 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
@@ -245,12 +230,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 ();
@@ -271,8 +255,7 @@ var
   RespawnPoints: Array of TRespawnPoint;
   FlagPoints:    Array [FLAG_RED..FLAG_BLUE] of PFlagPoint;
   //DOMFlagPoints: Array of TFlagPoint;
-  gMapGrid: TPanelGrid = nil;
-  //mapTree: TDynAABBTreeMap = nil;
+  mapGrid: TPanelGrid = nil;
 
 
 procedure g_Map_ProfilersBegin ();
@@ -295,118 +278,12 @@ begin
 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;
-
-var
-  qr: TDynAABBTreeMap.TSegmentQueryResult;
-  ray: Ray2D;
-  hxf, hyf: Single;
-  hx, hy: Integer;
-  maxDistSq: Single;
-begin
-  result := -1;
-  if (mapTree = nil) then exit;
-  if mapTree.segmentQuery(qr, x0, y0, x1, y1, sqchecker, (GridTagWall or GridTagDoor)) 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;
-  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;
-
+  ex, ey: Integer;
 begin
-  result := false;
-  if (gMapGrid = nil) then exit;
-  lastDistSq := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)+1;
-  lastX := 0;
-  lastY := 0;
-  result := gMapGrid.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)) <> nil);
 end;
 
 
@@ -414,24 +291,26 @@ function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
 
   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; // stop if wall is enabled
+      exit;
     end;
-
-    result := (x >= pan.X) and (y >= pan.Y) and (x < pan.X+pan.Width) and (y < pan.Y+pan.Height);
-    if not result then exit;
+    }
 
     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
@@ -448,7 +327,16 @@ begin
   if WordBool(PanelType and PANEL_BLOCKMON) then tagmask := tagmask or GridTagBlockMon;
 
   if (tagmask = 0) then exit;// just in case
-  result := gMapGrid.forEachAtPoint(x, y, checker, tagmask);
+  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;
 
 
@@ -660,7 +548,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;
@@ -1012,7 +901,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,
@@ -1197,6 +1086,7 @@ begin
   addResToExternalResList(mapHeader.SkyName);
 end;
 
+
 procedure mapCreateGrid ();
 var
   mapX0: Integer = $3fffffff;
@@ -1232,16 +1122,21 @@ var
       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
+      pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, tag);
+      // "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);
@@ -1255,8 +1150,7 @@ begin
 
   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();
+  mapGrid := TPanelGrid.Create(mapX0-512, mapY0-512, mapX1-mapX0+1+512*2, mapY1-mapY0+1+512*2);
 
   addPanelsToGrid(gWalls, PANEL_WALL);
   addPanelsToGrid(gWalls, PANEL_CLOSEDOOR);
@@ -1270,11 +1164,12 @@ begin
   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);
+  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';
@@ -1307,8 +1202,8 @@ var
   CurTex, ntn: Integer;
 
 begin
-  gMapGrid.Free();
-  gMapGrid := nil;
+  mapGrid.Free();
+  mapGrid := nil;
   //mapTree.Free();
   //mapTree := nil;
 
@@ -1617,6 +1512,11 @@ 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
@@ -1778,9 +1678,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;
@@ -2098,6 +1995,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
@@ -2115,13 +2013,14 @@ begin
   end
   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, true);
   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
@@ -2135,7 +2034,7 @@ begin
   end
   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), true);
   end;
 end;
 
@@ -2300,14 +2199,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
@@ -2322,13 +2224,13 @@ function g_Map_CollidePanel(X, Y: Integer; Width, Height: Word; PanelType: Word;
 
     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 := true;
+    result := true; // i found her!
   end;
 
 var
@@ -2361,11 +2263,29 @@ begin
     begin
       if (Width = 1) and (Height = 1) then
       begin
-        result := gMapGrid.forEachAtPoint(X, Y, checker, tagmask);
+        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
-        result := gMapGrid.forEachInAABB(X, Y, Width, Height, checker, tagmask);
+        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
@@ -2418,11 +2338,11 @@ begin
     begin
       if (Width = 1) and (Height = 1) then
       begin
-        gMapGrid.forEachAtPoint(X, Y, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
+        mapGrid.forEachAtPoint(X, Y, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
       end
       else
       begin
-        gMapGrid.forEachInAABB(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
+        mapGrid.forEachInAABB(X, Y, Width, Height, checker, (GridTagWater or GridTagAcid1 or GridTagAcid2));
       end;
     end;
     result := texid;
@@ -2434,26 +2354,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('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('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);
@@ -2493,8 +2429,8 @@ begin
   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)
@@ -2504,7 +2440,6 @@ begin
       g_Mark(X, Y, Width, Height, MARK_LIFTLEFT, True)
     else if LiftType = 3 then
       g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT, True);
-    }
 
     if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(PanelType, ID);
   end;
@@ -2872,4 +2807,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_TraceLiquid (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.