DEADSOFTWARE

slightly better mplat rendering (no support for NPOT yet)
[d2df-sdl.git] / src / game / g_panel.pas
index b522ae2beb5c49b38a31e602f7227bd238f2ae3a..1ae4b9e89556b650af90931c02c4749f5b74651d 100644 (file)
@@ -1,9 +1,26 @@
+(* Copyright (C)  DooM 2D:Forever Developers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *)
+{$INCLUDE ../shared/a_modes.inc}
+{$M+}
 unit g_panel;
 
 interface
 
 uses
-  MAPSTRUCT, BinEditor, g_textures;
+  MAPDEF, BinEditor, g_textures, xdynrec;
 
 type
   TAddTextureArray = Array of
@@ -14,6 +31,9 @@ type
 
   TPanel = Class (TObject)
   private
+    const
+  private
+    mGUID: Integer; // will be assigned in "g_map.pas"
     FTextureWidth:    Word;
     FTextureHeight:   Word;
     FAlpha:           Byte;
@@ -25,26 +45,66 @@ type
                             True:  (AnTex: TAnimation);
                         end;
 
+    mMovingSpeed: TDFPoint;
+    mMovingStart: TDFPoint;
+    mMovingEnd: TDFPoint;
+    mMovingActive: Boolean;
+    mMoveOnce: Boolean;
+
+    mSizeSpeed: TDFSize;
+    mSizeEnd: TDFSize;
+
+    mEndPosTrig: Integer;
+    mEndSizeTrig: Integer;
+
+  private
+    function getx1 (): Integer; inline;
+    function gety1 (): Integer; inline;
+    function getvisvalid (): Boolean; inline;
+
+    function getMovingSpeedX (): Integer; inline;
+    procedure setMovingSpeedX (v: Integer); inline;
+    function getMovingSpeedY (): Integer; inline;
+    procedure setMovingSpeedY (v: Integer); inline;
+
+    function getMovingStartX (): Integer; inline;
+    procedure setMovingStartX (v: Integer); inline;
+    function getMovingStartY (): Integer; inline;
+    procedure setMovingStartY (v: Integer); inline;
+
+    function getMovingEndX (): Integer; inline;
+    procedure setMovingEndX (v: Integer); inline;
+    function getMovingEndY (): Integer; inline;
+    procedure setMovingEndY (v: Integer); inline;
+
   public
     FCurTexture:      Integer; // Íîìåð òåêóùåé òåêñòóðû
     FCurFrame:        Integer;
     FCurFrameCount:   Byte;
-    X, Y:             Integer;
-    Width, Height:    Word;
-    PanelType:        Word;
-    SaveIt:           Boolean; // Ñîõðàíÿòü ïðè SaveState?
-    Enabled:          Boolean;
-    Door:             Boolean;
-    LiftType:         Byte;
-    LastAnimLoop:     Byte;
-
-    constructor Create(PanelRec: TPanelRec_1;
+    FX, FY:           Integer;
+    FWidth, FHeight:  Word;
+    FPanelType:       Word;
+    FSaveIt:          Boolean; // Ñîõðàíÿòü ïðè SaveState?
+    FEnabled:         Boolean;
+    FDoor:            Boolean;
+    FMoved:           Boolean;
+    FLiftType:        Byte;
+    FLastAnimLoop:    Byte;
+    // sorry, there fields are public to allow setting 'em in g_map; this should be fixed later
+    // for now, PLEASE, don't modify 'em, or all hell will break loose
+    arrIdx:           Integer; // index in one of internal arrays; sorry
+    tag:              Integer; // used in coldets and such; sorry; see g_map.GridTagXXX
+    proxyId:          Integer; // proxy id in map grid (DO NOT USE!)
+    mapId:            AnsiString; // taken directly from map file; dunno why it is here
+
+    constructor Create(PanelRec: TDynRecord;
                        AddTextures: TAddTextureArray;
                        CurTex: Integer;
-                       var Textures: TLevelTextureArray);
+                       var Textures: TLevelTextureArray; aguid: Integer);
     destructor  Destroy(); override;
 
     procedure   Draw();
+    procedure   DrawShadowVolume(lightX: Integer; lightY: Integer; radius: Integer);
     procedure   Update();
     procedure   SetFrame(Frame: Integer; Count: Byte);
     procedure   NextTexture(AnimLoop: Byte = 0);
@@ -54,25 +114,90 @@ type
 
     procedure   SaveState(var Mem: TBinMemoryWriter);
     procedure   LoadState(var Mem: TBinMemoryReader);
+
+    procedure positionChanged (); inline;
+
+    function getIsGBack (): Boolean; inline; // gRenderBackgrounds
+    function getIsGStep (): Boolean; inline; // gSteps
+    function getIsGWall (): Boolean; inline; // gWalls
+    function getIsGAcid1 (): Boolean; inline; // gAcid1
+    function getIsGAcid2 (): Boolean; inline; // gAcid2
+    function getIsGWater (): Boolean; inline; // gWater
+    function getIsGFore (): Boolean; inline; // gRenderForegrounds
+    function getIsGLift (): Boolean; inline; // gLifts
+    function getIsGBlockMon (): Boolean; inline; // gBlockMon
+
+  public
+    property visvalid: Boolean read getvisvalid; // panel is "visvalid" when it's width and height are positive
+
+  published
+    property guid: Integer read mGUID; // will be assigned in "g_map.pas"
+    property x0: Integer read FX;
+    property y0: Integer read FY;
+    property x1: Integer read getx1; // inclusive!
+    property y1: Integer read gety1; // inclusive!
+    property x: Integer read FX write FX;
+    property y: Integer read FY write FY;
+    property width: Word read FWidth write FWidth;
+    property height: Word read FHeight write FHeight;
+    property panelType: Word read FPanelType write FPanelType;
+    property saveIt: Boolean read FSaveIt write FSaveIt; // Ñîõðàíÿòü ïðè SaveState?
+    property enabled: Boolean read FEnabled write FEnabled; // Ñîõðàíÿòü ïðè SaveState?
+    property door: Boolean read FDoor write FDoor; // Ñîõðàíÿòü ïðè SaveState?
+    property moved: Boolean read FMoved write FMoved; // Ñîõðàíÿòü ïðè SaveState?
+    property liftType: Byte read FLiftType write FLiftType; // Ñîõðàíÿòü ïðè SaveState?
+    property lastAnimLoop: Byte read FLastAnimLoop write FLastAnimLoop; // Ñîõðàíÿòü ïðè SaveState?
+
+    property movingSpeedX: Integer read getMovingSpeedX write setMovingSpeedX;
+    property movingSpeedY: Integer read getMovingSpeedY write setMovingSpeedY;
+    property movingStartX: Integer read getMovingStartX write setMovingStartX;
+    property movingStartY: Integer read getMovingStartY write setMovingStartY;
+    property movingEndX: Integer read getMovingEndX write setMovingEndX;
+    property movingEndY: Integer read getMovingEndY write setMovingEndY;
+    property movingActive: Boolean read mMovingActive write mMovingActive;
+    property moveOnce: Boolean read mMoveOnce write mMoveOnce;
+
+    property isGBack: Boolean read getIsGBack;
+    property isGStep: Boolean read getIsGStep;
+    property isGWall: Boolean read getIsGWall;
+    property isGAcid1: Boolean read getIsGAcid1;
+    property isGAcid2: Boolean read getIsGAcid2;
+    property isGWater: Boolean read getIsGWater;
+    property isGFore: Boolean read getIsGFore;
+    property isGLift: Boolean read getIsGLift;
+    property isGBlockMon: Boolean read getIsGBlockMon;
+
+  public
+    property movingSpeed: TDFPoint read mMovingSpeed write mMovingSpeed;
+    property movingStart: TDFPoint read mMovingStart write mMovingStart;
+    property movingEnd: TDFPoint read mMovingEnd write mMovingEnd;
+
+    property endPosTrigId: Integer read mEndPosTrig write mEndPosTrig;
+    property endSizeTrigId: Integer read mEndSizeTrig write mEndSizeTrig;
   end;
 
   TPanelArray = Array of TPanel;
 
+var
+  g_dbgpan_mplat_active: Boolean = {$IF DEFINED(D2F_DEBUG)}true{$ELSE}true{$ENDIF};
+  g_dbgpan_mplat_step: Boolean = false; // one step, and stop
+
+
 implementation
 
 uses
-  g_basic, g_map, MAPDEF, g_game, e_graphics,
-  g_console, g_language;
+  SysUtils, g_basic, g_map, g_game, g_gfx, e_graphics, g_weapons, g_triggers,
+  g_console, g_language, g_monsters, g_player, g_grid, e_log, GL, utils;
 
 const
   PANEL_SIGNATURE = $4C4E4150; // 'PANL'
 
 { T P a n e l : }
 
-constructor TPanel.Create(PanelRec: TPanelRec_1;
+constructor TPanel.Create(PanelRec: TDynRecord;
                           AddTextures: TAddTextureArray;
                           CurTex: Integer;
-                          var Textures: TLevelTextureArray);
+                          var Textures: TLevelTextureArray; aguid: Integer);
 var
   i: Integer;
 begin
@@ -85,6 +210,22 @@ begin
   FCurFrame := 0;
   FCurFrameCount := 0;
   LastAnimLoop := 0;
+  Moved := False;
+
+  mapId := PanelRec.id;
+  mGUID := aguid;
+
+  mMovingSpeed := PanelRec.moveSpeed;
+  mMovingStart := PanelRec.moveStart;
+  mMovingEnd := PanelRec.moveEnd;
+  mMovingActive := PanelRec['move_active'].varvalue;
+  mMoveOnce := PanelRec.moveOnce;
+
+  mSizeSpeed := PanelRec.sizeSpeed;
+  mSizeEnd := PanelRec.sizeEnd;
+
+  mEndPosTrig := PanelRec.endPosTrig;
+  mEndSizeTrig := PanelRec.endSizeTrig;
 
 // Òèï ïàíåëè:
   PanelType := PanelRec.PanelType;
@@ -153,11 +294,11 @@ begin
 
     case PanelRec.PanelType of
       PANEL_WATER:
-        FTextureIDs[0].Tex := TEXTURE_SPECIAL_WATER;
+        FTextureIDs[0].Tex := LongWord(TEXTURE_SPECIAL_WATER);
       PANEL_ACID1:
-        FTextureIDs[0].Tex := TEXTURE_SPECIAL_ACID1;
+        FTextureIDs[0].Tex := LongWord(TEXTURE_SPECIAL_ACID1);
       PANEL_ACID2:
-        FTextureIDs[0].Tex := TEXTURE_SPECIAL_ACID2;
+        FTextureIDs[0].Tex := LongWord(TEXTURE_SPECIAL_ACID2);
     end;
 
     FCurTexture := 0;
@@ -197,7 +338,15 @@ begin
     SaveIt := True;
 
 // Åñëè íå ñïåöòåêñòóðà, òî çàäàåì ðàçìåðû:
-  if not g_Map_IsSpecialTexture(Textures[PanelRec.TextureNum].TextureName) then
+  if PanelRec.TextureNum > High(Textures) then
+  begin
+    e_WriteLog(Format('WTF?! PanelRec.TextureNum is out of limits! (%d : %d)', [PanelRec.TextureNum, High(Textures)]), MSG_FATALERROR);
+    FTextureWidth := 2;
+    FTextureHeight := 2;
+    FAlpha := 0;
+    FBlending := ByteBool(0);
+  end
+  else if not g_Map_IsSpecialTexture(Textures[PanelRec.TextureNum].TextureName) then
   begin
     FTextureWidth := Textures[PanelRec.TextureNum].Width;
     FTextureHeight := Textures[PanelRec.TextureNum].Height;
@@ -218,22 +367,50 @@ begin
   Inherited;
 end;
 
+function TPanel.getx1 (): Integer; inline; begin result := X+Width-1; end;
+function TPanel.gety1 (): Integer; inline; begin result := Y+Height-1; end;
+function TPanel.getvisvalid (): Boolean; inline; begin result := (Width > 0) and (Height > 0); end;
+
+function TPanel.getMovingSpeedX (): Integer; inline; begin result := mMovingSpeed.X; end;
+procedure TPanel.setMovingSpeedX (v: Integer); inline; begin mMovingSpeed.X := v; end;
+function TPanel.getMovingSpeedY (): Integer; inline; begin result := mMovingSpeed.Y; end;
+procedure TPanel.setMovingSpeedY (v: Integer); inline; begin mMovingSpeed.Y := v; end;
+
+function TPanel.getMovingStartX (): Integer; inline; begin result := mMovingStart.X; end;
+procedure TPanel.setMovingStartX (v: Integer); inline; begin mMovingStart.X := v; end;
+function TPanel.getMovingStartY (): Integer; inline; begin result := mMovingStart.Y; end;
+procedure TPanel.setMovingStartY (v: Integer); inline; begin mMovingStart.Y := v; end;
+
+function TPanel.getMovingEndX (): Integer; inline; begin result := mMovingEnd.X; end;
+procedure TPanel.setMovingEndX (v: Integer); inline; begin mMovingEnd.X := v; end;
+function TPanel.getMovingEndY (): Integer; inline; begin result := mMovingEnd.Y; end;
+procedure TPanel.setMovingEndY (v: Integer); inline; begin mMovingEnd.Y := v; end;
+
+function TPanel.getIsGBack (): Boolean; inline; begin result := ((tag and GridTagBack) <> 0); end;
+function TPanel.getIsGStep (): Boolean; inline; begin result := ((tag and GridTagStep) <> 0); end;
+function TPanel.getIsGWall (): Boolean; inline; begin result := ((tag and (GridTagWall or GridTagDoor)) <> 0); end;
+function TPanel.getIsGAcid1 (): Boolean; inline; begin result := ((tag and GridTagAcid1) <> 0); end;
+function TPanel.getIsGAcid2 (): Boolean; inline; begin result := ((tag and GridTagAcid2) <> 0); end;
+function TPanel.getIsGWater (): Boolean; inline; begin result := ((tag and GridTagWater) <> 0); end;
+function TPanel.getIsGFore (): Boolean; inline; begin result := ((tag and GridTagFore) <> 0); end;
+function TPanel.getIsGLift (): Boolean; inline; begin result := ((tag and GridTagLift) <> 0); end;
+function TPanel.getIsGBlockMon (): Boolean; inline; begin result := ((tag and GridTagBlockMon) <> 0); end;
+
 procedure TPanel.Draw();
 var
   xx, yy: Integer;
   NoTextureID: DWORD;
   NW, NH: Word;
 begin
-  if Enabled and (FCurTexture >= 0) and
+  if {Enabled and} (FCurTexture >= 0) and
      (Width > 0) and (Height > 0) and (FAlpha < 255) and
-     g_Collide(X, Y, Width, Height,
-               sX, sY, sWidth, sHeight) then
+     ((g_dbg_scale <> 1.0) or g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight)) then
   begin
     if FTextureIDs[FCurTexture].Anim then
       begin // Àíèìèðîâàííàÿ òåêñòóðà
         if FTextureIDs[FCurTexture].AnTex = nil then
           Exit;
-  
+
         for xx := 0 to (Width div FTextureWidth)-1 do
           for yy := 0 to (Height div FTextureHeight)-1 do
             FTextureIDs[FCurTexture].AnTex.Draw(
@@ -243,16 +420,16 @@ begin
     else
       begin // Îáû÷íàÿ òåêñòóðà
         case FTextureIDs[FCurTexture].Tex of
-          TEXTURE_SPECIAL_WATER:
+          LongWord(TEXTURE_SPECIAL_WATER):
             e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1,
                            0, 0, 255, 0, B_FILTER);
-          TEXTURE_SPECIAL_ACID1:
+          LongWord(TEXTURE_SPECIAL_ACID1):
             e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1,
                            0, 128, 0, 0, B_FILTER);
-          TEXTURE_SPECIAL_ACID2:
+          LongWord(TEXTURE_SPECIAL_ACID2):
             e_DrawFillQuad(X, Y, X+Width-1, Y+Height-1,
                            128, 0, 0, 0, B_FILTER);
-          TEXTURE_NONE:
+          LongWord(TEXTURE_NONE):
             if g_Texture_Get('NOTEXTURE', NoTextureID) then
             begin
               e_GetTextureSize(NoTextureID, @NW, @NH);
@@ -273,26 +450,411 @@ begin
             end;
 
           else
-            e_DrawFill(FTextureIDs[FCurTexture].Tex, X, Y,
-                       Width div FTextureWidth,
-                       Height div FTextureHeight,
-                       FAlpha, True, FBlending);
+            if not mMovingActive then
+              e_DrawFill(FTextureIDs[FCurTexture].Tex, X, Y, Width div FTextureWidth, Height div FTextureHeight, FAlpha, True, FBlending)
+            else
+              e_DrawFillX(FTextureIDs[FCurTexture].Tex, X, Y, Width, Height, FAlpha, True, FBlending);
         end;
       end;
   end;
 end;
 
+procedure TPanel.DrawShadowVolume(lightX: Integer; lightY: Integer; radius: Integer);
+  procedure extrude (x: Integer; y: Integer);
+  begin
+    glVertex2i(x+(x-lightX)*500, y+(y-lightY)*500);
+    //e_WriteLog(Format('  : (%d,%d)', [x+(x-lightX)*300, y+(y-lightY)*300]), MSG_WARNING);
+  end;
+
+  procedure drawLine (x0: Integer; y0: Integer; x1: Integer; y1: Integer);
+  begin
+    // does this side facing the light?
+    if ((x1-x0)*(lightY-y0)-(lightX-x0)*(y1-y0) >= 0) then exit;
+    //e_WriteLog(Format('lightpan: (%d,%d)-(%d,%d)', [x0, y0, x1, y1]), MSG_WARNING);
+    // this edge is facing the light, extrude and draw it
+    glVertex2i(x0, y0);
+    glVertex2i(x1, y1);
+    extrude(x1, y1);
+    extrude(x0, y0);
+  end;
+
+begin
+  if radius < 4 then exit;
+  if Enabled and (FCurTexture >= 0) and (Width > 0) and (Height > 0) and (FAlpha < 255) and g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight) then
+  begin
+    if not FTextureIDs[FCurTexture].Anim then
+    begin
+      case FTextureIDs[FCurTexture].Tex of
+        LongWord(TEXTURE_SPECIAL_WATER): exit;
+        LongWord(TEXTURE_SPECIAL_ACID1): exit;
+        LongWord(TEXTURE_SPECIAL_ACID2): exit;
+        LongWord(TEXTURE_NONE): exit;
+      end;
+    end;
+    if (X+Width < lightX-radius) then exit;
+    if (Y+Height < lightY-radius) then exit;
+    if (X > lightX+radius) then exit;
+    if (Y > lightY+radius) then exit;
+    //e_DrawFill(FTextureIDs[FCurTexture].Tex, X, Y, Width div FTextureWidth, Height div FTextureHeight, FAlpha, True, FBlending);
+
+    glBegin(GL_QUADS);
+      drawLine(x,       y,        x+width, y); // top
+      drawLine(x+width, y,        x+width, y+height); // right
+      drawLine(x+width, y+height, x,       y+height); // bottom
+      drawLine(x,       y+height, x,       y); // left
+    glEnd();
+  end;
+end;
+
+
+procedure TPanel.positionChanged (); inline;
+var
+  px, py, pw, ph: Integer;
+begin
+  if (proxyId >= 0) then
+  begin
+    mapGrid.getBodyDims(proxyId, px, py, pw, ph);
+    if (px <> x) or (py <> y) or (pw <> Width) or (ph <> Height) then
+    begin
+      {
+      e_LogWritefln('panel moved: arridx=%s; guid=%s; proxyid=%s; old:(%s,%s)-(%sx%s); new:(%s,%s)-(%sx%s)',
+        [arrIdx, mGUID, proxyId, px, py, pw, ph, x, y, width, height]);
+      }
+      g_Mark(px, py, pw, ph, MARK_WALL, false);
+      if (Width < 1) or (Height < 1) then
+      begin
+        mapGrid.proxyEnabled[proxyId] := false;
+      end
+      else
+      begin
+        mapGrid.proxyEnabled[proxyId] := Enabled;
+        if (pw <> Width) or (ph <> Height) then mapGrid.moveResizeBody(proxyId, X, Y, Width, Height)
+        else mapGrid.moveBody(proxyId, X, Y);
+        g_Mark(X, Y, Width, Height, MARK_WALL);
+      end;
+    end;
+  end;
+end;
+
+
+var
+  monCheckList: array of TMonster = nil;
+  monCheckListUsed: Integer = 0;
+
 procedure TPanel.Update();
+var
+  ox, oy: Integer;
+  nx, ny: Integer;
+  ex, ey, nex, ney: Integer;
+  mpw, mph: Integer;
+
+  // return `true` if we should move by dx,dy
+  function tryMPlatMove (px, py, pw, ph: Integer; out dx, dy: Integer; out squash: Boolean; ontop: PBoolean=nil): Boolean;
+  var
+    u0, u1: Single;
+    tex, tey: Integer;
+    pdx, pdy: Integer;
+    pan: TPanel;
+    trtag: Integer;
+    hedge: Integer;
+  begin
+    squash := false;
+    tex := px;
+    tey := py;
+    pdx := mMovingSpeed.X;
+    pdy := mMovingSpeed.Y;
+    // standing on the platform?
+    if (py+ph = oy) then
+    begin
+      if (ontop <> nil) then ontop^ := true;
+      // yes, move with it; but skip steps
+      pan := mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, nil, (GridTagWall or GridTagDoor));
+      if (pan <> nil) then
+      begin
+        //e_LogWritefln('entity on the platform; tracing=(%s,%s); endpoint=(%s,%s); mustbe=(%s,%s)', [px, py, tex, tey, px+pdx, py+pdy]);
+        // if we cannot move, only walls should squash the entity
+        {
+        if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
+        begin
+          if (tex = px) and (tey = py) then squash := true;
+        end;
+        }
+      end;
+    end
+    else
+    begin
+      if (ontop <> nil) then ontop^ := false;
+      // not standing on the platform: trace platform to see if it hits the entity
+      // hitedge (for `it`): 0: top; 1: right; 2: bottom; 3: left
+      {
+      if g_Collide(px, py, pw, ph, ox, oy, mpw, mph) then
+      begin
+        e_LogWritefln('entity is embedded: plr=(%s,%s)-(%s,%s); mpl=(%s,%s)-(%s,%s)', [px, py, px+pw-1, py+ph-1, ox, oy, ox+mpw-1, oy+mph-1]);
+      end;
+      }
+      if sweepAABB(ox, oy, mpw, mph, pdx, pdy, px, py, pw, ph, @u0, @hedge, @u1) then
+      begin
+        //e_LogWritefln('T: platsweep; u0=%s; u1=%s; hedge=%s; sweepAABB(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)', [u0, u1, hedge, ox, oy, mpw, mph, pdx, pdy, px-1, py-1, pw+2, ph+2]);
+        // yes, platform hits the entity, push the entity in the direction of the platform
+        u0 := 1.0-u0; // how much path left?
+        pdx := trunc(pdx*u0);
+        pdy := trunc(pdy*u0);
+        //e_LogWritefln(' platsweep; uleft=%s; pd=(%s,%s)', [u0, pdx, pdy]);
+        if (pdx <> 0) or (pdy <> 0) then
+        begin
+          // has some path to go, trace the entity
+          trtag := (GridTagWall or GridTagDoor);
+          // if we're moving down, consider steps too
+          if (pdy > 0) then trtag := trtag or GridTagStep;
+          pan := mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, nil, trtag);
+          //e_LogWritefln('  tracebox: te=(%s,%s)', [tex, tey]);
+          // if we cannot move, only walls should squash the entity
+          {
+          if ((tag and (GridTagWall or GridTagDoor)) <> 0) then
+          begin
+            if (pan <> nil) and (tex = px) and (tey = py) then squash := true;
+          end;
+          }
+        end;
+      end
+      else
+      begin
+        // no collistion, but may be embedded
+        //e_LogWritefln('F: platsweep; u0=%s; u1=%s; sweepAABB(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)', [u0, u1, ox, oy, mpw, mph, pdx, pdy, px-1, py-1, pw+2, ph+2]);
+        //squash := (u1 >= 0.0);
+      end;
+    end;
+    dx := tex-px;
+    dy := tey-py;
+    result := (dx <> 0) or (dy <> 0);
+    if (not squash) and ((tag and (GridTagWall or GridTagDoor)) <> 0) then
+    begin
+      squash := g_Collide(tex, tey, pw, ph, nx, ny, mpw, mph); // still in platform?
+      //if not squash then squash := g_Map_CollidePanel(tex, tey, pw, ph, (PANEL_WALL or PANEL_OPENDOOR or PANEL_CLOSEDOOR));
+    end;
+  end;
+
+  function monCollect (mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    if (monCheckListUsed >= Length(monCheckList)) then SetLength(monCheckList, monCheckListUsed+128);
+    monCheckList[monCheckListUsed] := mon;
+    Inc(monCheckListUsed);
+  end;
+
+var
+  cx0, cy0, cx1, cy1, cw, ch: Integer;
+  f: Integer;
+  px, py, pw, ph, pdx, pdy: Integer;
+  squash: Boolean;
+  plr: TPlayer;
+  gib: PGib;
+  cor: TCorpse;
+  mon: TMonster;
+  mpfrid: LongWord;
+  ontop: Boolean;
+  actMoveTrig: Boolean;
+  actSizeTrig: Boolean;
 begin
-  if Enabled and (FCurTexture >= 0) and
+  if (not Enabled) or (Width < 1) or (Height < 1) then exit;
+
+  if (FCurTexture >= 0) and
     (FTextureIDs[FCurTexture].Anim) and
     (FTextureIDs[FCurTexture].AnTex <> nil) and
-    (Width > 0) and (Height > 0) and (FAlpha < 255) then
+    (FAlpha < 255) then
   begin
     FTextureIDs[FCurTexture].AnTex.Update();
     FCurFrame := FTextureIDs[FCurTexture].AnTex.CurrentFrame;
     FCurFrameCount := FTextureIDs[FCurTexture].AnTex.CurrentCounter;
   end;
+
+  if not g_dbgpan_mplat_active then exit;
+
+  if not mMovingActive then exit;
+  if mMovingSpeed.isZero and mSizeSpeed.isZero then exit;
+
+  //TODO: write wall size change processing
+
+  // moving platform?
+  begin
+    (*
+     * collect all monsters and players (aka entities) along the possible platform path
+     *   if entity is standing on a platform:
+     *     try to move it along the platform path, checking wall collisions
+     *   if entity is NOT standing on a platform, but hit with sweeped platform aabb:
+     *     try to push entity
+     *     if we can't push entity all the way, squash it
+     *)
+    mpw := Width;
+    mph := Height;
+
+    // old rect
+    ox := X;
+    oy := Y;
+    ex := ox+mpw-1;
+    ey := ox+mph-1;
+    // new rect
+    nx := ox+mMovingSpeed.X;
+    ny := oy+mMovingSpeed.Y;
+    nex := nx+mpw-1;
+    ney := ny+mph-1;
+    // full rect
+    cx0 := nmin(ox, nx);
+    cy0 := nmin(oy, ny);
+    cx1 := nmax(ex, nex);
+    cy1 := nmax(ey, ney);
+    // extrude
+    cx0 -= 1;
+    cy0 -= 1;
+    cx1 += 1;
+    cy1 += 1;
+    cw := cx1-cx0+1;
+    ch := cy1-cy0+1;
+
+    // process "obstacle" panels
+    if ((tag and GridTagObstacle) <> 0) then
+    begin
+      // temporarily turn off this panel, so it won't interfere with collision checks
+      mapGrid.proxyEnabled[proxyId] := false;
+
+      // process players
+      for f := 0 to High(gPlayers) do
+      begin
+        plr := gPlayers[f];
+        if (plr = nil) or (not plr.alive) then continue;
+        plr.getMapBox(px, py, pw, ph);
+        if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
+        if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash) then
+        begin
+          // set new position
+          plr.moveBy(pdx, pdy); // this will call `positionChanged()` for us
+        end;
+        // squash player, if necessary
+        if squash then plr.Damage(15000, 0, 0, 0, HIT_TRAP);
+      end;
+
+      // process gibs
+      for f := 0 to High(gGibs) do
+      begin
+        gib := @gGibs[f];
+        if not gib.alive then continue;
+        gib.getMapBox(px, py, pw, ph);
+        if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
+        if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash, @ontop) then
+        begin
+          // set new position
+          gib.moveBy(pdx, pdy); // this will call `positionChanged()` for us
+          {
+          if ontop then
+          begin
+            gib.Obj.Vel.X += pdx;
+            gib.Obj.Vel.Y += pdy;
+          end;
+          }
+        end;
+      end;
+
+      // move and push corpses
+      for f := 0 to High(gCorpses) do
+      begin
+        cor := gCorpses[f];
+        if (cor = nil) then continue;
+        cor.getMapBox(px, py, pw, ph);
+        if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
+        if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash, @ontop) then
+        begin
+          // set new position
+          cor.moveBy(pdx, pdy); // this will call `positionChanged()` for us
+          {
+          if ontop then
+          begin
+            cor.ObjPtr.Vel.X += pdx;
+            cor.ObjPtr.Vel.Y += pdy;
+          end;
+          }
+        end;
+      end;
+
+      // collect monsters
+      monCheckListUsed := 0;
+      g_Mons_ForEachAt(cx0, cy0, cw, ch, monCollect);
+
+      // process collected monsters
+      if (monCheckListUsed > 0) then
+      begin
+        mpfrid := g_Mons_getNewMPlatFrameId();
+        for f := 0 to monCheckListUsed do
+        begin
+          mon := monCheckList[f];
+          if (mon = nil) or (not mon.alive) or (mon.mplatCheckFrameId = mpfrid) then continue;
+          mon.mplatCheckFrameId := mpfrid;
+          mon.getMapBox(px, py, pw, ph);
+          //if not g_Collide(px, py, pw, ph, cx0, cy0, cw, ch) then continue;
+          if tryMPlatMove(px, py, pw, ph, pdx, pdy, squash) then
+          begin
+            // set new position
+            mon.moveBy(pdx, pdy); // this will call `positionChanged()` for us
+          end;
+          // squash player, if necessary
+          if squash then mon.Damage(15000, 0, 0, 0, HIT_TRAP);
+        end;
+      end;
+
+      // restore panel state
+      mapGrid.proxyEnabled[proxyId] := true;
+    end;
+
+    // move panel
+    X := nx;
+    Y := ny;
+    FWidth += mSizeSpeed.w;
+    FHeight += mSizeSpeed.h;
+    positionChanged();
+
+    actMoveTrig := false;
+    actSizeTrig := false;
+
+    {
+    if not mSizeSpeed.isZero then
+    begin
+      e_LogWritefln('ss: size_speed=(%s,%s); size=(%s,%s); move_speed=(%s,%s); oy=%s; ny=%s; etp:%s; ets:%s', [mSizeSpeed.w, mSizeSpeed.h, FWidth, FHeight, mMovingSpeed.X, mMovingSpeed.Y, oy, ny, mEndPosTrig, mEndSizeTrig]);
+    end;
+    }
+
+    // reverse moving direction, if necessary
+    if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
+    begin
+      if mMoveOnce then mMovingActive := false else mMovingSpeed.X := -mMovingSpeed.X;
+      actMoveTrig := true;
+    end;
+
+    if ((mMovingSpeed.Y < 0) and (ny <= mMovingStart.Y)) or ((mMovingSpeed.Y > 0) and (ny >= mMovingEnd.Y)) then
+    begin
+      if mMoveOnce then mMovingActive := false else mMovingSpeed.Y := -mMovingSpeed.Y;
+      actMoveTrig := true;
+    end;
+
+    // check "size stop"
+    if not mSizeSpeed.isZero and (Width = mSizeEnd.w) and (Height = mSizeEnd.h) then
+    begin
+      mSizeSpeed.w := 0;
+      mSizeSpeed.h := 0;
+      actSizeTrig := true;
+      if (Width < 1) or (Height < 1) then mMovingActive := false; //HACK!
+      //e_LogWritefln('FUUUUUUUUUUUUUU', []);
+    end;
+
+
+    if actMoveTrig then
+    begin
+      g_Triggers_Press(mEndPosTrig, ACTIVATE_CUSTOM);
+    end;
+
+    if actSizeTrig then
+    begin
+      g_Triggers_Press(mEndSizeTrig, ACTIVATE_CUSTOM);
+    end;
+  end;
 end;
 
 procedure TPanel.SetFrame(Frame: Integer; Count: Byte);
@@ -356,7 +918,7 @@ begin
     else
       if AnimLoop = 2 then
         FTextureIDs[FCurTexture].AnTex.Loop := False;
-        
+
     FTextureIDs[FCurTexture].AnTex.Reset();
   end;
 
@@ -396,7 +958,7 @@ begin
     else
       if AnimLoop = 2 then
         FTextureIDs[FCurTexture].AnTex.Loop := False;
-        
+
     FTextureIDs[FCurTexture].AnTex.Reset();
   end;
 
@@ -405,7 +967,7 @@ end;
 
 function TPanel.GetTextureID(): DWORD;
 begin
-  Result := TEXTURE_NONE;
+  Result := LongWord(TEXTURE_NONE);
 
   if (FCurTexture >= 0) then
   begin
@@ -431,18 +993,21 @@ var
   sig: DWORD;
   anim: Boolean;
 begin
-  if (not SaveIt) or (Mem = nil) then
-    Exit;
+  if (Mem = nil) then exit;
+  //if not SaveIt then exit;
 
 // Ñèãíàòóðà ïàíåëè:
   sig := PANEL_SIGNATURE; // 'PANL'
   Mem.WriteDWORD(sig);
 // Îòêðûòà/çàêðûòà, åñëè äâåðü:
-  Mem.WriteBoolean(Enabled);
+  Mem.WriteBoolean(FEnabled);
 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò:
-  Mem.WriteByte(LiftType);
+  Mem.WriteByte(FLiftType);
 // Íîìåð òåêóùåé òåêñòóðû:
   Mem.WriteInt(FCurTexture);
+// Êîîðäû
+  Mem.WriteInt(FX);
+  Mem.WriteInt(FY);
 // Àíèìèðîâàííàÿ ëè òåêóùàÿ òåêñòóðà:
   if (FCurTexture >= 0) and (FTextureIDs[FCurTexture].Anim) then
     begin
@@ -456,15 +1021,24 @@ begin
 // Åñëè äà - ñîõðàíÿåì àíèìàöèþ:
   if anim then
     FTextureIDs[FCurTexture].AnTex.SaveState(Mem);
+  // moving platform state
+  Mem.WriteInt(mMovingSpeed.X);
+  Mem.WriteInt(mMovingSpeed.Y);
+  Mem.WriteInt(mMovingStart.X);
+  Mem.WriteInt(mMovingStart.Y);
+  Mem.WriteInt(mMovingEnd.X);
+  Mem.WriteInt(mMovingEnd.Y);
+  Mem.WriteBoolean(mMovingActive);
 end;
 
 procedure TPanel.LoadState(var Mem: TBinMemoryReader);
 var
   sig: DWORD;
   anim: Boolean;
+  //ox, oy: Integer;
 begin
-  if (not SaveIt) or (Mem = nil) then
-    Exit;
+  if (Mem = nil) then exit;
+  //if not SaveIt then exit;
 
 // Ñèãíàòóðà ïàíåëè:
   Mem.ReadDWORD(sig);
@@ -473,11 +1047,17 @@ begin
     raise EBinSizeError.Create('TPanel.LoadState: Wrong Panel Signature');
   end;
 // Îòêðûòà/çàêðûòà, åñëè äâåðü:
-  Mem.ReadBoolean(Enabled);
+  Mem.ReadBoolean(FEnabled);
 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò:
-  Mem.ReadByte(LiftType);
+  Mem.ReadByte(FLiftType);
 // Íîìåð òåêóùåé òåêñòóðû:
   Mem.ReadInt(FCurTexture);
+// Êîîðäû
+  //ox := FX;
+  //oy := FY;
+  Mem.ReadInt(FX);
+  Mem.ReadInt(FY);
+  //e_LogWritefln('panel %s(%s): old=(%s,%s); new=(%s,%s); delta=(%s,%s)', [arrIdx, proxyId, ox, oy, FX, FY, FX-ox, FY-oy]);
 // Àíèìèðîâàííàÿ ëè òåêóùàÿ òåêñòóðà:
   Mem.ReadBoolean(anim);
 // Åñëè äà - çàãðóæàåì àíèìàöèþ:
@@ -489,6 +1069,17 @@ begin
            'TPanel.LoadState: No animation object');
     FTextureIDs[FCurTexture].AnTex.LoadState(Mem);
   end;
+  // moving platform state
+  Mem.ReadInt(mMovingSpeed.X);
+  Mem.ReadInt(mMovingSpeed.Y);
+  Mem.ReadInt(mMovingStart.X);
+  Mem.ReadInt(mMovingStart.Y);
+  Mem.ReadInt(mMovingEnd.X);
+  Mem.ReadInt(mMovingEnd.Y);
+  Mem.ReadBoolean(mMovingActive);
+
+  positionChanged();
+  //mapGrid.proxyEnabled[proxyId] := FEnabled; // done in g_map.pas
 end;
 
 end.