DEADSOFTWARE

network code for mplats: looks like it works; see commit comments for some more info
authorKetmar Dark <ketmar@ketmar.no-ip.org>
Wed, 6 Sep 2017 00:52:18 +0000 (03:52 +0300)
committerKetmar Dark <ketmar@ketmar.no-ip.org>
Wed, 6 Sep 2017 00:55:28 +0000 (03:55 +0300)
  there was a bug with panel syncing on join (introduced by me earlier);
  it is fixed now.

  internally, server only sends new panel state when something "interesting"
  happens (active state changed, direction changed, etc.). in this case,
  server will also send monster positions for affected monsters (just in case).
  otherwise, dead reckoning should do it's work.

  maybe it will be better to spam network with panel/monster state constantly,
  i don't know. for now, players with huge lag can be suddenly squashed by
  unsynced mplat. this should be checked with real network games.

  note that network protocol will likely be changed again when we'll get final
  specs for mplats.

src/game/g_game.pas
src/game/g_map.pas
src/game/g_monsters.pas
src/game/g_net.pas
src/game/g_netmsg.pas
src/game/g_panel.pas
src/game/g_saveload.pas

index c83fb8e455e06decf3ce5a07a13f94a8aa06a33c..3be96517735e7b8f12b98fe79a453dadde11c3e2 100644 (file)
@@ -1487,20 +1487,30 @@ var
   function sendMonsPos (mon: TMonster): Boolean;
   begin
     result := false; // don't stop
-    if (mon.MonsterType = MONSTER_BARREL) then
+    // this will also reset "need-send" flag
+    if mon.gncNeedSend then
+    begin
+      MH_SEND_MonsterPos(mon.UID);
+    end
+    else if (mon.MonsterType = MONSTER_BARREL) then
     begin
       if (mon.GameVelX <> 0) or (mon.GameVelY <> 0) then MH_SEND_MonsterPos(mon.UID);
     end
-    else
-      if (mon.MonsterState <> MONSTATE_SLEEP) then
-      begin
-        if (mon.MonsterState <> MONSTATE_DEAD) or (mon.GameVelX <> 0) or (mon.GameVelY <> 0) then
-        begin
-          MH_SEND_MonsterPos(mon.UID);
-        end;
-      end;
+    else if (mon.MonsterState <> MONSTATE_SLEEP) then
+    begin
+      if (mon.MonsterState <> MONSTATE_DEAD) or (mon.GameVelX <> 0) or (mon.GameVelY <> 0) then MH_SEND_MonsterPos(mon.UID);
+    end;
   end;
 
+  function sendMonsPosUnexpected (mon: TMonster): Boolean;
+  begin
+    result := false; // don't stop
+    // this will also reset "need-send" flag
+    if mon.gncNeedSend then MH_SEND_MonsterPos(mon.UID);
+  end;
+
+var
+  reliableUpdate: Boolean;
 begin
   g_ResetDynlights();
 // Ïîðà âûêëþ÷àòü èãðó:
@@ -1844,13 +1854,16 @@ begin
     g_GFX_Update();
     g_Player_UpdateAll();
     g_Player_UpdatePhysicalObjects();
-    if gGameSettings.GameType = GT_SERVER then
-      if Length(gMonstersSpawned) > 0 then
+
+    // server: send newly spawned monsters unconditionally
+    if (gGameSettings.GameType = GT_SERVER) then
+    begin
+      if (Length(gMonstersSpawned) > 0) then
       begin
-        for I := 0 to High(gMonstersSpawned) do
-          MH_SEND_MonsterSpawn(gMonstersSpawned[I]);
+        for I := 0 to High(gMonstersSpawned) do MH_SEND_MonsterSpawn(gMonstersSpawned[I]);
         SetLength(gMonstersSpawned, 0);
       end;
+    end;
 
     if (gSoundTriggerTime > 8) then
     begin
@@ -1858,49 +1871,65 @@ begin
       gSoundTriggerTime := 0;
     end
     else
+    begin
       Inc(gSoundTriggerTime);
+    end;
 
     if (NetMode = NET_SERVER) then
     begin
       Inc(NetTimeToUpdate);
       Inc(NetTimeToReliable);
-      if NetTimeToReliable >= NetRelupdRate then
+
+      // send monster updates
+      if (NetTimeToReliable >= NetRelupdRate) or (NetTimeToUpdate >= NetUpdateRate) then
       begin
+        // send all monsters (periodic sync)
+        reliableUpdate := (NetTimeToReliable >= NetRelupdRate);
+
         for I := 0 to High(gPlayers) do
-          if gPlayers[I] <> nil then
-            MH_SEND_PlayerPos(True, gPlayers[I].UID);
+        begin
+          if (gPlayers[I] <> nil) then MH_SEND_PlayerPos(reliableUpdate, gPlayers[I].UID);
+        end;
 
         g_Mons_ForEach(sendMonsPos);
 
-        NetTimeToReliable := 0;
-        NetTimeToUpdate := NetUpdateRate;
+        if reliableUpdate then
+        begin
+          NetTimeToReliable := 0;
+          NetTimeToUpdate := NetUpdateRate;
+        end
+        else
+        begin
+          NetTimeToUpdate := 0;
+        end;
       end
-      else if NetTimeToUpdate >= NetUpdateRate then
+      else
       begin
-        if gPlayers <> nil then
-          for I := 0 to High(gPlayers) do
-            if gPlayers[I] <> nil then
-              MH_SEND_PlayerPos(False, gPlayers[I].UID);
-
-        g_Mons_ForEach(sendMonsPos);
-
-        NetTimeToUpdate := 0;
+        // send only mosters with some unexpected changes
+        g_Mons_ForEach(sendMonsPosUnexpected);
       end;
 
+      // send unexpected platform changes
+      g_Map_NetSendInterestingPanels();
+
       if NetUseMaster then
+      begin
         if gTime >= NetTimeToMaster then
         begin
           if (NetMHost = nil) or (NetMPeer = nil) then
-            if not g_Net_Slist_Connect then
-              g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR]);
+          begin
+            if not g_Net_Slist_Connect then g_Console_Add(_lc[I_NET_MSG_ERROR] + _lc[I_NET_SLIST_ERROR]);
+          end;
 
           g_Net_Slist_Update;
           NetTimeToMaster := gTime + NetMasterRate;
         end;
+      end;
     end
-    else
-      if NetMode = NET_CLIENT then
-        MC_SEND_PlayerPos();
+    else if (NetMode = NET_CLIENT) then
+    begin
+      MC_SEND_PlayerPos();
+    end;
   end; // if gameOn ...
 
 // Àêòèâíî îêíî èíòåðôåéñà - ïåðåäàåì êëàâèøè åìó:
index 101c3c544d06658454afb94777d730c333832545..6cfd0da3b578ccd8d20f6ca93fe46e1637dcca37 100644 (file)
@@ -109,7 +109,7 @@ function g_Map_traceToNearestWall (x0, y0, x1, y1: Integer; hitx: PInteger=nil;
 function g_Map_traceToNearest (x0, y0, x1, y1: Integer; tag: Integer; hitx: PInteger=nil; hity: PInteger=nil): TPanel;
 
 type
-  TForEachPanelCB = function (pan: TPanel): Boolean; // return `true` to stop
+  TForEachPanelCB = function (pan: TPanel): Boolean is nested; // return `true` to stop
 
 function g_Map_HasAnyPanelAtPoint (x, y: Integer; panelType: Word): Boolean;
 function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
@@ -119,6 +119,12 @@ function g_Map_PanelAtPoint (x, y: Integer; tagmask: Integer=-1): TPanel;
 function g_Map_TraceLiquidNonPrecise (x, y, dx, dy: Integer; out topx, topy: Integer): Boolean;
 
 
+// return `true` from `cb` to stop
+function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
+
+procedure g_Map_NetSendInterestingPanels (); // yay!
+
+
 procedure g_Map_ProfilersBegin ();
 procedure g_Map_ProfilersEnd ();
 
@@ -262,6 +268,34 @@ begin
 end;
 
 
+// return `true` from `cb` to stop
+function g_Map_ForEachPanel (cb: TForEachPanelCB): TPanel;
+var
+  pan: TPanel;
+begin
+  result := nil;
+  if not assigned(cb) then exit;
+  for pan in panByGUID do
+  begin
+    if cb(pan) then begin result := pan; exit; end;
+  end;
+end;
+
+
+procedure g_Map_NetSendInterestingPanels ();
+var
+  pan: TPanel;
+begin
+  if g_Game_IsServer and g_Game_IsNet then
+  begin
+    for pan in panByGUID do
+    begin
+      if pan.gncNeedSend then MH_SEND_PanelState(pan.panelType, pan.guid);
+    end;
+  end;
+end;
+
+
 // ////////////////////////////////////////////////////////////////////////// //
 function g_Map_MinX (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridX0 else result := 0; end;
 function g_Map_MinY (): Integer; inline; begin if (mapGrid <> nil) then result := mapGrid.gridY0 else result := 0; end;
@@ -2897,6 +2931,8 @@ begin
   //pan := gWalls[ID];
   pan := g_Map_PanelByGUID(pguid);
   if (pan = nil) then exit;
+  if pan.Enabled and mapGrid.proxyEnabled[pan.proxyId] then exit;
+
   pan.Enabled := True;
   g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, true);
 
@@ -2904,7 +2940,9 @@ begin
   //if (pan.proxyId >= 0) then mapGrid.proxyEnabled[pan.proxyId] := true
   //else pan.proxyId := mapGrid.insertBody(pan, pan.X, pan.Y, pan.Width, pan.Height, GridTagDoor);
 
-  if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState({gWalls[ID]}pan.PanelType, pguid);
+  //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState({gWalls[ID]}pan.PanelType, pguid);
+  // mark platform as interesting
+  pan.setDirty();
 
   {$IFDEF MAP_DEBUG_ENABLED_FLAG}
   //e_WriteLog(Format('ENABLE: wall #%d(%d) enabled (%d)  (%d,%d)-(%d,%d)', [Integer(ID), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.x, pan.y, pan.width, pan.height]), MSG_NOTIFY);
@@ -2919,13 +2957,17 @@ begin
   //pan := gWalls[ID];
   pan := g_Map_PanelByGUID(pguid);
   if (pan = nil) then exit;
+  if (not pan.Enabled) and (not mapGrid.proxyEnabled[pan.proxyId]) then exit;
+
   pan.Enabled := False;
   g_Mark(pan.X, pan.Y, pan.Width, pan.Height, MARK_DOOR, false);
 
   mapGrid.proxyEnabled[pan.proxyId] := false;
   //if (pan.proxyId >= 0) then begin mapGrid.removeBody(pan.proxyId); pan.proxyId := -1; end;
 
-  if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pan.PanelType, pguid);
+  //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(pan.PanelType, pguid);
+  // mark platform as interesting
+  pan.setDirty();
 
   {$IFDEF MAP_DEBUG_ENABLED_FLAG}
   //e_WriteLog(Format('DISABLE: wall #%d(%d) disabled (%d)  (%d,%d)-(%d,%d)', [Integer(ID), Integer(pan.proxyId), Integer(mapGrid.proxyEnabled[pan.proxyId]), pan.x, pan.y, pan.width, pan.height]), MSG_NOTIFY);
@@ -2982,7 +3024,9 @@ begin
       3: g_Mark(X, Y, Width, Height, MARK_LIFTRIGHT);
     end;
 
-    if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(PanelType, pguid);
+    //if g_Game_IsServer and g_Game_IsNet then MH_SEND_PanelState(PanelType, pguid);
+    // mark platform as interesting
+    pan.setDirty();
   end;
 end;
 
index 13dfb21ab6e307ce5858967c9bf307d32283c416..accdcb7ee23d5892e4f0fc39ebdfbe52ec3509ea 100644 (file)
@@ -87,7 +87,7 @@ type
     FDieTriggers: Array of Integer;
     FSpawnTrigger: Integer;
 
-    mNeedSend: Boolean; // for networl
+    mNeedSend: Boolean; // for network
 
     procedure Turn();
     function findNewPrey(): Boolean;
@@ -344,7 +344,7 @@ begin
   {$ENDIF}
   if (mProxyId = -1) then
   begin
-    mNeedSend := true;
+    //mNeedSend := true;
     mProxyId := monsGrid.insertBody(self, FObj.X+FObj.Rect.X, FObj.Y+FObj.Rect.Y, FObj.Rect.Width, FObj.Rect.Height);
     {$IF DEFINED(D2F_DEBUG_MONS_MOVE)}
     monsGrid.getBodyXY(mProxyId, x, y);
@@ -358,7 +358,7 @@ begin
 
     if (w <> nw) or (h <> nh) then
     begin
-      mNeedSend := true;
+      //mNeedSend := true;
       {$IF DEFINED(D2F_DEBUG_MONS_MOVE)}
       e_WriteLog(Format('monster #%d:(%u): resized; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY);
       {$ENDIF}
@@ -366,7 +366,7 @@ begin
     end
     else if (x <> nx) or (y <> ny) then
     begin
-      mNeedSend := true;
+      //mNeedSend := true;
       {$IF DEFINED(D2F_DEBUG_MONS_MOVE)}
       e_WriteLog(Format('monster #%d:(%u): updating grid; mProxyid=%d; gx=%d; gy=%d', [mArrIdx, UID, mProxyId, x-monsGrid.gridX0, y-monsGrid.gridY0]), MSG_NOTIFY);
       {$ENDIF}
index 16e6bb511d3c2437e8d0bd76da86a26fec99343b..95f96739e62eea4e74a3d662097d2421261c1a50 100644 (file)
@@ -22,7 +22,7 @@ uses
   e_log, e_msg, ENet, Classes;
 
 const
-  NET_PROTOCOL_VER = 172;
+  NET_PROTOCOL_VER = 173;
 
   NET_MAXCLIENTS = 24;
   NET_CHANS = 11;
index 21046a048064e930129811bc684715206448f37c..8b9aa1e05da1bb65c9ac50872d49f3dd4fd944bc 100644 (file)
@@ -640,6 +640,13 @@ procedure MH_SEND_Everything(CreatePlayers: Boolean = False; ID: Integer = NET_E
     MH_SEND_MonsterSpawn(mon.UID, ID);
   end;
 
+  function sendPanelState (pan: TPanel): Boolean;
+  begin
+    result := false; // don't stop
+    MH_SEND_PanelState(pan.PanelType, pan.guid, ID); // anyway, to sync mplats
+    if (pan.GetTextureCount > 1) then MH_SEND_PanelTexture(pan.PanelType, pan.guid, pan.LastAnimLoop, ID);
+  end;
+
 var
   I: Integer;
 begin
@@ -654,7 +661,9 @@ begin
         MH_SEND_PlayerStats(gPlayers[I].UID, ID);
 
         if (gPlayers[I].Flag <> FLAG_NONE) and (gGameSettings.GameMode = GM_CTF) then
+        begin
           MH_SEND_FlagEvent(FLAG_STATE_CAPTURED, gPlayers[I].Flag, gPlayers[I].UID, True, ID);
+        end;
       end;
     end;
   end;
@@ -662,12 +671,15 @@ begin
   g_Items_ForEachAlive(sendItemRespawn, true); // backwards
   g_Mons_ForEach(sendMonSpawn);
 
+  g_Map_ForEachPanel(sendPanelState);
+
+  (* replaced with the `g_Map_ForEachPanel()` call above
   if gWalls <> nil then
     for I := Low(gWalls) to High(gWalls) do
       if gWalls[I] <> nil then
         with gWalls[I] do
         begin
-          if Door then
+          {if Door then} // anyway, to sync mplats
             MH_SEND_PanelState(PanelType, I, ID);
 
           if GetTextureCount > 1 then
@@ -687,7 +699,7 @@ begin
         begin
           if (GetTextureCount > 1) then
             MH_SEND_PanelTexture(PanelType, I, LastAnimLoop, ID);
-          if Moved then
+          {if Moved then} // anyway, to sync mplats
             MH_SEND_PanelState(PanelType, I, ID);
         end;
   if gRenderBackgrounds <> nil then
@@ -697,33 +709,50 @@ begin
         begin
           if (GetTextureCount > 1) then
             MH_SEND_PanelTexture(PanelType, I, LastAnimLoop, ID);
-          if Moved then
+          {if Moved then} // anyway, to sync mplats
             MH_SEND_PanelState(PanelType, I, ID);
         end;
   if gWater <> nil then
     for I := Low(gWater) to High(gWater) do
       if gWater[I] <> nil then
         with gWater[I] do
+        begin
           if GetTextureCount > 1 then
             MH_SEND_PanelTexture(PanelType, I, LastAnimLoop, ID);
+          {if Moved then} // anyway, to sync mplats
+            MH_SEND_PanelState(PanelType, I, ID);
+        end;
   if gAcid1 <> nil then
     for I := Low(gAcid1) to High(gAcid1) do
       if gAcid1[I] <> nil then
         with gAcid1[I] do
+        begin
           if GetTextureCount > 1 then
             MH_SEND_PanelTexture(PanelType, I, LastAnimLoop, ID);
+          {if Moved then} // anyway, to sync mplats
+            MH_SEND_PanelState(PanelType, I, ID);
+        end;
   if gAcid2 <> nil then
     for I := Low(gAcid2) to High(gAcid2) do
       if gAcid2[I] <> nil then
         with gAcid2[I] do
+        begin
           if GetTextureCount > 1 then
             MH_SEND_PanelTexture(PanelType, I, LastAnimLoop, ID);
+          {if Moved then} // anyway, to sync mplats
+            MH_SEND_PanelState(PanelType, I, ID);
+        end;
   if gSteps <> nil then
     for I := Low(gSteps) to High(gSteps) do
       if gSteps[I] <> nil then
         with gSteps[I] do
+        begin
           if GetTextureCount > 1 then
             MH_SEND_PanelTexture(PanelType, I, LastAnimLoop, ID);
+          {if Moved then} // anyway, to sync mplats
+            MH_SEND_PanelState(PanelType, I, ID);
+        end;
+  *)
 
   if gTriggers <> nil then
     for I := Low(gTriggers) to High(gTriggers) do
@@ -1261,6 +1290,7 @@ end;
 procedure MH_SEND_PanelState(PType: Word; PGUID: Integer; ID: Integer = NET_EVERYONE);
 var
   TP: TPanel;
+  mpflags: Byte = 0;
 begin
   TP := g_Map_PanelByGUID(PGUID);
   if (TP = nil) then exit;
@@ -1292,6 +1322,22 @@ begin
   NetOut.Write(TP.LiftType);
   NetOut.Write(TP.X);
   NetOut.Write(TP.Y);
+  NetOut.Write(Word(TP.Width));
+  NetOut.Write(Word(TP.Height));
+  // mplats
+  NetOut.Write(LongInt(TP.movingSpeedX));
+  NetOut.Write(LongInt(TP.movingSpeedY));
+  NetOut.Write(LongInt(TP.movingStartX));
+  NetOut.Write(LongInt(TP.movingStartY));
+  NetOut.Write(LongInt(TP.movingEndX));
+  NetOut.Write(LongInt(TP.movingEndY));
+  NetOut.Write(LongInt(TP.sizeSpeedX));
+  NetOut.Write(LongInt(TP.sizeSpeedY));
+  NetOut.Write(LongInt(TP.sizeEndX));
+  NetOut.Write(LongInt(TP.sizeEndY));
+  if TP.movingActive then mpflags := mpflags or 1;
+  if TP.moveOnce then mpflags := mpflags or 2;
+  NetOut.Write(Byte(mpflags));
 
   g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
 end;
@@ -2439,7 +2485,7 @@ begin
   end;
   }
 
-  if TP <> nil then
+  if (TP <> nil) then
   begin
     if Loop = 0 then
     begin
@@ -2461,8 +2507,11 @@ var
   E: Boolean;
   Lift: Byte;
   PType: Word;
-  X, Y: Integer;
+  X, Y, W, H: Integer;
   TP: TPanel;
+  speedX, speedY, startX, startY, endX, endY: Integer;
+  sizeSpX, sizeSpY, sizeEX, sizeEY: Integer;
+  mpflags: Byte;
 begin
   if not gGameOn then Exit;
   PType := M.ReadWord();
@@ -2471,16 +2520,31 @@ begin
   Lift := M.ReadByte();
   X := M.ReadLongInt();
   Y := M.ReadLongInt();
+  W := M.ReadWord();
+  H := M.ReadWord();
+  // mplats
+  speedX := M.ReadLongInt();
+  speedY := M.ReadLongInt();
+  startX := M.ReadLongInt();
+  startY := M.ReadLongInt();
+  endX := M.ReadLongInt();
+  endY := M.ReadLongInt();
+  sizeSpX := M.ReadLongInt();
+  sizeSpY := M.ReadLongInt();
+  sizeEX := M.ReadLongInt();
+  sizeEY := M.ReadLongInt();
+  mpflags := M.ReadByte(); // bit0: TP.movingActive; bit1: TP.moveOnce
 
   case PType of
-    PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR:
-      if E then g_Map_EnableWallGUID(PGUID) else g_Map_DisableWallGUID(PGUID);
+    {PANEL_WALL, PANEL_OPENDOOR, PANEL_CLOSEDOOR:
+      if E then g_Map_EnableWallGUID(PGUID) else g_Map_DisableWallGUID(PGUID);}
 
     PANEL_LIFTUP, PANEL_LIFTDOWN, PANEL_LIFTLEFT, PANEL_LIFTRIGHT:
       g_Map_SetLiftGUID(PGUID, Lift);
 
     {PANEL_BACK,
     PANEL_FORE:}
+    {
     else
     begin
       TP := g_Map_PanelByGUID(PGUID);
@@ -2493,6 +2557,7 @@ begin
       //gRenderBackgrounds[ID].X := X;
       //gRenderBackgrounds[ID].Y := Y;
     end;
+    }
 
     {
     PANEL_FORE:
@@ -2502,6 +2567,34 @@ begin
     end;
     }
   end;
+
+  // update enabled/disabled state for all panels
+  if E then g_Map_EnableWallGUID(PGUID) else g_Map_DisableWallGUID(PGUID);
+
+  // update panel position, as it can be moved
+  TP := g_Map_PanelByGUID(PGUID);
+  if (TP <> nil) then
+  begin
+    // mplat
+    TP.movingSpeedX := speedX;
+    TP.movingSpeedY := speedY;
+    TP.movingStartX := startX;
+    TP.movingStartY := startY;
+    TP.movingEndX := endX;
+    TP.movingEndY := endY;
+    TP.sizeSpeedX := sizeSpX;
+    TP.sizeSpeedY := sizeSpY;
+    TP.sizeEndX := sizeEX;
+    TP.sizeEndY := sizeEY;
+    TP.movingActive := ((mpflags and 1) <> 0);
+    TP.moveOnce := ((mpflags and 2) <> 0);
+    // position
+    TP.X := X;
+    TP.Y := Y;
+    TP.Width := W;
+    TP.Height := H;
+    TP.positionChanged();
+  end;
 end;
 
 // TRIGGERS
index d3101aa0e1b6e0f1c7102b458f7b61db447d2280..00407dbd68921463601b5b932ae4cd444d9d07d6 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;
@@ -127,6 +141,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
 
@@ -157,6 +175,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 +195,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;
@@ -219,6 +245,7 @@ begin
   mMovingStart := PanelRec.moveStart;
   mMovingEnd := PanelRec.moveEnd;
   mMovingActive := PanelRec['move_active'].varvalue;
+  mOldMovingActive := mMovingActive;
   mMoveOnce := PanelRec.moveOnce;
 
   mSizeSpeed := PanelRec.sizeSpeed;
@@ -227,6 +254,8 @@ begin
   mEndPosTrig := PanelRec.endPosTrig;
   mEndSizeTrig := PanelRec.endSizeTrig;
 
+  mNeedSend := false;
+
 // Òèï ïàíåëè:
   PanelType := PanelRec.PanelType;
   Enabled := True;
@@ -386,6 +415,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,7 +435,11 @@ 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;
@@ -480,7 +523,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_dbg_scale <> 1.0) or g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight)) then
   begin
     if not FTextureIDs[FCurTexture].Anim then
     begin
@@ -672,6 +716,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;
 
@@ -697,6 +744,21 @@ begin
     nx := ox+mMovingSpeed.X;
     ny := oy+mMovingSpeed.Y;
 
+    // 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
+      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;
+
     // if pannel disappeared, we don't have to do anything
     if (nw > 0) and (nh > 0) then
     begin
@@ -788,8 +850,12 @@ begin
             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 player, if necessary
+            // squash monster, if necessary
             if not g_Game_IsClient and squash then mon.Damage(15000, 0, 0, 0, HIT_TRAP);
           end;
         end;
@@ -809,6 +875,8 @@ begin
     actMoveTrig := false;
     actSizeTrig := false;
 
+    // `mNeedSend` was set above
+
     // check "size stop"
     if not mSizeSpeed.isZero and (nw = mSizeEnd.w) and (nh = mSizeEnd.h) then
     begin
@@ -816,7 +884,6 @@ begin
       mSizeSpeed.h := 0;
       actSizeTrig := true;
       if (nw < 1) or (nh < 1) then mMovingActive := false; //HACK!
-      //e_LogWritefln('FUUUUUUUUUUUUUU', []);
     end;
 
     // reverse moving direction, if necessary
@@ -832,9 +899,19 @@ begin
       actMoveTrig := true;
     end;
 
+    if (mOldMovingActive <> mMovingActive) then mNeedSend := true;
+    mOldMovingActive := mMovingActive;
 
-    if actMoveTrig then g_Triggers_Press(mEndPosTrig, ACTIVATE_CUSTOM);
-    if actSizeTrig then g_Triggers_Press(mEndSizeTrig, ACTIVATE_CUSTOM);
+    if g_Game_IsServer and g_Game_IsNet then
+    begin
+      if actMoveTrig then g_Triggers_Press(mEndPosTrig, ACTIVATE_CUSTOM);
+      if actSizeTrig then 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;
 
@@ -970,10 +1047,14 @@ 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;
@@ -981,6 +1062,8 @@ begin
 // Ñèãíàòóðà ïàíåëè:
   sig := PANEL_SIGNATURE; // 'PANL'
   Mem.WriteDWORD(sig);
+  ver := PAN_SAVE_VERSION;
+  Mem.WriteByte(ver);
 // Îòêðûòà/çàêðûòà, åñëè äâåðü:
   Mem.WriteBoolean(FEnabled);
 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò:
@@ -990,6 +1073,8 @@ begin
 // Êîîðäû
   Mem.WriteInt(FX);
   Mem.WriteInt(FY);
+  Mem.WriteWord(FWidth);
+  Mem.WriteWord(FHeight);
 // Àíèìèðîâàííàÿ ëè òåêóùàÿ òåêñòóðà:
   if (FCurTexture >= 0) and (FTextureIDs[FCurTexture].Anim) then
     begin
@@ -1003,6 +1088,7 @@ begin
 // Åñëè äà - ñîõðàíÿåì àíèìàöèþ:
   if anim then
     FTextureIDs[FCurTexture].AnTex.SaveState(Mem);
+
   // moving platform state
   Mem.WriteInt(mMovingSpeed.X);
   Mem.WriteInt(mMovingSpeed.Y);
@@ -1010,13 +1096,23 @@ 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;
@@ -1024,10 +1120,9 @@ begin
 
 // Ñèãíàòóðà ïàíåëè:
   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);
 // Íàïðàâëåíèå ëèôòà, åñëè ëèôò:
@@ -1039,6 +1134,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);
@@ -1051,6 +1148,7 @@ begin
            'TPanel.LoadState: No animation object');
     FTextureIDs[FCurTexture].AnTex.LoadState(Mem);
   end;
+
   // moving platform state
   Mem.ReadInt(mMovingSpeed.X);
   Mem.ReadInt(mMovingSpeed.Y);
@@ -1058,7 +1156,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
index bf56c6eeb0afb6b227e1dcbee88595b806f49489..93c392ce8804dc298ccdbc9b9a5fa9be42037392 100644 (file)
@@ -39,7 +39,7 @@ uses
 
 const
   SAVE_SIGNATURE = $56534644; // 'DFSV'
-  SAVE_VERSION = $05;
+  SAVE_VERSION = $06;
   END_MARKER_STRING = 'END';
   PLAYER_VIEW_SIGNATURE = $57564C50; // 'PLVW'
   OBJ_SIGNATURE = $4A424F5F; // '_OBJ'