DEADSOFTWARE

moving platforms experiment (DO NOT USE YET!)
[d2df-sdl.git] / src / game / g_gfx.pas
index d90fa3b042f5cdbb9cdc5dd75efcfac3c5bb5a57..7903e13b28ca2a8d139e29b42ab64c4c3f937911 100644 (file)
@@ -14,6 +14,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *)
 {$INCLUDE ../shared/a_modes.inc}
+{$DEFINE D2F_NEW_SPARK_THINKER}
 unit g_gfx;
 
 interface
@@ -53,7 +54,7 @@ function  g_GFX_GetMax(): Integer;
 
 procedure g_GFX_OnceAnim(X, Y: Integer; Anim: TAnimation; AnimType: Byte = 0);
 
-procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean);
+procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean=true);
 
 procedure g_GFX_Update();
 procedure g_GFX_Draw();
@@ -67,9 +68,9 @@ var
 implementation
 
 uses
-  g_map, g_basic, Math, e_graphics, GL, GLExt,
+  g_map, g_panel, g_basic, Math, e_graphics, GL, GLExt,
   g_options, g_console, SysUtils, g_triggers, MAPDEF,
-  g_game, g_language, g_net;
+  g_game, g_language, g_net, xprofiler;
 
 type
   PParticle = ^TParticle;
@@ -86,6 +87,14 @@ type
     offsetX, offsetY:   ShortInt;
     // for bubbles
     liquidTopY: Integer; // don't float higher than this
+    // for water
+    stickDX: Integer;
+    // for blood
+    justSticked: Boolean;
+    stickEY: Integer;
+    // for all
+    onGround: Boolean;
+    awaken: Boolean;
 
     //k8: sorry, i have to emulate virtual methods this way, 'cause i haet `Object`
 
@@ -187,9 +196,35 @@ begin
 end;
 
 
-procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean);
+procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean=true);
 {$IF not DEFINED(HAS_COLLIDE_BITMAP)}
+var
+  part: PParticle;
+  f: Integer;
 begin
+  for f := 0 to High(Particles) do
+  begin
+    part := @Particles[f];
+    if part.alive and (part.onGround or (not part.justSticked and (part.State = STATE_STICK))) and
+       (part.X >= x-2) and (part.Y >= y-2) and (part.X < x+Width+4) and (part.Y < y+Height+4) then
+    begin
+      // wakup this particle
+      {
+      if (part.ParticleType = PARTICLE_SPARK) then
+      begin
+        e_LogWritefln('waking up particle of type %s; justSticked=%s; onGround=%s; VelY=%s; AccelY=%s', [part.ParticleType, part.justSticked, part.onGround, part.VelY, part.AccelY]);
+      end;
+      }
+      part.justSticked := true; // so sticked state will be re-evaluated
+      if part.onGround then
+      begin
+        if (part.VelY = 0) then part.VelY := 0.1;
+        if (part.AccelY = 0) then part.AccelY := 0.5;
+      end;
+      part.onGround := false; // so onground state will be re-evaluated
+      part.awaken := true;
+    end;
+  end;
 end;
 {$ELSE}
 var
@@ -324,6 +359,9 @@ end;
 procedure g_GFX_Init();
 begin
   //CreateCollideMap();
+{$IFDEF HEADLESS}
+  gpart_dbg_enabled := False;
+{$ENDIF}
 end;
 
 
@@ -346,208 +384,563 @@ begin
 end;
 
 
-{
-procedure CorrectOffsets(id: Integer); inline;
-var
-  part: PParticle;
-begin
-  part := @Particles[id];
-  part.offsetX := 0;
-  part.offsetY := 0;
-  // check for upper wall
-  if isBlockedAt(part.X, part.Y-1) then part.offsetY := 1;
-  // check for left wall
-  if isBlockedAt(part.X-1, part.Y) then part.offsetX := 1;
-end;
-}
-
-
 // ////////////////////////////////////////////////////////////////////////// //
 procedure TParticle.thinkerBlood ();
 var
   w, h: Integer;
   dX, dY: SmallInt;
+  {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
   b: Integer;
   s: ShortInt;
+  {$ELSE}
+  pan: TPanel;
+  ex, ey: Integer;
+  {$ENDIF}
 begin
   w := gMapInfo.Width;
   h := gMapInfo.Height;
 
   if gAdvBlood then
+  begin
+    if (State = STATE_STICK) then
     begin
-      if (State = STATE_STICK) then
-        {
-        if (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
-           (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) and
-           (not ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) and
-           (not ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED))
-        then
-        }
-        if (not isBlockedAt(X, Y-1)) and
-           (not isBlockedAt(X, Y+1)) and
-           (not isBlockedAt(X-1, Y)) and
-           (not isBlockedAt(X+1, Y))
-        then
-          begin // Îòëèïëà - êàïàåò
-            VelY := 0.5;
-            AccelY := 0.15;
-            State := STATE_NORMAL;
-          end
+      {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
+      {
+      if (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
+         (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) and
+         (not ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) and
+         (not ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED))
+      then
+      }
+      if (not isBlockedAt(X, Y-1)) and
+         (not isBlockedAt(X, Y+1)) and
+         (not isBlockedAt(X-1, Y)) and
+         (not isBlockedAt(X+1, Y))
+      {$ELSE}
+      if justSticked then
+      begin
+        if not mapGrid.traceOrthoRayWhileIn(ex, ey, X+stickDX, Y, X+stickDX, mapGrid.gridY0+mapGrid.gridHeight, GridTagWall or GridTagDoor or GridTagStep) then
+        begin
+           // îòëèïëà
+          State := STATE_NORMAL;
+          //e_LogWritefln('juststicked unsticked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s', [X, X+stickDX, stickDX, Y]);
+        end
         else
-          if Random(200) = 100 then
-          begin // Ïðèëåïëåíà - íî âîçìîæíî ñòåêàåò
-            VelY := 0.5;
-            AccelY := 0.15;
-            exit;
+        begin
+          stickEY := ey+1;
+          if (nil <> g_Map_traceToNearest(X, Y, X, stickEY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then
+          begin
+            if (ey > stickEY) then stickEY := ey-1;
           end;
+          justSticked := false;
+          //e_LogWritefln('juststicked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s; stickEY=%s', [X, X+stickDX, stickDX, Y, stickEY]);
+        end;
+      end;
+      if (State <> STATE_STICK) or (Y >= stickEY)
+      //if not g_Map_CollidePanel(X-1, Y-1, 3, 3, (PANEL_STEP or PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR))
+      {$ENDIF}
+      then
+      begin // Îòëèïëà - êàïàåò
+        VelY := 0.5;
+        AccelY := 0.15;
+        State := STATE_NORMAL;
+      end
+      else if (Random(200) = 100) then
+      begin // Ïðèëåïëåíà - íî âîçìîæíî ñòåêàåò
+        VelY := 0.5;
+        AccelY := 0.15;
+        exit;
+      end;
+    end;
 
-      if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
+    {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
+    if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
+    begin
+      if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
+      begin // Ëèôò ââåðõ
+        if (VelY > -4-Random(3)) then VelY -= 0.8;
+        if (abs(VelX) > 0.1) then VelX -= VelX/10.0;
+        VelX += (Random-Random)*0.2;
+        AccelY := 0.15;
+      end;
+      if isLiftLeftAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
+      begin // Ïîòîê âëåâî
+        if (VelX > -8-Random(3)) then VelX -= 0.8;
+        AccelY := 0.15;
+      end;
+      if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
+      begin // Ïîòîê âïðàâî
+        if (VelX < 8+Random(3)) then VelX += 0.8;
+        AccelY := 0.15;
+      end;
+    end;
+    {$ELSE}
+    pan := g_Map_PanelAtPoint(X, Y, GridTagLift);
+    if (pan <> nil) then
+    begin
+      if ((pan.PanelType and PANEL_LIFTUP) <> 0) then
       begin
-        if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
-        begin // Ëèôò ââåðõ
-          if VelY > -4-Random(3) then
-            VelY := VelY - 0.8;
-          if Abs(VelX) > 0.1 then
-            VelX := VelX - VelX/10.0;
-          VelX := VelX + (Random-Random)*0.2;
-          AccelY := 0.15;
-        end;
-        if isLiftLeftAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
-        begin // Ïîòîê âëåâî
-          if VelX > -8-Random(3) then
-            VelX := VelX - 0.8;
-          AccelY := 0.15;
-        end;
-        if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
-        begin // Ïîòîê âïðàâî
-          if VelX < 8+Random(3) then
-            VelX := VelX + 0.8;
-          AccelY := 0.15;
-        end;
+        if (VelY > -4-Random(3)) then VelY -= 0.8;
+        if (abs(VelX) > 0.1) then VelX -= VelX/10.0;
+        VelX += (Random-Random)*0.2;
+        AccelY := 0.15;
       end;
+      if ((pan.PanelType and PANEL_LIFTLEFT) <> 0) then
+      begin
+        if (VelX > -8-Random(3)) then VelX -= 0.8;
+        AccelY := 0.15;
+      end;
+      if ((pan.PanelType and PANEL_LIFTRIGHT) <> 0) then
+      begin
+        if (VelX < 8+Random(3)) then VelX += 0.8;
+        AccelY := 0.15;
+      end;
+    end;
+    {$ENDIF}
 
-      dX := Round(VelX);
-      dY := Round(VelY);
-
-      if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
-        if (State <> STATE_STICK) and
-           (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
-           (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
-           (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
-        begin // Âèñèò â âîçäóõå - êàïàåò
-          VelY := 0.8;
-          AccelY := 0.5;
-          State := STATE_NORMAL;
-        end;
+    dX := Round(VelX);
+    dY := Round(VelY);
 
-      if dX <> 0 then
+    {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
+    if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
+    begin
+      if (State <> STATE_STICK) and
+         (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
+         (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
+         (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
+      begin // Âèñèò â âîçäóõå - êàïàåò
+        VelY := 0.8;
+        AccelY := 0.5;
+        State := STATE_NORMAL;
+      end;
+    end;
+    {$ELSE}
+    if (State <> STATE_STICK) and (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
+    begin
+      // Âèñèò â âîçäóõå - êàïàåò
+      if (nil = g_Map_traceToNearest(X, Y-1, X, Y+1, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then
       begin
-        if dX > 0 then
-          s := 1
-        else
-          s := -1;
-
-        dX := Abs(dX);
+        VelY := 0.8;
+        AccelY := 0.5;
+        State := STATE_NORMAL;
+      end;
+    end;
+    {$ENDIF}
 
-        for b := 1 to dX do
+    {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
+    // horizontal
+    if (dX <> 0) then
+    begin
+      pan := g_Map_traceToNearest(X, Y, X+dX, Y, (GridTagWall or GridTagDoor or GridTagStep), @ex, @ey);
+      X := ex;
+      // free to ride?
+      if (pan <> nil) then
+      begin
+        // Ñòåíà/äâåðü
+        VelX := 0;
+        VelY := 0;
+        AccelX := 0;
+        AccelY := 0;
+        State := STATE_STICK;
+        justSticked := true;
+        if (dX > 0) then stickDX := 1 else stickDX := -1;
+      end;
+      if (X < 0) or (X >= w) then begin die(); exit; end;
+    end;
+    // vertical
+    if (dY <> 0) then
+    begin
+      if (dY < 0) or not onGround then
+      begin
+        pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep), @ex, @ey);
+        Y := ey;
+        // free to ride?
+        if (pan <> nil) then
         begin
-          if (X+s >= w) or (X+s <= 0) then begin die(); break; end;
-
-          //c := gCollideMap[Y, X+s];
-
-          if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
-          begin // Ñòåíà/äâåðü
-            VelX := 0;
-            VelY := 0;
-            AccelX := 0;
-            AccelY := 0;
+          // Ñòåíà/äâåðü
+          VelX := 0;
+          VelY := 0;
+          AccelX := 0;
+          AccelY := 0;
+          if (dY > 0) and (State <> STATE_STICK) then
+          begin
+            State := STATE_NORMAL;
+          end
+          else
+          begin
             State := STATE_STICK;
-            Break;
+                 if (g_Map_PanelAtPoint(X-1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1
+            else if (g_Map_PanelAtPoint(X+1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1
+            else stickDX := 0;
+            justSticked := true;
           end;
-
-          X := X+s;
         end;
+        onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
       end;
-
-      if dY <> 0 then
+      if (Y < 0) or (Y >= h) then begin die(); exit; end;
+    end;
+    {$ELSE}
+    // horizontal
+    if (dX <> 0) then
+    begin
+      if (dX > 0) then s := 1 else s := -1;
+      dX := Abs(dX);
+      for b := 1 to dX do
       begin
-        if dY > 0 then
-          s := 1
-        else
-          s := -1;
-
-        dY := Abs(dY);
-
-        for b := 1 to dY do
-        begin
-          if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
-
-          //c := gCollideMap[Y+s, X];
-
-          if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
-          begin // Ñòåíà/äâåðü
-            VelX := 0;
-            VelY := 0;
-            AccelX := 0;
-            AccelY := 0;
-            if (s > 0) and (State <> STATE_STICK) then
-              State := STATE_NORMAL
-            else
-              State := STATE_STICK;
-            Break;
-          end;
-
-          Y := Y+s;
+        if (X+s >= w) or (X+s <= 0) then begin die(); break; end;
+        //c := gCollideMap[Y, X+s];
+        if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
+        begin // Ñòåíà/äâåðü
+          VelX := 0;
+          VelY := 0;
+          AccelX := 0;
+          AccelY := 0;
+          State := STATE_STICK;
+          justSticked := true;
+          break;
         end;
+        X := X+s;
       end;
-    end // if gAdvBlood
-  else
+    end;
+    // vertical
+    if (dY <> 0) then
     begin
-      dX := Round(VelX);
-      dY := Round(VelY);
-
-      if (X+dX >= w) or (Y+dY >= h) or
-         (X+dX <= 0) or (Y+dY <= 0) or
-         isBlockedAt(X+dX, Y+dY) {ByteBool(gCollideMap[Y+dY, X+dX] and MARK_BLOCKED)} then
-        begin // Ñòåíà/äâåðü/ãðàíèöà
-          die();
+      if (dY > 0) then s := 1 else s := -1;
+      dY := Abs(dY);
+      for b := 1 to dY do
+      begin
+        if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
+        //c := gCollideMap[Y+s, X];
+        if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
+        begin // Ñòåíà/äâåðü
           VelX := 0;
           VelY := 0;
-        end
-      else
-        begin
-          Y := Y + dY;
-          X := X + dX;
+          AccelX := 0;
+          AccelY := 0;
+          if (s > 0) and (State <> STATE_STICK) then State := STATE_NORMAL else State := STATE_STICK;
+          justSticked := (State = STATE_STICK);
+          break;
         end;
+        Y := Y+s;
+      end;
     end;
+    {$ENDIF}
+  end // if gAdvBlood
+  else
+  begin
+    dX := Round(VelX);
+    dY := Round(VelY);
+    if (X+dX >= w) or (Y+dY >= h) or (X+dX <= 0) or (Y+dY <= 0) or isBlockedAt(X+dX, Y+dY) {ByteBool(gCollideMap[Y+dY, X+dX] and MARK_BLOCKED)} then
+    begin // Ñòåíà/äâåðü/ãðàíèöà
+      die();
+      exit;
+      //VelX := 0;
+      //VelY := 0;
+    end
+    else
+    begin
+      Y += dY;
+      X += dX;
+    end;
+  end;
 
-  VelX := VelX + AccelX;
-  VelY := VelY + AccelY;
+  VelX += AccelX;
+  VelY += AccelY;
 
-// Êðîâü ðàñòâîðÿåòñÿ â æèäêîñòè:
+  // Êðîâü ðàñòâîðÿåòñÿ â æèäêîñòè:
   if isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
   begin
-    Inc(Time);
-
-    Alpha := 255 - Trunc((255.0 * Time) / LiveTime);
+    Time += 1;
+    Alpha := 255-trunc((255.0*Time)/LiveTime);
   end;
 end;
 
 
+
 // ////////////////////////////////////////////////////////////////////////// //
-procedure TParticle.thinkerSpark ();
+procedure TParticle.thinkerWater ();
 var
-  w, h: Integer;
   dX, dY: SmallInt;
+  {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
+  w, h: Integer;
   b: Integer;
   s: ShortInt;
+  {$ELSE}
+  pan: TPanel;
+  ex, ey: Integer;
+  {$ENDIF}
 begin
+  {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
   w := gMapInfo.Width;
   h := gMapInfo.Height;
+  {$ENDIF}
+
+  //TODO: trace wall end when water becomes stick
+  if (State = STATE_STICK) and (Random(30) = 15) then
+  begin // Ñòåêàåò/îòëèïàåò
+    VelY := 0.5;
+    AccelY := 0.15;
+    {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
+    if (not isBlockedAt(X-1, Y) {ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)}) and
+       (not isBlockedAt(X+1, Y) {ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)}) then
+      State := STATE_NORMAL;
+    {$ELSE}
+    if (stickDX = 0) then
+    begin
+      // no walls around, drop
+      State := STATE_NORMAL;
+    end
+    else
+    begin
+      if justSticked then
+      begin
+        if not mapGrid.traceOrthoRayWhileIn(ex, ey, X+stickDX, Y, X+stickDX, mapGrid.gridY0+mapGrid.gridHeight, GridTagWall or GridTagDoor or GridTagStep) then
+        begin
+           // îòëèïëà
+          State := STATE_NORMAL;
+          //e_LogWritefln('juststicked unsticked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s', [X, X+stickDX, stickDX, Y]);
+        end
+        else
+        begin
+          stickEY := ey+1;
+          justSticked := false;
+          if (nil <> g_Map_traceToNearest(X, Y, X, stickEY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then
+          begin
+            if (ey > stickEY) then stickEY := ey-1;
+          end;
+          //e_LogWritefln('juststicked: X=%s; X+stickDX=%s; stickDX=%s; Y=%s; stickEY=%s', [X, X+stickDX, stickDX, Y, stickEY]);
+        end;
+      end
+      else
+      begin
+        if (Y >= stickEY) then State := STATE_NORMAL;
+      end;
+      //if not g_Map_CollidePanel(X-1, Y-1, 3, 3, (PANEL_STEP or PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR))
+    end;
+    {$ENDIF}
+    exit;
+  end;
+
+  {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
+  if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
+  begin
+    if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
+    begin // Ëèôò ââåðõ
+      if VelY > -4-Random(3) then
+        VelY := VelY - 0.8;
+      if Abs(VelX) > 0.1 then
+        VelX := VelX - VelX/10.0;
+      VelX := VelX + (Random-Random)*0.2;
+      AccelY := 0.15;
+    end;
+    if isLiftLeftAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
+    begin // Ïîòîê âëåâî
+      if VelX > -8-Random(3) then
+        VelX := VelX - 0.8;
+      AccelY := 0.15;
+    end;
+    if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
+    begin // Ïîòîê âïðàâî
+      if VelX < 8+Random(3) then
+        VelX := VelX + 0.8;
+      AccelY := 0.15;
+    end;
+  end;
+  {$ELSE}
+  pan := g_Map_PanelAtPoint(X, Y, (GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagLift));
+  if (pan <> nil) then
+  begin
+    if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
+    if ((pan.PanelType and PANEL_LIFTUP) <> 0) then
+    begin
+      if (VelY > -4-Random(3)) then VelY -= 0.8;
+      if (Abs(VelX) > 0.1) then VelX -= VelX/10.0;
+      VelX += (Random-Random)*0.2;
+      AccelY := 0.15;
+    end;
+    if ((pan.PanelType and PANEL_LIFTLEFT) <> 0) then
+    begin
+      if (VelX > -8-Random(3)) then VelX -= 0.8;
+      AccelY := 0.15;
+    end;
+    if ((pan.PanelType and PANEL_LIFTRIGHT) <> 0) then
+    begin
+      if (VelX < 8+Random(3)) then VelX += 0.8;
+      AccelY := 0.15;
+    end;
+  end;
+  {$ENDIF}
 
   dX := Round(VelX);
   dY := Round(VelY);
 
+  {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
+  if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
+  begin
+    if (State <> STATE_STICK) and
+       (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
+       (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
+       (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
+    begin // Âèñèò â âîçäóõå - êàïàåò
+      VelY := 0.8;
+      AccelY := 0.5;
+      State := STATE_NORMAL;
+    end;
+  end;
+  {$ELSE}
+  if (State <> STATE_STICK) and (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
+  begin
+    // Âèñèò â âîçäóõå - êàïàåò
+    if (nil = g_Map_traceToNearest(X, Y-1, X, Y+1, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey)) then
+    begin
+      VelY := 0.8;
+      AccelY := 0.5;
+      State := STATE_NORMAL;
+    end;
+  end;
+  {$ENDIF}
+
+  {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
+  // horizontal
+  if (dX <> 0) then
+  begin
+    pan := g_Map_traceToNearest(X, Y, X+dX, Y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
+    X := ex;
+    // free to ride?
+    if (pan <> nil) then
+    begin
+      // nope
+      if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
+      // Ñòåíà/äâåðü?
+      if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then
+      begin
+        VelX := 0;
+        VelY := 0;
+        AccelX := 0;
+        AccelY := 0;
+        State := STATE_STICK;
+        justSticked := true;
+        if (dX > 0) then stickDX := 1 else stickDX := -1;
+      end;
+    end;
+    if (X < 0) or (X >= gMapInfo.Width) then begin die(); exit; end;
+  end;
+  // vertical
+  if (dY <> 0) then
+  begin
+    if (dY < 0) or not onGround then
+    begin
+      pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
+      Y := ey;
+      // free to ride?
+      if (pan <> nil) then
+      begin
+        // nope
+        if (dY > 0) and ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
+        // Ñòåíà/äâåðü?
+        if ((pan.tag and (GridTagWall or GridTagDoor or GridTagStep)) <> 0) then
+        begin
+          VelX := 0;
+          VelY := 0;
+          AccelX := 0;
+          AccelY := 0;
+          if (dY > 0) and (State <> STATE_STICK) then
+          begin
+            State := STATE_NORMAL;
+          end
+          else
+          begin
+            State := STATE_STICK;
+                 if (g_Map_PanelAtPoint(X-1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := -1
+            else if (g_Map_PanelAtPoint(X+1, Y, (GridTagWall or GridTagDoor or GridTagStep)) <> nil) then stickDX := 1
+            else stickDX := 0;
+            justSticked := true;
+          end;
+        end;
+      end;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+    end;
+    if (Y < 0) or (Y >= gMapInfo.Height) then begin die(); exit; end;
+  end;
+  {$ELSE}
+  // horizontal
+  if (dX <> 0) then
+  begin
+    if (dX > 0) then s := 1 else s := -1;
+    for b := 1 to Abs(dX) do
+    begin
+      // Ñáîêó ãðàíèöà?
+      if (X+s >= w) or (X+s <= 0) then begin die(); break;end;
+      //c := gCollideMap[Y, X+s];
+      // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò?
+      if isLiquidAt(X+s, Y) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
+      if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
+      begin // Ñòåíà/äâåðü
+        VelX := 0;
+        VelY := 0;
+        AccelX := 0;
+        AccelY := 0;
+        State := STATE_STICK;
+        justSticked := true;
+        Break;
+      end;
+      X := X+s;
+    end;
+  end;
+  // vertical
+  if (dY <> 0) then
+  begin
+    if (dY > 0) then s := 1 else s := -1;
+    for b := 1 to Abs(dY) do
+    begin
+      // Ñíèçó/ñâåðõó ãðàíèöà
+      if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
+      //c := gCollideMap[Y+s, X];
+      // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
+      if isLiquidAt(X, Y+s) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
+      if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
+      begin // Ñòåíà/äâåðü
+        VelX := 0;
+        VelY := 0;
+        AccelX := 0;
+        AccelY := 0;
+        if (s > 0) and (State <> STATE_STICK) then State := STATE_NORMAL else State := STATE_STICK;
+        justSticked := (State = STATE_STICK);
+        break;
+      end;
+      Y := Y+s;
+    end;
+  end;
+  {$ENDIF}
+
+  VelX += AccelX;
+  VelY += AccelY;
+
+  Time += 1;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+procedure TParticle.thinkerSpark ();
+var
+  dX, dY: SmallInt;
+  {$IF not DEFINED(D2F_NEW_SPARK_THINKER)}
+  b: Integer;
+  s: ShortInt;
+  {$ELSE}
+  pan: TPanel;
+  ex, ey: Integer;
+  {$ENDIF}
+begin
+  dX := Round(VelX);
+  dY := Round(VelY);
+
+  {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
+  if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
+  begin
+    pan := g_Map_traceToNearest(X, Y-1, X, Y+1, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
+  end;
+  {$ELSE}
   if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) and
      (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
      (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
@@ -556,22 +949,30 @@ begin
     VelY := 0.8;
     AccelY := 0.5;
   end;
+  {$ENDIF}
 
-  if dX <> 0 then
+  if (dX <> 0) then
   begin
-    if dX > 0 then
-      s := 1
-    else
-      s := -1;
-
+    {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
+    pan := g_Map_traceToNearest(X, Y, X+dX, Y, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
+    //e_WriteLog(Format('spark h-trace: (%d,%d)-(%d,%d); dx=%d; end=(%d,%d); hit=%d', [X, Y, X+dX, Y, dX, ex, ey, Integer(pan <> nil)]), MSG_NOTIFY);
+    X := ex;
+    // free to ride?
+    if (pan <> nil) then
+    begin
+      // nope
+      if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
+      VelX := 0;
+      AccelX := 0;
+    end;
+    if (X < 0) or (X >= gMapInfo.Width) then begin die(); exit; end;
+    {$ELSE}
+    if (dX > 0) then s := 1 else s := -1;
     dX := Abs(dX);
-
     for b := 1 to dX do
     begin
-      if (X+s >= w) or (X+s <= 0) then begin die(); break; end;
-
+      if (X+s >= gMapInfo.Width) or (X+s <= 0) then begin die(); break; end;
       //c := gCollideMap[Y, X+s];
-
       if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
         begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
           VelX := 0;
@@ -587,23 +988,51 @@ begin
             break;
           end;
     end;
+    {$ENDIF}
   end;
 
-  if dY <> 0 then
+  if (dY <> 0) then
   begin
-    if dY > 0 then
-      s := 1
-    else
-      s := -1;
-
+    {$IF DEFINED(D2F_NEW_SPARK_THINKER)}
+    if (dY < 0) or not onGround then
+    begin
+      pan := g_Map_traceToNearest(X, Y, X, Y+dY, (GridTagWall or GridTagDoor or GridTagStep or GridTagAcid1 or GridTagAcid2 or GridTagWater), @ex, @ey);
+      Y := ey;
+      {
+      if awaken then
+      begin
+        awaken := false;
+        e_LogWritefln('AWAKEN particle of type %s; justSticked=%s; onGround=%s; VelY=%s; AccelY=%s; Y=%s; ey=%s', [ParticleType, justSticked, onGround, VelY, AccelY, Y, ey]);
+      end;
+      }
+      // free to ride?
+      if (pan <> nil) then
+      begin
+        // nope
+        if ((pan.tag and (GridTagAcid1 or GridTagAcid2 or GridTagWater)) <> 0) then begin die(); exit; end;
+        if (dY < 0) then
+        begin
+          VelY := -VelY;
+          AccelY := abs(AccelY);
+        end
+        else
+        begin
+          VelX := 0;
+          AccelX := 0;
+          VelY := 0;
+          AccelY := 0.8;
+        end;
+      end;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+    end;
+    if (Y < 0) or (Y >= gMapInfo.Height) then begin die(); exit; end;
+    {$ELSE}
+    if (dY > 0) then s := 1 else s := -1;
     dY := Abs(dY);
-
     for b := 1 to dY do
     begin
-      if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
-
+      if (Y+s >= gMapInfo.Height) or (Y+s <= 0) then begin die(); break; end;
       //c := gCollideMap[Y+s, X];
-
       if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
         begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
           if s < 0 then
@@ -630,21 +1059,20 @@ begin
             break;
           end;
     end;
+    {$ENDIF}
   end;
 
-  if VelX <> 0.0 then
-    VelX := VelX + AccelX;
-  if VelY <> 0.0 then
+  if (VelX <> 0.0) then VelX += AccelX;
+
+  if (VelY <> 0.0) then
   begin
-    if AccelY < 10 then
-      AccelY := AccelY + 0.08;
-    VelY := VelY + AccelY;
+    if (AccelY < 10) then AccelY += 0.08;
+    VelY += AccelY;
   end;
 
-  Time := Time + 1;
+  Time += 1;
 end;
 
-
 // ////////////////////////////////////////////////////////////////////////// //
 procedure TParticle.thinkerBubble ();
 var
@@ -689,138 +1117,6 @@ begin
 end;
 
 
-// ////////////////////////////////////////////////////////////////////////// //
-procedure TParticle.thinkerWater ();
-var
-  w, h: Integer;
-  dX, dY: SmallInt;
-  b: Integer;
-  s: ShortInt;
-begin
-  w := gMapInfo.Width;
-  h := gMapInfo.Height;
-
-  if (State = STATE_STICK) and (Random(30) = 15) then
-  begin // Ñòåêàåò/îòëèïàåò
-    VelY := 0.5;
-    AccelY := 0.15;
-    if (not isBlockedAt(X-1, Y) {ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)}) and
-       (not isBlockedAt(X+1, Y) {ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)}) then
-      State := STATE_NORMAL;
-    exit;
-  end;
-
-  if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
-  begin
-    if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
-    begin // Ëèôò ââåðõ
-      if VelY > -4-Random(3) then
-        VelY := VelY - 0.8;
-      if Abs(VelX) > 0.1 then
-        VelX := VelX - VelX/10.0;
-      VelX := VelX + (Random-Random)*0.2;
-      AccelY := 0.15;
-    end;
-    if isLiftLeftAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT)} then
-    begin // Ïîòîê âëåâî
-      if VelX > -8-Random(3) then
-        VelX := VelX - 0.8;
-      AccelY := 0.15;
-    end;
-    if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
-    begin // Ïîòîê âïðàâî
-      if VelX < 8+Random(3) then
-        VelX := VelX + 0.8;
-      AccelY := 0.15;
-    end;
-  end;
-
-  dX := Round(VelX);
-  dY := Round(VelY);
-
-  if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
-    if (State <> STATE_STICK) and
-       (not isBlockedAt(X, Y-1) {ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)}) and
-       (not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)}) and
-       (not isBlockedAt(X, Y+1) {ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)}) then
-    begin // Âèñèò â âîçäóõå - êàïàåò
-      VelY := 0.8;
-      AccelY := 0.5;
-      State := STATE_NORMAL;
-    end;
-
-  if dX <> 0 then
-  begin
-    if dX > 0 then
-      s := 1
-    else
-      s := -1;
-
-    for b := 1 to Abs(dX) do
-    begin
-       // Ñáîêó ãðàíèöà?
-      if (X+s >= w) or (X+s <= 0) then begin die(); break;end;
-
-      //c := gCollideMap[Y, X+s];
-
-      // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò?
-      if isLiquidAt(X+s, Y) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
-
-      if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
-      begin // Ñòåíà/äâåðü
-        VelX := 0;
-        VelY := 0;
-        AccelX := 0;
-        AccelY := 0;
-        State := STATE_STICK;
-        Break;
-      end;
-
-      X := X+s;
-    end;
-  end;
-
-  if dY <> 0 then
-  begin
-    if dY > 0 then
-      s := 1
-    else
-      s := -1;
-
-    for b := 1 to Abs(dY) do
-    begin
-      // Ñíèçó/ñâåðõó ãðàíèöà
-      if (Y+s >= h) or (Y+s <= 0) then begin die(); break; end;
-
-      //c := gCollideMap[Y+s, X];
-
-      // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
-      if isLiquidAt(X, Y+s) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then begin die(); break; end;
-
-      if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
-      begin // Ñòåíà/äâåðü
-        VelX := 0;
-        VelY := 0;
-        AccelX := 0;
-        AccelY := 0;
-        if (s > 0) and (State <> STATE_STICK) then
-          State := STATE_NORMAL
-        else
-          State := STATE_STICK;
-        Break;
-      end;
-
-      Y := Y+s;
-    end;
-  end;
-
-  VelX := VelX + AccelX;
-  VelY := VelY + AccelY;
-
-  Time := Time + 1;
-end;
-
-
 // ////////////////////////////////////////////////////////////////////////// //
 procedure g_GFX_SparkVel (fX, fY: Integer; Count: Word; VX, VY: Integer; DevX, DevY: Byte);
 var
@@ -829,6 +1125,7 @@ var
   DevY1, DevY2: Byte;
   l: Integer;
 begin
+  if not gpart_dbg_enabled then Exit;
   l := Length(Particles);
   if l = 0 then exit;
   if Count > l then Count := l;
@@ -866,8 +1163,9 @@ begin
       Time := 0;
       LiveTime := 30+Random(60);
       ParticleType := PARTICLE_SPARK;
-
-      {CorrectOffsets(CurrentParticle);}
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
     end;
 
     if CurrentParticle+2 > MaxParticles then
@@ -888,6 +1186,7 @@ var
   CRnd: Byte;
   CC: SmallInt;
 begin
+  if not gpart_dbg_enabled then Exit;
   if Kind = BLOOD_SPARKS then
   begin
     g_GFX_SparkVel(fX, fY, 2 + Random(2), -VX div 2, -VY div 2, DevX, DevY);
@@ -963,8 +1262,10 @@ begin
       Time := 0;
       LiveTime := 120+Random(40);
       ParticleType := PARTICLE_BLOOD;
-
-      {CorrectOffsets(CurrentParticle);}
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
+      //stickEY := 0;
     end;
 
     if CurrentParticle >= MaxParticles-1 then
@@ -984,6 +1285,7 @@ var
   BaseVelX, BaseVelY: Single;
   l: Integer;
 begin
+  if not gpart_dbg_enabled then Exit;
   l := Length(Particles);
   if l = 0 then
     Exit;
@@ -1025,8 +1327,9 @@ begin
       Time := 0;
       LiveTime := 30+Random(60);
       ParticleType := PARTICLE_SPARK;
-
-      {CorrectOffsets(CurrentParticle);}
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
     end;
 
     if CurrentParticle+2 > MaxParticles then
@@ -1043,6 +1346,7 @@ var
   DevY1, DevY2: Byte;
   l: Integer;
 begin
+  if not gpart_dbg_enabled then Exit;
   l := Length(Particles);
   if l = 0 then
     Exit;
@@ -1107,8 +1411,9 @@ begin
       Time := 0;
       LiveTime := 60+Random(60);
       ParticleType := PARTICLE_WATER;
-
-      {CorrectOffsets(CurrentParticle);}
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
     end;
 
     if CurrentParticle+2 > MaxParticles then
@@ -1123,6 +1428,7 @@ var
   a: Integer;
   l: Integer;
 begin
+  if not gpart_dbg_enabled then Exit;
   l := Length(Particles);
   if l = 0 then
     Exit;
@@ -1192,8 +1498,9 @@ begin
       Time := 0;
       LiveTime := 60+Random(60);
       ParticleType := PARTICLE_WATER;
-
-      {CorrectOffsets(CurrentParticle);}
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
     end;
 
     if CurrentParticle+2 > MaxParticles then
@@ -1204,13 +1511,19 @@ begin
 end;
 
 
+{.$DEFINE D2F_DEBUG_BUBBLES}
 procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
 var
   a: Integer;
   DevX1, DevX2,
   DevY1, DevY2: Byte;
   l, liquidx: Integer;
+  {$IF DEFINED(D2F_DEBUG_BUBBLES)}
+  stt: UInt64;
+  nptr, ptr: Boolean;
+  {$ENDIF}
 begin
+  if not gpart_dbg_enabled then Exit;
   l := Length(Particles);
   if l = 0 then
     Exit;
@@ -1241,7 +1554,21 @@ begin
 
       // trace liquid, so we'll know where it ends; do it in 8px steps for speed
       // tracer will return `false` if we started outside of the liquid
-      if not g_Map_TraceLiquid(X, Y, 0, -8, liquidx, liquidTopY) then continue;
+
+      {$IF DEFINED(D2F_DEBUG_BUBBLES)}
+      stt := curTimeMicro();
+      ptr := mapGrid.traceOrthoRayWhileIn(liquidx, liquidTopY, X, Y, X, 0, GridTagWater or GridTagAcid1 or GridTagAcid2);
+      stt := curTimeMicro()-stt;
+      e_LogWritefln('traceOrthoRayWhileIn: time=%s (%s); liquidTopY=%s', [Integer(stt), ptr, liquidTopY]);
+      //
+      stt := curTimeMicro();
+      nptr := g_Map_TraceLiquidNonPrecise(X, Y, 0, -8, liquidx, liquidTopY);
+      stt := curTimeMicro()-stt;
+      e_LogWritefln('g_Map_TraceLiquidNonPrecise: time=%s (%s); liquidTopY=%s', [Integer(stt), nptr, liquidTopY]);
+      if not nptr then continue;
+      {$ELSE}
+      if not g_Map_TraceLiquidNonPrecise(X, Y, 0, -8, liquidx, liquidTopY) then continue;
+      {$ENDIF}
 
       VelX := 0;
       VelY := -1-Random;
@@ -1257,8 +1584,9 @@ begin
       Time := 0;
       LiveTime := 65535;
       ParticleType := PARTICLE_BUBBLES;
-
-      {CorrectOffsets(CurrentParticle);}
+      justSticked := false;
+      onGround := (VelY >= 0) and g_Map_HasAnyPanelAtPoint(X, Y+1, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+      awaken := false;
     end;
 
     if CurrentParticle+2 > MaxParticles then
@@ -1315,6 +1643,7 @@ procedure g_GFX_OnceAnim(X, Y: Integer; Anim: TAnimation; AnimType: Byte = 0);
 var
   find_id: DWORD;
 begin
+  if not gpart_dbg_enabled then Exit;
   if Anim = nil then
     Exit;
 
@@ -1353,7 +1682,6 @@ begin
           //if not alive then Continue;
           //e_WriteLog(Format('particle #%d: %d', [State, ParticleType]), MSG_NOTIFY);
           think();
-          {CorrectOffsets(a);}
         end; // with
       end; // if
     end; // for