DEADSOFTWARE

restored particles
authorKetmar Dark <ketmar@ketmar.no-ip.org>
Mon, 21 Aug 2017 20:56:25 +0000 (23:56 +0300)
committerKetmar Dark <ketmar@ketmar.no-ip.org>
Mon, 21 Aug 2017 21:02:16 +0000 (00:02 +0300)
src/game/g_gfx.pas
src/game/g_map.pas

index 37036278d08175ca4dbf3a93c88e724ac32995f7..fe494a8cd539128405cc5b12c4ef78a1ac65f496 100644 (file)
@@ -74,6 +74,7 @@ uses
   g_game, g_language, g_net;
 
 type
+  PParticle = ^TParticle;
   TParticle = record
     X, Y:               Integer;
     VelX, VelY:         Single;
@@ -107,6 +108,53 @@ var
   MaxParticles: Integer;
   CurrentParticle: Integer;
 
+
+function isBlockedAt (x, y: Integer; w: Integer=1; h: Integer=1): Boolean; inline;
+begin
+  result := g_Map_CollidePanel(x, y, w, h, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_STEP));
+end;
+
+
+// ???
+function isWallAt (x, y: Integer; w: Integer=1; h: Integer=1): Boolean; inline;
+begin
+  result := g_Map_CollidePanel(x, y, w, h, (PANEL_WALL or PANEL_STEP));
+end;
+
+
+function isLiftUpAt (x, y: Integer; w: Integer=1; h: Integer=1): Boolean; inline;
+begin
+  result := g_Map_CollidePanel(x, y, w, h, PANEL_LIFTUP);
+end;
+
+function isLiftDownAt (x, y: Integer; w: Integer=1; h: Integer=1): Boolean; inline;
+begin
+  result := g_Map_CollidePanel(x, y, w, h, PANEL_LIFTDOWN);
+end;
+
+function isLiftLeftAt (x, y: Integer; w: Integer=1; h: Integer=1): Boolean; inline;
+begin
+  result := g_Map_CollidePanel(x, y, w, h, PANEL_LIFTLEFT);
+end;
+
+function isLiftRightAt (x, y: Integer; w: Integer=1; h: Integer=1): Boolean; inline;
+begin
+  result := g_Map_CollidePanel(x, y, w, h, PANEL_LIFTRIGHT);
+end;
+
+
+function isLiquidAt (x, y: Integer; w: Integer=1; h: Integer=1): Boolean; inline;
+begin
+  result := g_Map_CollidePanel(x, y, w, h, (PANEL_WATER or PANEL_ACID1 or PANEL_ACID2));
+end;
+
+
+function isAnythingAt (x, y: Integer; w: Integer=1; h: Integer=1): Boolean; inline;
+begin
+  result := g_Map_CollidePanel(x, y, w, h, (PANEL_WALL or PANEL_CLOSEDOOR or PANEL_OPENDOOR or PANEL_WATER or PANEL_ACID1 or PANEL_ACID2 or PANEL_STEP or PANEL_LIFTUP or PANEL_LIFTDOWN or PANEL_LIFTLEFT or PANEL_LIFTRIGHT));
+end;
+
+
 procedure g_Mark(x, y, Width, Height: Integer; t: Byte; st: Boolean);
 {$IF DEFINED(HAS_COLLIDE_BITMAP)}
 var
@@ -267,9 +315,13 @@ begin
 end;
 
 
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
 procedure CorrectOffsets(id: Integer);
+{$IF not DEFINED(HAS_COLLIDE_BITMAP)}
+var
+  part: PParticle;
+{$ENDIF}
 begin
+{$IF DEFINED(HAS_COLLIDE_BITMAP)}
   with Particles[id] do
   begin
     if (X >= 0) and (Y > 0) and
@@ -286,25 +338,28 @@ begin
     else
       offsetX := 0;
   end;
-end;
+{$ELSE}
+  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;
 {$ENDIF}
+end;
 
 
-procedure g_GFX_SparkVel(fX, fY: Integer; Count: Word; VX, VY: Integer; DevX, DevY: Byte);
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
+procedure g_GFX_SparkVel (fX, fY: Integer; Count: Word; VX, VY: Integer; DevX, DevY: Byte);
 var
   a: Integer;
   DevX1, DevX2,
   DevY1, DevY2: Byte;
   l: Integer;
-{$ENDIF}
 begin
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
   l := Length(Particles);
-  if l = 0 then
-    Exit;
-  if Count > l then
-    Count := l;
+  if l = 0 then exit;
+  if Count > l then Count := l;
 
   DevX1 := DevX div 2;
   DevX2 := DevX + 1;
@@ -348,12 +403,11 @@ begin
     else
       CurrentParticle := CurrentParticle+1;
   end;
-{$ENDIF}
 end;
 
+
 procedure g_GFX_Blood(fX, fY: Integer; Count: Word; vx, vy: Integer;
   DevX, DevY: Word; CR, CG, CB: Byte; Kind: Byte = BLOOD_NORMAL);
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
 var
   a: Integer;
   DevX1, DevX2,
@@ -361,9 +415,7 @@ var
   l: Integer;
   CRnd: Byte;
   CC: SmallInt;
-{$ENDIF}
 begin
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
   if Kind = BLOOD_SPARKS then
   begin
     g_GFX_SparkVel(fX, fY, 2 + Random(2), -VX div 2, -VY div 2, DevX, DevY);
@@ -387,10 +439,13 @@ begin
       X := fX - DevX1 + Random(DevX2);
       Y := fY - DevY1 + Random(DevY2);
 
+      {
       if (X < 0) or (X > gMapInfo.Width-1) or
          (Y < 0) or (Y > gMapInfo.Height-1) or
          ByteBool(gCollideMap[Y, X] and MARK_WALL) then
         Continue;
+      }
+      if isWallAt(X, Y) then continue;
 
       VelX := vx + (Random-Random)*3;
       VelY := vy + (Random-Random)*3;
@@ -445,11 +500,10 @@ begin
     else
       CurrentParticle := CurrentParticle+1;
   end;
-{$ENDIF}
 end;
 
+
 procedure g_GFX_Spark(fX, fY: Integer; Count: Word; Angle: SmallInt; DevX, DevY: Byte);
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
 var
   a: Integer;
   b: Single;
@@ -457,9 +511,7 @@ var
   DevY1, DevY2: Byte;
   BaseVelX, BaseVelY: Single;
   l: Integer;
-{$ENDIF}
 begin
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
   l := Length(Particles);
   if l = 0 then
     Exit;
@@ -510,19 +562,15 @@ begin
     else
       CurrentParticle := CurrentParticle+1;
   end;
-{$ENDIF}
 end;
 
 procedure g_GFX_Water(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DevX, DevY, Color: Byte);
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
 var
   a: Integer;
   DevX1, DevX2,
   DevY1, DevY2: Byte;
   l: Integer;
-{$ENDIF}
 begin
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
   l := Length(Particles);
   if l = 0 then
     Exit;
@@ -596,17 +644,13 @@ begin
     else
       CurrentParticle := CurrentParticle+1;
   end;
-{$ENDIF}
 end;
 
 procedure g_GFX_SimpleWater(fX, fY: Integer; Count: Word; fVelX, fVelY: Single; DefColor, CR, CG, CB: Byte);
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
 var
   a: Integer;
   l: Integer;
-{$ENDIF}
 begin
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
   l := Length(Particles);
   if l = 0 then
     Exit;
@@ -685,19 +729,15 @@ begin
     else
       CurrentParticle := CurrentParticle+1;
   end;
-{$ENDIF}
 end;
 
 procedure g_GFX_Bubbles(fX, fY: Integer; Count: Word; DevX, DevY: Byte);
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
 var
   a: Integer;
   DevX1, DevX2,
   DevY1, DevY2: Byte;
   l: Integer;
-{$ENDIF}
 begin
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
   l := Length(Particles);
   if l = 0 then
     Exit;
@@ -720,7 +760,7 @@ begin
          (Y >= gMapInfo.Height) or (Y <= 0) then
         Continue;
 
-      if not ByteBool(gCollideMap[Y, X] and MARK_LIQUID) then
+      if not isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
         Continue;
 
       VelX := 0;
@@ -746,7 +786,6 @@ begin
     else
       CurrentParticle := CurrentParticle+1;
   end;
-{$ENDIF}
 end;
 
 procedure g_GFX_SetMax(Count: Integer);
@@ -809,15 +848,12 @@ end;
 procedure g_GFX_Update();
 var
   a: Integer;
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
   w, h: Integer;
   dX, dY: SmallInt;
   b, len: Integer;
   s: ShortInt;
-  c: Byte;
-{$ENDIF}
+  //c: Byte;
 begin
-{$IF DEFINED(HAS_COLLIDE_BITMAP)}
   if Particles <> nil then
   begin
     w := gMapInfo.Width;
@@ -842,10 +878,18 @@ begin
               if gAdvBlood 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
+                       (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;
@@ -859,9 +903,9 @@ begin
                         Continue;
                       end;
 
-                  if not ByteBool(gCollideMap[Y, X] and MARK_BLOCKED) then
+                  if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
                   begin
-                    if ByteBool(gCollideMap[Y, X] and MARK_LIFTUP) then
+                    if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
                     begin // Ëèôò ââåðõ
                       if VelY > -4-Random(3) then
                         VelY := VelY - 0.8;
@@ -870,13 +914,13 @@ begin
                       VelX := VelX + (Random-Random)*0.2;
                       AccelY := 0.15;
                     end;
-                    if ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT) then
+                    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 ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT) then
+                    if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
                     begin // Ïîòîê âïðàâî
                       if VelX < 8+Random(3) then
                         VelX := VelX + 0.8;
@@ -889,9 +933,9 @@ begin
 
                   if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
                     if (State <> STATE_STICK) and
-                       (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
-                       (not ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)) and
-                       (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) then
+                       (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;
@@ -915,9 +959,9 @@ begin
                         Break;
                       end;
 
-                      c := gCollideMap[Y, X+s];
+                      //c := gCollideMap[Y, X+s];
 
-                      if ByteBool(c and MARK_BLOCKED) then
+                      if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
                       begin // Ñòåíà/äâåðü
                         VelX := 0;
                         VelY := 0;
@@ -948,9 +992,9 @@ begin
                         Break;
                       end;
 
-                      c := gCollideMap[Y+s, X];
+                      //c := gCollideMap[Y+s, X];
 
-                      if ByteBool(c and MARK_BLOCKED) then
+                      if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
                       begin // Ñòåíà/äâåðü
                         VelX := 0;
                         VelY := 0;
@@ -974,7 +1018,7 @@ begin
 
                   if (X+dX >= w) or (Y+dY >= h) or
                      (X+dX <= 0) or (Y+dY <= 0) or
-                     ByteBool(gCollideMap[Y+dY, X+dX] and MARK_BLOCKED) then
+                     isBlockedAt(X+dX, Y+dY) {ByteBool(gCollideMap[Y+dY, X+dX] and MARK_BLOCKED)} then
                     begin // Ñòåíà/äâåðü/ãðàíèöà
                       State := STATE_FREE;
                       VelX := 0;
@@ -991,7 +1035,7 @@ begin
               VelY := VelY + AccelY;
 
             // Êðîâü ðàñòâîðÿåòñÿ â æèäêîñòè:
-              if ByteBool(gCollideMap[Y, X] and MARK_LIQUID) then
+              if isLiquidAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIQUID)} then
               begin
                 Inc(Time);
 
@@ -1005,9 +1049,9 @@ begin
               dY := Round(VelY);
 
               if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) and
-                 (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
-                 (not ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)) and
-                 (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) then
+                 (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;
@@ -1030,16 +1074,16 @@ begin
                     Break;
                   end;
 
-                  c := gCollideMap[Y, X+s];
+                  //c := gCollideMap[Y, X+s];
 
-                  if ByteBool(c and MARK_BLOCKED) then
+                  if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
                     begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
                       VelX := 0;
                       AccelX := 0;
                       Break;
                     end
                   else // Ïóñòî:
-                    if c = MARK_FREE then
+                    if not isAnythingAt(X+s, Y) {c = MARK_FREE} then
                       X := X + s
                     else // Îñòàëüíîå:
                       begin
@@ -1066,9 +1110,9 @@ begin
                     Break;
                   end;
 
-                  c := gCollideMap[Y+s, X];
+                  //c := gCollideMap[Y+s, X];
 
-                  if ByteBool(c and MARK_BLOCKED) then
+                  if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
                     begin // Ñòåíà/äâåðü - ïàäàåò âåðòèêàëüíî
                       if s < 0 then
                         begin
@@ -1086,7 +1130,7 @@ begin
                       Break;
                     end
                   else // Ïóñòî:
-                    if c = MARK_FREE then
+                    if not isAnythingAt(X, Y+s) {c = MARK_FREE} then
                       Y := Y + s
                     else // Îñàëüíîå:
                       begin
@@ -1114,15 +1158,15 @@ begin
               begin // Ñòåêàåò/îòëèïàåò
                 VelY := 0.5;
                 AccelY := 0.15;
-                if (not ByteBool(gCollideMap[Y, X-1] and MARK_BLOCKED)) and
-                   (not ByteBool(gCollideMap[Y, X+1] and MARK_BLOCKED)) then
+                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;
                 Continue;
               end;
 
-              if not ByteBool(gCollideMap[Y, X] and MARK_BLOCKED) then
+              if not isBlockedAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)} then
               begin
-                if ByteBool(gCollideMap[Y, X] and MARK_LIFTUP) then
+                if isLiftUpAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTUP)} then
                 begin // Ëèôò ââåðõ
                   if VelY > -4-Random(3) then
                     VelY := VelY - 0.8;
@@ -1131,13 +1175,13 @@ begin
                   VelX := VelX + (Random-Random)*0.2;
                   AccelY := 0.15;
                 end;
-                if ByteBool(gCollideMap[Y, X] and MARK_LIFTLEFT) then
+                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 ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT) then
+                if isLiftRightAt(X, Y) {ByteBool(gCollideMap[Y, X] and MARK_LIFTRIGHT)} then
                 begin // Ïîòîê âïðàâî
                   if VelX < 8+Random(3) then
                     VelX := VelX + 0.8;
@@ -1150,9 +1194,9 @@ begin
 
               if (Abs(VelX) < 0.1) and (Abs(VelY) < 0.1) then
                 if (State <> STATE_STICK) and
-                   (not ByteBool(gCollideMap[Y-1, X] and MARK_BLOCKED)) and
-                   (not ByteBool(gCollideMap[Y, X] and MARK_BLOCKED)) and
-                   (not ByteBool(gCollideMap[Y+1, X] and MARK_BLOCKED)) then
+                   (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;
@@ -1174,15 +1218,15 @@ begin
                     Break;
                   end;
 
-                  c := gCollideMap[Y, X+s];
+                  //c := gCollideMap[Y, X+s];
 
-                  if ByteBool(c and MARK_LIQUID) and (dY > 0) then
+                  if isLiquidAt(X+s, Y) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then
                   begin // Ñáîêó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
                     State := STATE_FREE;
                     Break;
                   end;
 
-                  if ByteBool(c and MARK_BLOCKED) then
+                  if isBlockedAt(X+s, Y) {ByteBool(c and MARK_BLOCKED)} then
                   begin // Ñòåíà/äâåðü
                     VelX := 0;
                     VelY := 0;
@@ -1211,15 +1255,15 @@ begin
                     Break;
                   end;
 
-                  c := gCollideMap[Y+s, X];
+                  //c := gCollideMap[Y+s, X];
 
-                  if ByteBool(c and MARK_LIQUID) and (dY > 0) then
+                  if isLiquidAt(X, Y+s) {ByteBool(c and MARK_LIQUID)} and (dY > 0) then
                   begin // Ñíèçó æèäêîñòü, à ÷àñòèöà óæå ïàäàåò
                     State := STATE_FREE;
                     Break;
                   end;
 
-                  if ByteBool(c and MARK_BLOCKED) then
+                  if isBlockedAt(X, Y+s) {ByteBool(c and MARK_BLOCKED)} then
                   begin // Ñòåíà/äâåðü
                     VelX := 0;
                     VelY := 0;
@@ -1261,7 +1305,7 @@ begin
                     Break;
                   end;
 
-                  if not ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID) then
+                  if not isLiquidAt(X, Y+s) {ByteBool(gCollideMap[Y+s, X] and MARK_LIQUID)} then
                   begin // Óæå íå æèäêîñòü
                     State := STATE_FREE;
                     Break;
@@ -1281,7 +1325,6 @@ begin
           CorrectOffsets(a);
         end;
   end; // Particles <> nil
-{$ENDIF}
 
   if OnceAnims <> nil then
   begin
index a73434b9b8c6632bd41dbe63a194dd5376885def..cc6d9d2715d304e6b89c553117e3cf745445a540 100644 (file)
@@ -67,7 +67,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,6 +91,15 @@ 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): Integer;
+
+type
+  TForEachPanelCB = function (pan: TPanel): Boolean; // return `true` to stop
+
+function g_Map_ForEachPanelAt (x, y: Integer; cb: TForEachPanelCB; panelType: Word): Boolean;
+
+
 procedure g_Map_ProfilersBegin ();
 procedure g_Map_ProfilersEnd ();
 
@@ -172,8 +181,6 @@ var
 
 function panelTypeToTag (panelType: Word): Integer; // returns GridTagXXX
 
-function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): Integer;
-
 
 implementation
 
@@ -286,8 +293,6 @@ end;
 
 // wall index in `gWalls` or -1
 function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil; hity: PInteger=nil): Integer;
-var
-  maxDistSq: Single;
 
   function sqchecker (pan: TPanel; var ray: Ray2D): Single;
   var
@@ -315,12 +320,13 @@ var
   ray: Ray2D;
   hxf, hyf: Single;
   hx, hy: Integer;
+  maxDistSq: Single;
 begin
   result := -1;
   if (mapTree = nil) then exit;
-  maxDistSq := (x1-x0)*(x1-x0)+(y1-y0)*(y1-y0);
   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;
@@ -352,6 +358,55 @@ begin
 end;
 
 
+function g_Map_ForEachPanelAt (x, y: Integer; cb: TForEachPanelCB; 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;
+    end;
+
+    if ((tag and GridTagLift) <> 0) then
+    begin
+      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))) and
+         (x >= pan.X) and (y >= pan.Y) and (x < pan.X+pan.Width) and (y < pan.Y+pan.Height);
+      if result then result := cb(pan);;
+      exit;
+    end;
+
+    // other shit
+    result := (x >= pan.X) and (y >= pan.Y) and (x < pan.X+pan.Width) and (y < pan.Y+pan.Height);
+    if result then result := cb(pan);
+  end;
+
+var
+  tagmask: Integer = 0;
+begin
+  result := false;
+  if not assigned(cb) then exit;
+  //if (mapTree = nil) then exit;
+  //function TDynAABBTreeBase.pointQuery (ax, ay: TreeNumber; cb: TQueryOverlapCB; tagmask: Integer=-1): TTreeFlesh;
+
+  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
+  result := gMapGrid.forEachInAABB(x, y, 1, 1, checker, tagmask);
+end;
+
+
 function g_Map_IsSpecialTexture(Texture: String): Boolean;
 begin
   Result := (Texture = TEXTURE_NAME_WATER) or
@@ -2049,7 +2104,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