6060#include < string.h>
6161#include < assetmgr.h>
6262#include < texture.h>
63+ #include " Common/FramePacer.h"
6364#include " Common/MapReaderWriterInfo.h"
6465#include " Common/FileSystem.h"
6566#include " Common/file.h"
@@ -1274,8 +1275,6 @@ void W3DTreeBuffer::clearAllTrees(void)
12741275 m_areaPartition[i] = END_OF_PARTITION;
12751276 }
12761277 m_numTreeTypes = 0 ;
1277-
1278- m_lastLogicFrame = 0 ;
12791278}
12801279
12811280// =============================================================================
@@ -1537,34 +1536,20 @@ void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pD
15371536 // if breeze changes, always process the full update, even if not visible,
15381537 // so that things offscreen won't 'pop' when first viewed
15391538 const BreezeInfo& info = TheScriptEngine->getBreezeInfo ();
1540- Bool pause = TheScriptEngine->isTimeFrozenScript () || TheScriptEngine->isTimeFrozenDebug ();
1541- if (TheGameLogic && TheGameLogic->isGamePaused ()) {
1542- pause = true ;
1543- }
1544-
1545- // TheSuperHackers @bugfix Mauller 04/07/2025 decouple the tree sway position updates from the client fps
1546- if (TheGameLogic) {
1547- UnsignedInt currentFrame = TheGameLogic->getFrame ();
1548- if (m_lastLogicFrame == currentFrame) {
1549- pause = true ;
1550- }
1551- m_lastLogicFrame = currentFrame;
1539+ if (info.m_breezeVersion != m_curSwayVersion)
1540+ {
1541+ updateSway (info);
15521542 }
15531543
1554- Int i;
1555- if (!pause) {
1556- if (info.m_breezeVersion != m_curSwayVersion)
1557- {
1558- updateSway (info);
1559- }
1560- }
1544+ // TheSuperHackers @tweak The tree sway, topple and sink time steps are now decoupled from the render update.
1545+ const Real timeScale = TheFramePacer->getActualLogicTimeScaleOverFpsRatio ();
15611546 Vector3 swayFactor[MAX_SWAY_TYPES];
1562- for (i= 0 ; i<MAX_SWAY_TYPES; i++) {
1563- if (!pause) {
1564- m_curSwayOffset[i] += m_curSwayStep[i];
1565- if ( m_curSwayOffset[i] > NUM_SWAY_ENTRIES- 1 ) {
1566- m_curSwayOffset[i] -= NUM_SWAY_ENTRIES-1 ;
1567- }
1547+ Int i;
1548+ for (i= 0 ; i<MAX_SWAY_TYPES; i++)
1549+ {
1550+ m_curSwayOffset[i] += m_curSwayStep[i] * timeScale;
1551+ if ( m_curSwayOffset[i] > NUM_SWAY_ENTRIES-1 ) {
1552+ m_curSwayOffset[i] -= NUM_SWAY_ENTRIES- 1 ;
15681553 }
15691554 Int minOffset = REAL_TO_INT_FLOOR (m_curSwayOffset[i]);
15701555 if (minOffset>=0 && minOffset+1 <NUM_SWAY_ENTRIES) {
@@ -1613,30 +1598,29 @@ void W3DTreeBuffer::drawTrees(CameraClass * camera, RefRenderObjListIterator *pD
16131598
16141599 // Update pushed aside and toppling trees.
16151600 for (curTree=0 ; curTree<m_numTrees; curTree++) {
1616- if (pause) {
1617- break ;
1618- }
16191601 Int type = m_trees[curTree].treeType ;
16201602 if (type<0 ) { // deleted.
16211603 continue ;
16221604 }
1605+ const W3DTreeDrawModuleData *moduleData = m_treeTypes[type].m_data ;
16231606 if (m_trees[curTree].m_toppleState == TOPPLE_FALLING ||
16241607 m_trees[curTree].m_toppleState == TOPPLE_FOGGED) {
1625- updateTopplingTree (m_trees+curTree);
1608+ updateTopplingTree (m_trees+curTree, timeScale );
16261609 } else if (m_trees[curTree].m_toppleState == TOPPLE_DOWN) {
1627- if (m_treeTypes[type]. m_data ->m_killWhenToppled ) {
1628- if (m_trees[curTree].m_sinkFramesLeft == 0 ) {
1610+ if (moduleData ->m_killWhenToppled ) {
1611+ if (m_trees[curTree].m_sinkFramesLeft <= 0 . 0f ) {
16291612 m_trees[curTree].treeType = DELETED_TREE_TYPE; // delete it. [7/11/2003]
16301613 m_anythingChanged = true ; // need to regenerate trees. [7/11/2003]
16311614 }
1632- m_trees[curTree].m_sinkFramesLeft --;
1633- m_trees[curTree].location .Z -= m_treeTypes[type].m_data ->m_sinkDistance /m_treeTypes[type].m_data ->m_sinkFrames ;
1615+ const Real sinkDistancePerFrame = moduleData->m_sinkDistance / moduleData->m_sinkFrames ;
1616+ m_trees[curTree].m_sinkFramesLeft -= timeScale;
1617+ m_trees[curTree].location .Z -= sinkDistancePerFrame * timeScale;
16341618 m_trees[curTree].m_mtx .Set_Translation (m_trees[curTree].location );
16351619 }
16361620 } else if (m_trees[curTree].pushAsideDelta !=0 .0f ) {
16371621 m_trees[curTree].pushAside += m_trees[curTree].pushAsideDelta ;
16381622 if (m_trees[curTree].pushAside >=1 .0f ) {
1639- m_trees[curTree].pushAsideDelta = -1.0 /(Real)m_treeTypes[type]. m_data ->m_framesToMoveInward ;
1623+ m_trees[curTree].pushAsideDelta = -1 .0f /(Real)moduleData ->m_framesToMoveInward ;
16401624 } else if (m_trees[curTree].pushAside <=0 .0f ) {
16411625 m_trees[curTree].pushAsideDelta = 0 .0f ;
16421626 m_trees[curTree].pushAside = 0 .0f ;
@@ -1856,7 +1840,7 @@ static const Real ANGULAR_LIMIT = PI/2 - PI/64;
18561840// -------------------------------------------------------------------------------------------------
18571841// /< Keep track of rotational fall distance, bounce and/or stop when needed.
18581842// -------------------------------------------------------------------------------------------------
1859- void W3DTreeBuffer::updateTopplingTree (TTree *tree)
1843+ void W3DTreeBuffer::updateTopplingTree (TTree *tree, Real timeScale )
18601844{
18611845 // DLOG(Debug::Format("updating W3DTreeBuffer %08lx\n",this));
18621846 DEBUG_ASSERTCRASH (tree->m_toppleState != TOPPLE_UPRIGHT, (" hmm, we should be sleeping here" ));
@@ -1880,21 +1864,20 @@ void W3DTreeBuffer::updateTopplingTree(TTree *tree)
18801864 tree->m_mtx .In_Place_Pre_Rotate_Y (ANGULAR_LIMIT * tree->m_toppleDirection .x );
18811865 if (d->m_killWhenToppled ) {
18821866 // If got killed in the fog, just remove. jba [8/11/2003]
1883- tree->m_sinkFramesLeft = 0 ;
1867+ tree->m_sinkFramesLeft = 0 . 0f ;
18841868 }
18851869 return ;
18861870 }
18871871 const Real VELOCITY_BOUNCE_LIMIT = 0 .01f ; // if the velocity after a bounce will be this or lower, just stop at zero
18881872 const Real VELOCITY_BOUNCE_SOUND_LIMIT = 0 .03f ; // and if this low, then skip the bounce sound
18891873
1890- Real curVelToUse = tree->m_angularVelocity ;
1874+ Real curVelToUse = tree->m_angularVelocity * timeScale ;
18911875 if (tree->m_angularAccumulation + curVelToUse > ANGULAR_LIMIT)
18921876 curVelToUse = ANGULAR_LIMIT - tree->m_angularAccumulation ;
18931877
18941878 tree->m_mtx .In_Place_Pre_Rotate_X (-curVelToUse * tree->m_toppleDirection .y );
18951879 tree->m_mtx .In_Place_Pre_Rotate_Y (curVelToUse * tree->m_toppleDirection .x );
18961880
1897-
18981881 tree->m_angularAccumulation += curVelToUse;
18991882 if ((tree->m_angularAccumulation >= ANGULAR_LIMIT) && (tree->m_angularVelocity > 0 ))
19001883 {
@@ -1926,7 +1909,7 @@ void W3DTreeBuffer::updateTopplingTree(TTree *tree)
19261909 }
19271910 else
19281911 {
1929- tree->m_angularVelocity += tree->m_angularAcceleration ;
1912+ tree->m_angularVelocity += tree->m_angularAcceleration * timeScale ;
19301913 }
19311914
19321915}
@@ -1948,7 +1931,11 @@ void W3DTreeBuffer::xfer( Xfer *xfer )
19481931{
19491932
19501933 // version
1934+ #if RETAIL_COMPATIBLE_XFER_SAVE
19511935 XferVersion currentVersion = 1 ;
1936+ #else
1937+ XferVersion currentVersion = 2 ;
1938+ #endif
19521939 XferVersion version = currentVersion;
19531940 xfer->xferVersion ( &version, currentVersion );
19541941
@@ -2008,7 +1995,17 @@ void W3DTreeBuffer::xfer( Xfer *xfer )
20081995 xfer->xferReal (&tree.m_angularAccumulation ); // /< How much have I rotated so I know when to bounce.
20091996 xfer->xferUnsignedInt (&tree.m_options ); // /< topple options
20101997 xfer->xferMatrix3D (&tree.m_mtx );
2011- xfer->xferUnsignedInt (&tree.m_sinkFramesLeft ); // /< Toppled trees sink into the terrain & disappear, how many frames left.
1998+
1999+ if (version <= 1 )
2000+ {
2001+ UnsignedInt sinkFramesLeft = (UnsignedInt)tree.m_sinkFramesLeft ;
2002+ xfer->xferUnsignedInt (&sinkFramesLeft); // /< Toppled trees sink into the terrain & disappear, how many frames left.
2003+ tree.m_sinkFramesLeft = (Real)sinkFramesLeft;
2004+ }
2005+ else
2006+ {
2007+ xfer->xferReal (&tree.m_sinkFramesLeft ); // /< Toppled trees sink into the terrain & disappear, how many frames left.
2008+ }
20122009
20132010 if (xfer->getXferMode () == XFER_LOAD && treeType != DELETED_TREE_TYPE && treeType < m_numTreeTypes) {
20142011 Coord3D pos;
0 commit comments