DEADSOFTWARE

center player when the game is scaled (lighting is not working correctly yet, tho)
[d2df-sdl.git] / src / game / g_panel.pas
index 2f54116f98da1fcf96782ba70d2035e2e335553d..5e2988572ad5e129746b5caf8b546a9bb36e4e99 100644 (file)
@@ -51,12 +51,16 @@ type
     mMovingActive: Boolean;
     mMoveOnce: Boolean;
 
+    mOldMovingActive: Boolean;
+
     mSizeSpeed: TDFSize;
     mSizeEnd: TDFSize;
 
     mEndPosTrig: Integer;
     mEndSizeTrig: Integer;
 
+    mNeedSend: Boolean; // for network
+
   private
     function getx1 (): Integer; inline;
     function gety1 (): Integer; inline;
@@ -77,6 +81,16 @@ type
     function getMovingEndY (): Integer; inline;
     procedure setMovingEndY (v: Integer); inline;
 
+    function getSizeSpeedX (): Integer; inline;
+    procedure setSizeSpeedX (v: Integer); inline;
+    function getSizeSpeedY (): Integer; inline;
+    procedure setSizeSpeedY (v: Integer); inline;
+
+    function getSizeEndX (): Integer; inline;
+    procedure setSizeEndX (v: Integer); inline;
+    function getSizeEndY (): Integer; inline;
+    procedure setSizeEndY (v: Integer); inline;
+
   public
     FCurTexture:      Integer; // Íîìåð òåêóùåé òåêñòóðû
     FCurFrame:        Integer;
@@ -84,10 +98,8 @@ type
     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
@@ -127,6 +139,10 @@ type
     function getIsGLift (): Boolean; inline; // gLifts
     function getIsGBlockMon (): Boolean; inline; // gBlockMon
 
+    // get-and-clear
+    function gncNeedSend (): Boolean; inline;
+    procedure setDirty (); inline; // why `dirty`? 'cause i may introduce property `needSend` later
+
   public
     property visvalid: Boolean read getvisvalid; // panel is "visvalid" when it's width and height are positive
 
@@ -141,10 +157,8 @@ type
     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?
 
@@ -157,6 +171,11 @@ type
     property movingActive: Boolean read mMovingActive write mMovingActive;
     property moveOnce: Boolean read mMoveOnce write mMoveOnce;
 
+    property sizeSpeedX: Integer read getSizeSpeedX write setSizeSpeedX;
+    property sizeSpeedY: Integer read getSizeSpeedY write setSizeSpeedY;
+    property sizeEndX: Integer read getSizeEndX write setSizeEndX;
+    property sizeEndY: Integer read getSizeEndY write setSizeEndY;
+
     property isGBack: Boolean read getIsGBack;
     property isGStep: Boolean read getIsGStep;
     property isGWall: Boolean read getIsGWall;
@@ -172,6 +191,9 @@ type
     property movingStart: TDFPoint read mMovingStart write mMovingStart;
     property movingEnd: TDFPoint read mMovingEnd write mMovingEnd;
 
+    property sizeSpeed: TDFSize read mSizeSpeed write mSizeSpeed;
+    property sizeEnd: TDFSize read mSizeEnd write mSizeEnd;
+
     property endPosTrigId: Integer read mEndPosTrig write mEndPosTrig;
     property endSizeTrigId: Integer read mEndSizeTrig write mEndSizeTrig;
   end;
@@ -210,7 +232,6 @@ begin
   FCurFrame := 0;
   FCurFrameCount := 0;
   LastAnimLoop := 0;
-  Moved := False;
 
   mapId := PanelRec.id;
   mGUID := aguid;
@@ -218,7 +239,8 @@ begin
   mMovingSpeed := PanelRec.moveSpeed;
   mMovingStart := PanelRec.moveStart;
   mMovingEnd := PanelRec.moveEnd;
-  mMovingActive := PanelRec['move_active'].varvalue;
+  mMovingActive := PanelRec['move_active'].value;
+  mOldMovingActive := mMovingActive;
   mMoveOnce := PanelRec.moveOnce;
 
   mSizeSpeed := PanelRec.sizeSpeed;
@@ -227,42 +249,21 @@ begin
   mEndPosTrig := PanelRec.endPosTrig;
   mEndSizeTrig := PanelRec.endSizeTrig;
 
+  mNeedSend := false;
+
 // Òèï ïàíåëè:
   PanelType := PanelRec.PanelType;
   Enabled := True;
   Door := False;
   LiftType := 0;
-  SaveIt := False;
 
   case PanelType of
-    PANEL_OPENDOOR:
-      begin
-        Enabled := False;
-        Door := True;
-        SaveIt := True;
-      end;
-    PANEL_CLOSEDOOR:
-      begin
-        Door := True;
-        SaveIt := True;
-      end;
-    PANEL_LIFTUP:
-      SaveIt := True;
-    PANEL_LIFTDOWN:
-      begin
-        LiftType := 1;
-        SaveIt := True;
-      end;
-    PANEL_LIFTLEFT:
-      begin
-        LiftType := 2;
-        SaveIt := True;
-      end;
-    PANEL_LIFTRIGHT:
-      begin
-        LiftType := 3;
-        SaveIt := True;
-      end;
+    PANEL_OPENDOOR: begin Enabled := False; Door := True; end;
+    PANEL_CLOSEDOOR: Door := True;
+    PANEL_LIFTUP: LiftType := 0; //???
+    PANEL_LIFTDOWN: LiftType := 1;
+    PANEL_LIFTLEFT: LiftType := 2;
+    PANEL_LIFTRIGHT: LiftType := 3;
   end;
 
 // Íåâèäèìàÿ:
@@ -325,7 +326,6 @@ begin
                              True, Textures[AddTextures[i].Texture].Speed);
         FTextureIDs[i].AnTex.Blending := ByteBool(PanelRec.Flags and PANEL_FLAG_BLENDING);
         FTextureIDs[i].AnTex.Alpha := PanelRec.Alpha;
-        SaveIt := True;
       end
     else
       begin // Îáû÷íàÿ òåêñòóðà
@@ -334,8 +334,7 @@ begin
   end;
 
 // Òåêñòóð íåñêîëüêî - íóæíî ñîõðàíÿòü òåêóùóþ:
-  if Length(FTextureIDs) > 1 then
-    SaveIt := True;
+  //if Length(FTextureIDs) > 1 then SaveIt := True;
 
 // Åñëè íå ñïåöòåêñòóðà, òî çàäàåì ðàçìåðû:
   if PanelRec.TextureNum > High(Textures) then
@@ -386,6 +385,16 @@ procedure TPanel.setMovingEndX (v: Integer); inline; begin mMovingEnd.X := v; en
 function TPanel.getMovingEndY (): Integer; inline; begin result := mMovingEnd.Y; end;
 procedure TPanel.setMovingEndY (v: Integer); inline; begin mMovingEnd.Y := v; end;
 
+function TPanel.getSizeSpeedX (): Integer; inline; begin result := mSizeSpeed.w; end;
+procedure TPanel.setSizeSpeedX (v: Integer); inline; begin mSizeSpeed.w := v; end;
+function TPanel.getSizeSpeedY (): Integer; inline; begin result := mSizeSpeed.h; end;
+procedure TPanel.setSizeSpeedY (v: Integer); inline; begin mSizeSpeed.h := v; end;
+
+function TPanel.getSizeEndX (): Integer; inline; begin result := mSizeEnd.w; end;
+procedure TPanel.setSizeEndX (v: Integer); inline; begin mSizeEnd.w := v; end;
+function TPanel.getSizeEndY (): Integer; inline; begin result := mSizeEnd.h; end;
+procedure TPanel.setSizeEndY (v: Integer); inline; begin mSizeEnd.h := 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;
@@ -396,15 +405,19 @@ function TPanel.getIsGFore (): Boolean; inline; begin result := ((tag and GridTa
 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();
+function TPanel.gncNeedSend (): Boolean; inline; begin result := mNeedSend; mNeedSend := false; end;
+procedure TPanel.setDirty (); inline; begin mNeedSend := true; end;
+
+
+procedure TPanel.Draw ();
 var
   xx, yy: Integer;
   NoTextureID: DWORD;
   NW, NH: Word;
 begin
   if {Enabled and} (FCurTexture >= 0) and
-     (Width > 0) and (Height > 0) and (FAlpha < 255) and
-     ((g_dbg_scale <> 1.0) or g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight)) then
+     (Width > 0) and (Height > 0) and (FAlpha < 255) {and
+     g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight)} then
   begin
     if FTextureIDs[FCurTexture].Anim then
       begin // Àíèìèðîâàííàÿ òåêñòóðà
@@ -450,10 +463,10 @@ 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, g_dbg_scale);
         end;
       end;
   end;
@@ -480,7 +493,8 @@ procedure TPanel.DrawShadowVolume(lightX: Integer; lightY: Integer; radius: Inte
 
 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
+  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
@@ -528,8 +542,15 @@ begin
       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);
+        if (pw <> Width) or (ph <> Height) then
+        begin
+          //writeln('panel resize!');
+          mapGrid.moveResizeBody(proxyId, X, Y, Width, Height)
+        end
+        else
+        begin
+          mapGrid.moveBody(proxyId, X, Y);
+        end;
         g_Mark(X, Y, Width, Height, MARK_WALL);
       end;
     end;
@@ -544,19 +565,18 @@ var
 procedure TPanel.Update();
 var
   ox, oy: Integer;
-  nx, ny: Integer;
+  nx, ny, nw, nh: 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;
+    u0: Single;
     tex, tey: Integer;
     pdx, pdy: Integer;
-    pan: TPanel;
     trtag: Integer;
-    hedge: Integer;
+    szdx, szdy: Integer;
   begin
     squash := false;
     tex := px;
@@ -567,32 +587,41 @@ var
     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;
+      // yes, move with it; but skip steps (no need to process size change here, 'cause platform top cannot be changed with it)
+      mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, nil, (GridTagWall or GridTagDoor));
     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
+      // first, process size change (as we cannot sweeptest both move and size change)
+      // but we don't have to check for pushing if the panel is shrinking
+      szdx := nw-mpw;
+      szdy := nh-mph;
+      if (szdx > 0) or (szdy > 0) 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]);
+        // ignore shrinking dimension
+        if (szdx < 0) then szdx := 0;
+        if (szdy < 0) then szdy := 0;
+        // move platform by szd* back, and check for szd* movement
+        if sweepAABB(ox-szdx, oy-szdy, nw, nh, szdx, szdy, px, py, pw, ph, @u0) then
+        begin
+          // yes, platform hits the entity, push the entity in the resizing direction
+          u0 := 1.0-u0; // how much path left?
+          szdx := trunc(szdx*u0);
+          szdy := trunc(szdy*u0);
+          if (szdx <> 0) or (szdy <> 0) then
+          begin
+            // has some path to go, trace the entity
+            trtag := (GridTagWall or GridTagDoor);
+            // if we're moving down, consider steps too
+            if (szdy > 0) then trtag := trtag or GridTagStep;
+            mapGrid.traceBox(tex, tey, px, py, pw, ph, szdx, szdy, nil, trtag);
+          end;
+        end;
       end;
-      }
-      if sweepAABB(ox, oy, mpw, mph, pdx, pdy, px, py, pw, ph, @u0, @hedge, @u1) then
+      // second, process platform movement, using te* as entity starting point
+      if sweepAABB(ox, oy, nw, nh, pdx, pdy, tex, tey, pw, ph, @u0) 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
@@ -606,31 +635,18 @@ var
           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;
-          }
+          mapGrid.traceBox(tex, tey, px, py, pw, ph, pdx, pdy, nil, trtag);
         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;
+    // done with entity movement, new coords are in te*
     dx := tex-px;
     dy := tey-py;
     result := (dx <> 0) or (dy <> 0);
-    if (not squash) and ((tag and (GridTagWall or GridTagDoor)) <> 0) then
+    if ((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));
+      // check for squashing; as entity cannot be pushed into a wall, check only collision with the platform itself
+      squash := g_Collide(tex, tey, pw, ph, nx, ny, nw, nh); // squash, if still in platform
     end;
   end;
 
@@ -670,6 +686,9 @@ begin
 
   if not g_dbgpan_mplat_active then exit;
 
+  if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
+  mOldMovingActive := mMovingActive;
+
   if not mMovingActive then exit;
   if mMovingSpeed.isZero and mSizeSpeed.isZero then exit;
 
@@ -685,141 +704,157 @@ begin
      *     try to push entity
      *     if we can't push entity all the way, squash it
      *)
+    ox := X;
+    oy := Y;
     mpw := Width;
     mph := Height;
 
-    // old rect
-    ox := X;
-    oy := Y;
-    ex := ox+mpw-1;
-    ey := ox+mph-1;
-    // new rect
+    nw := mpw+mSizeSpeed.w;
+    nh := mph+mSizeSpeed.h;
     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
+
+    // force network updates only if some sudden change happened
+    // set the flag here, so we can sync affected monsters
+    if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) then
     begin
-      // temporarily turn off this panel, so it won't interfere with collision checks
-      mapGrid.proxyEnabled[proxyId] := false;
+      mNeedSend := true;
+    end
+    else if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
+    begin
+      mNeedSend := true;
+    end
+    else if ((mMovingSpeed.Y < 0) and (ny <= mMovingStart.Y)) or ((mMovingSpeed.Y > 0) and (ny >= mMovingEnd.Y)) then
+    begin
+      mNeedSend := true;
+    end;
 
-      // process players
-      for f := 0 to High(gPlayers) do
+    // if pannel disappeared, we don't have to do anything
+    if (nw > 0) and (nh > 0) then
+    begin
+      // old rect
+      ex := ox+mpw-1;
+      ey := ox+mph-1;
+      // new rect
+      nex := nx+nw-1;
+      ney := ny+nh-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
-        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
+        // 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
-          // set new position
-          plr.moveBy(pdx, pdy); // this will call `positionChanged()` for us
+          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 not g_Game_IsClient and squash then plr.Damage(15000, 0, 0, 0, HIT_TRAP);
         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
+        // process gibs
+        for f := 0 to High(gGibs) do
         begin
-          // set new position
-          gib.moveBy(pdx, pdy); // this will call `positionChanged()` for us
-          {
-          if ontop then
+          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
-            gib.Obj.Vel.X += pdx;
-            gib.Obj.Vel.Y += pdy;
+            // set new position
+            gib.moveBy(pdx, pdy); // this will call `positionChanged()` for us
           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
+        // move and push corpses
+        for f := 0 to High(gCorpses) do
         begin
-          // set new position
-          cor.moveBy(pdx, pdy); // this will call `positionChanged()` for us
-          {
-          if ontop then
+          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
-            cor.ObjPtr.Vel.X += pdx;
-            cor.ObjPtr.Vel.Y += pdy;
+            // set new position
+            cor.moveBy(pdx, pdy); // this will call `positionChanged()` for us
           end;
-          }
         end;
-      end;
 
-      // collect monsters
-      monCheckListUsed := 0;
-      g_Mons_ForEachAt(cx0, cy0, cw, ch, monCollect);
+        // 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
+        // process collected monsters
+        if (monCheckListUsed > 0) then
         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
+          mpfrid := g_Mons_getNewMPlatFrameId();
+          for f := 0 to monCheckListUsed do
           begin
-            // set new position
-            mon.moveBy(pdx, pdy); // this will call `positionChanged()` for us
+            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
+              //???FIXME: do we really need to send monsters over the net?
+              //          i don't think so, as dead reckoning should take care of 'em
+              // ok, send new monster position only if platform is going to change it's direction
+              if mNeedSend then mon.setDirty();
+            end;
+            // squash monster, if necessary
+            if not g_Game_IsClient and squash then mon.Damage(15000, 0, 0, 0, HIT_TRAP);
           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;
+        // restore panel state
+        mapGrid.proxyEnabled[proxyId] := true;
+      end;
     end;
 
     // move panel
     X := nx;
     Y := ny;
-    FWidth += mSizeSpeed.w;
-    FHeight += mSizeSpeed.h;
+    FWidth := nw;
+    FHeight := nh;
     positionChanged();
 
     actMoveTrig := false;
     actSizeTrig := false;
 
-    {
-    if not mSizeSpeed.isZero then
+    // `mNeedSend` was set above
+
+    // check "size stop"
+    if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) 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]);
+      mSizeSpeed.w := 0;
+      mSizeSpeed.h := 0;
+      actSizeTrig := true;
+      if (nw < 1) or (nh < 1) then mMovingActive := false; //HACK!
     end;
-    }
 
     // reverse moving direction, if necessary
     if ((mMovingSpeed.X < 0) and (nx <= mMovingStart.X)) or ((mMovingSpeed.X > 0) and (nx >= mMovingEnd.X)) then
@@ -834,29 +869,23 @@ begin
       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 (mOldMovingActive <> mMovingActive) then mNeedSend := true;
+    mOldMovingActive := mMovingActive;
 
-    if actMoveTrig then
+    if not g_Game_IsClient then
     begin
-      g_Triggers_Press(mEndPosTrig, ACTIVATE_CUSTOM);
+      if actMoveTrig then g_Triggers_Press(mEndPosTrig, ACTIVATE_CUSTOM);
+      if actSizeTrig then g_Triggers_Press(mEndSizeTrig, ACTIVATE_CUSTOM);
     end;
 
-    if actSizeTrig then
-    begin
-      g_Triggers_Press(mEndSizeTrig, ACTIVATE_CUSTOM);
-    end;
+    // some triggers may activate this, don't delay sending
+    //TODO: when triggers will be able to control speed and size, check that here too
+    if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
+    mOldMovingActive := mMovingActive;
   end;
 end;
 
+
 procedure TPanel.SetFrame(Frame: Integer; Count: Byte);
 
   function ClampInt(X, A, B: Integer): Integer;
@@ -988,17 +1017,22 @@ begin
        Result := Result + 100;
 end;
 
+const
+  PAN_SAVE_VERSION = 1;
+
 procedure TPanel.SaveState(Var Mem: TBinMemoryWriter);
 var
   sig: DWORD;
   anim: Boolean;
+  ver: Byte;
 begin
   if (Mem = nil) then exit;
-  //if not SaveIt then exit;
 
 // Ñèãíàòóðà ïàíåëè:
   sig := PANEL_SIGNATURE; // 'PANL'
   Mem.WriteDWORD(sig);
+  ver := PAN_SAVE_VERSION;
+  Mem.WriteByte(ver);
 // Îòêðûòà/çàêðûòà, åñëè äâåðü:
   Mem.WriteBoolean(FEnabled);
 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò:
@@ -1008,6 +1042,8 @@ begin
 // Êîîðäû
   Mem.WriteInt(FX);
   Mem.WriteInt(FY);
+  Mem.WriteWord(FWidth);
+  Mem.WriteWord(FHeight);
 // Àíèìèðîâàííàÿ ëè òåêóùàÿ òåêñòóðà:
   if (FCurTexture >= 0) and (FTextureIDs[FCurTexture].Anim) then
     begin
@@ -1021,6 +1057,7 @@ begin
 // Åñëè äà - ñîõðàíÿåì àíèìàöèþ:
   if anim then
     FTextureIDs[FCurTexture].AnTex.SaveState(Mem);
+
   // moving platform state
   Mem.WriteInt(mMovingSpeed.X);
   Mem.WriteInt(mMovingSpeed.Y);
@@ -1028,24 +1065,32 @@ begin
   Mem.WriteInt(mMovingStart.Y);
   Mem.WriteInt(mMovingEnd.X);
   Mem.WriteInt(mMovingEnd.Y);
+
+  Mem.WriteInt(mSizeSpeed.w);
+  Mem.WriteInt(mSizeSpeed.h);
+  Mem.WriteInt(mSizeEnd.w);
+  Mem.WriteInt(mSizeEnd.h);
   Mem.WriteBoolean(mMovingActive);
+  Mem.WriteBoolean(mMoveOnce);
+
+  Mem.WriteInt(mEndPosTrig);
+  Mem.WriteInt(mEndSizeTrig);
 end;
 
 procedure TPanel.LoadState(var Mem: TBinMemoryReader);
 var
   sig: DWORD;
   anim: Boolean;
+  ver: Byte;
   //ox, oy: Integer;
 begin
   if (Mem = nil) then exit;
-  //if not SaveIt then exit;
 
 // Ñèãíàòóðà ïàíåëè:
   Mem.ReadDWORD(sig);
-  if sig <> PANEL_SIGNATURE then // 'PANL'
-  begin
-    raise EBinSizeError.Create('TPanel.LoadState: Wrong Panel Signature');
-  end;
+  if (sig <> PANEL_SIGNATURE) then raise EBinSizeError.Create('TPanel.LoadState: wrong panel signature'); // 'PANL'
+  Mem.ReadByte(ver);
+  if (ver <> PAN_SAVE_VERSION) then raise EBinSizeError.Create('TPanel.LoadState: invalid panel version');
 // Îòêðûòà/çàêðûòà, åñëè äâåðü:
   Mem.ReadBoolean(FEnabled);
 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò:
@@ -1057,6 +1102,8 @@ begin
   //oy := FY;
   Mem.ReadInt(FX);
   Mem.ReadInt(FY);
+  Mem.ReadWord(FWidth);
+  Mem.ReadWord(FHeight);
   //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);
@@ -1069,6 +1116,7 @@ begin
            'TPanel.LoadState: No animation object');
     FTextureIDs[FCurTexture].AnTex.LoadState(Mem);
   end;
+
   // moving platform state
   Mem.ReadInt(mMovingSpeed.X);
   Mem.ReadInt(mMovingSpeed.Y);
@@ -1076,7 +1124,15 @@ begin
   Mem.ReadInt(mMovingStart.Y);
   Mem.ReadInt(mMovingEnd.X);
   Mem.ReadInt(mMovingEnd.Y);
+  Mem.ReadInt(mSizeSpeed.w);
+  Mem.ReadInt(mSizeSpeed.h);
+  Mem.ReadInt(mSizeEnd.w);
+  Mem.ReadInt(mSizeEnd.h);
   Mem.ReadBoolean(mMovingActive);
+  Mem.ReadBoolean(mMoveOnce);
+
+  Mem.ReadInt(mEndPosTrig);
+  Mem.ReadInt(mEndSizeTrig);
 
   positionChanged();
   //mapGrid.proxyEnabled[proxyId] := FEnabled; // done in g_map.pas