diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index 0034feabdca..2736ad0ff4f 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -18,6 +18,8 @@ #pragma once +#include "WWDefines.h" + // Note: Retail compatibility must not be broken before this project officially does. // Use RETAIL_COMPATIBLE_CRC and RETAIL_COMPATIBLE_XFER_SAVE to guard breaking changes. diff --git a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt index 62d95116929..b81bac059b7 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt @@ -133,6 +133,7 @@ set(WWLIB_SRC widestring.h win.h WWCommon.h + WWDefines.h wwfile.cpp WWFILE.H wwstring.cpp diff --git a/Core/Libraries/Source/WWVegas/WWLib/WWCommon.h b/Core/Libraries/Source/WWVegas/WWLib/WWCommon.h index 77218a12e2a..859eb54496e 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/WWCommon.h +++ b/Core/Libraries/Source/WWVegas/WWLib/WWCommon.h @@ -18,7 +18,7 @@ #pragma once -#include +#include "stringex.h" #if defined(_MSC_VER) && _MSC_VER < 1300 typedef unsigned MemValueType; diff --git a/Core/Libraries/Source/WWVegas/WWLib/WWDefines.h b/Core/Libraries/Source/WWVegas/WWLib/WWDefines.h new file mode 100644 index 00000000000..d7acdd7b4ed --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WWLib/WWDefines.h @@ -0,0 +1,25 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** 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 . +*/ + +#pragma once + +// The WW3D Sync time. This was originally 33 ms, ~30 fps, integer. +// Changing or removing this will require tweaking all Drawable code that concerns logic time step, including locomotion physics. +#ifndef MSEC_PER_WWSYNC_FRAME +#define MSEC_PER_WWSYNC_FRAME (33) +#endif diff --git a/Core/Libraries/Source/WWVegas/WWLib/always.h b/Core/Libraries/Source/WWVegas/WWLib/always.h index 3ce54d3d9f6..d9c912b462e 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/always.h +++ b/Core/Libraries/Source/WWVegas/WWLib/always.h @@ -41,6 +41,7 @@ #define ALWAYS_H #include "WWCommon.h" +#include "WWDefines.h" #include #include diff --git a/Generals/Code/GameEngine/Include/Common/GameEngine.h b/Generals/Code/GameEngine/Include/Common/GameEngine.h index 7f0dc1757a0..57852123860 100644 --- a/Generals/Code/GameEngine/Include/Common/GameEngine.h +++ b/Generals/Code/GameEngine/Include/Common/GameEngine.h @@ -53,11 +53,16 @@ class Radar; class WebBrowser; class ParticleSystemManager; -/** - * The implementation of the game engine - */ class GameEngine : public SubsystemInterface { +public: + + typedef UnsignedInt LogicTimeQueryFlags; + enum LogicTimeQueryFlags_ CPP_11(: LogicTimeQueryFlags) + { + IgnoreFrozenTime = 1<<0, // Ignore frozen time for the query + IgnoreHaltedGame = 1<<1, // Ignore halted game for the query + }; public: @@ -76,13 +81,18 @@ class GameEngine : public SubsystemInterface Real getUpdateTime(); ///< Get the last engine update delta time. Real getUpdateFps(); ///< Get the last engine update fps. + static Bool isTimeFrozen(); ///< Returns true if a script has frozen time. + static Bool isGameHalted(); ///< Returns true if the game is paused or the network is stalling. + virtual void setLogicTimeScaleFps( Int fps ); ///< Set the logic time scale fps and therefore scale the simulation time. Is capped by the max render fps and does not apply to network matches. virtual Int getLogicTimeScaleFps(); ///< Get the raw logic time scale fps value. virtual void enableLogicTimeScale( Bool enable ); ///< Enable the logic time scale setup. If disabled, the simulation time scale is bound to the render frame time or network update time. virtual Bool isLogicTimeScaleEnabled(); ///< Check whether the logic time scale setup is enabled. - Int getActualLogicTimeScaleFps(); ///< Get the real logic time scale fps, depending on the max render fps, network state and enabled state. - Real getActualLogicTimeScaleRatio(); ///< Get the real logic time scale ratio, depending on the max render fps, network state and enabled state. - Real getActualLogicTimeScaleOverFpsRatio(); ///< Get the real logic time scale over render fps ratio, used to scale down steps in render updates to match logic updates. + Int getActualLogicTimeScaleFps(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale fps, depending on the max render fps, network state and enabled state. + Real getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale ratio, depending on the max render fps, network state and enabled state. + Real getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale over render fps ratio, used to scale down steps in render updates to match logic updates. + Real getLogicTimeStepSeconds(LogicTimeQueryFlags flags = 0); ///< Get the logic time step in seconds + Real getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags = 0); ///< Get the logic time step in milliseconds virtual void setQuitting( Bool quitting ); ///< set quitting status virtual Bool getQuitting(void); ///< is app getting ready to quit. @@ -97,6 +107,10 @@ class GameEngine : public SubsystemInterface virtual void resetSubsystems( void ); + Bool canUpdateGameLogic(); + Bool canUpdateNetworkGameLogic(); + Bool canUpdateRegularGameLogic(); + virtual FileSystem *createFileSystem( void ); ///< Factory for FileSystem classes virtual LocalFileSystem *createLocalFileSystem( void ) = 0; ///< Factory for LocalFileSystem classes virtual ArchiveFileSystem *createArchiveFileSystem( void ) = 0; ///< Factory for ArchiveFileSystem classes @@ -117,11 +131,14 @@ class GameEngine : public SubsystemInterface Real m_updateTime; ///< Last engine update delta time Real m_logicTimeAccumulator; ///< Frame time accumulated towards submitting a new logic frame - Bool m_quitting; ///< true when we need to quit the game - Bool m_isActive; ///< app has OS focus. + Bool m_quitting; ///< true when we need to quit the game + Bool m_isActive; ///< app has OS focus. Bool m_enableLogicTimeScale; + Bool m_isTimeFrozen; + Bool m_isGameHalted; }; + inline void GameEngine::setQuitting( Bool quitting ) { m_quitting = quitting; } inline Bool GameEngine::getQuitting(void) { return m_quitting; } diff --git a/Generals/Code/GameEngine/Include/GameClient/Drawable.h b/Generals/Code/GameEngine/Include/GameClient/Drawable.h index d9c52dd4d0d..557c7cf000c 100644 --- a/Generals/Code/GameEngine/Include/GameClient/Drawable.h +++ b/Generals/Code/GameEngine/Include/GameClient/Drawable.h @@ -390,7 +390,7 @@ class Drawable : public Thing, const Matrix3D *getTransformMatrix( void ) const; ///< return the world transform - void draw( View *view ); ///< render the drawable to the given view + void draw(); ///< render the drawable to the given view void updateDrawable(); ///< update the drawable void drawIconUI( void ); ///< draw "icon"(s) needed on drawable (health bars, veterency, etc) @@ -600,6 +600,8 @@ class Drawable : public Thing, private: + const Locomotor* getLocomotor() const; + // note, these are lazily allocated! TintEnvelope* m_selectionFlashEnvelope; ///< used for selection flash, works WITH m_colorTintEnvelope TintEnvelope* m_colorTintEnvelope; ///< house color flashing, etc... works WITH m_selectionFlashEnvelope @@ -644,6 +646,8 @@ class Drawable : public Thing, DrawableLocoInfo* m_locoInfo; // lazily allocated + PhysicsXformInfo* m_physicsXform; + DynamicAudioEventRTS* m_ambientSound; ///< sound module for ambient sound (lazily allocated) Bool m_ambientSoundEnabled; diff --git a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h index 70a4d836497..9879667a4fc 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/Generals/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -197,6 +197,7 @@ class GameLogic : public SubsystemInterface, public Snapshot void updateObjectsChangedTriggerAreas(void) {m_frameObjectsChangedTriggerAreas = m_frame;} UnsignedInt getFrameObjectsChangedTriggerAreas(void) {return m_frameObjectsChangedTriggerAreas;} + void exitGame(); void clearGameData(Bool showScoreScreen = TRUE); ///< Clear the game data void closeWindows( void ); @@ -205,7 +206,7 @@ class GameLogic : public SubsystemInterface, public Snapshot void bindObjectAndDrawable(Object* obj, Drawable* draw); - void setGamePausedInFrame( UnsignedInt frame ); + void setGamePausedInFrame( UnsignedInt frame, Bool disableLogicTimeScale ); UnsignedInt getGamePauseFrame() const { return m_pauseFrame; } void setGamePaused( Bool paused, Bool pauseMusic = TRUE, Bool pauseInput = TRUE ); Bool isGamePaused( void ); @@ -352,6 +353,7 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool m_pauseInput; Bool m_inputEnabledMemory;// Latches used to remember what to restore to after we unpause Bool m_mouseVisibleMemory; + Bool m_logicTimeScaleEnabledMemory; Bool m_progressComplete[MAX_SLOTS]; enum { PROGRESS_COMPLETE_TIMEOUT = 60000 }; ///< Timeout we wait for when we've completed our Load diff --git a/Generals/Code/GameEngine/Include/GameNetwork/NetworkInterface.h b/Generals/Code/GameEngine/Include/GameNetwork/NetworkInterface.h index 5cf9db18706..d1b58ad019e 100644 --- a/Generals/Code/GameEngine/Include/GameNetwork/NetworkInterface.h +++ b/Generals/Code/GameEngine/Include/GameNetwork/NetworkInterface.h @@ -68,6 +68,7 @@ class NetworkInterface : public SubsystemInterface virtual void setLocalAddress(UnsignedInt ip, UnsignedInt port) = 0; ///< Tell the network what local ip and port to bind to. virtual Bool isFrameDataReady( void ) = 0; ///< Are the commands for the next frame available? + virtual Bool isStalling() = 0; virtual void parseUserList( const GameInfo *game ) = 0; ///< Parse a userlist, creating connections virtual void startGame(void) = 0; ///< Sets the network game frame counter to -1 virtual UnsignedInt getRunAhead(void) = 0; ///< Get the current RunAhead value diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp index 8f3d23e0b35..999dc3c517c 100644 --- a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp @@ -260,6 +260,8 @@ GameEngine::GameEngine( void ) m_quitting = FALSE; m_isActive = FALSE; m_enableLogicTimeScale = FALSE; + m_isTimeFrozen = FALSE; + m_isGameHalted = FALSE; _Module.Init(NULL, ApplicationHInstance, NULL); } @@ -335,6 +337,45 @@ Real GameEngine::getUpdateFps() return 1.0f / m_updateTime; } +//------------------------------------------------------------------------------------------------- +Bool GameEngine::isTimeFrozen() +{ + // TheSuperHackers @fix The time can no longer be frozen in Network games. It would disconnect the player. + if (TheNetwork != NULL) + return false; + + if (TheTacticalView != NULL) + { + if (TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished()) + return true; + } + + if (TheScriptEngine != NULL) + { + if (TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript()) + return true; + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +Bool GameEngine::isGameHalted() +{ + if (TheNetwork != NULL) + { + if (TheNetwork->isStalling()) + return true; + } + else + { + if (TheGameLogic != NULL && TheGameLogic->isGamePaused()) + return true; + } + + return false; +} + //------------------------------------------------------------------------------------------------- void GameEngine::setLogicTimeScaleFps( Int fps ) { @@ -360,41 +401,53 @@ Bool GameEngine::isLogicTimeScaleEnabled() } //------------------------------------------------------------------------------------------------- -Int GameEngine::getActualLogicTimeScaleFps( void ) +Int GameEngine::getActualLogicTimeScaleFps(LogicTimeQueryFlags flags) { + if (m_isTimeFrozen && (flags & IgnoreFrozenTime) == 0) + { + return 0; + } + + if (m_isGameHalted && (flags & IgnoreHaltedGame) == 0) + { + return 0; + } + if (TheNetwork != NULL) { return TheNetwork->getFrameRate(); } - else - { - const Bool enabled = isLogicTimeScaleEnabled(); - const Int logicTimeScaleFps = getLogicTimeScaleFps(); - const Int maxFps = getFramesPerSecondLimit(); - if (!enabled || logicTimeScaleFps >= maxFps) - { - return getFramesPerSecondLimit(); - } - else - { - return logicTimeScaleFps; - } + if (isLogicTimeScaleEnabled()) + { + return min(getLogicTimeScaleFps(), getFramesPerSecondLimit()); } + + return getFramesPerSecondLimit(); } //------------------------------------------------------------------------------------------------- -Real GameEngine::getActualLogicTimeScaleRatio() +Real GameEngine::getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags) { - return (Real)getActualLogicTimeScaleFps() / LOGICFRAMES_PER_SECONDS_REAL; + return (Real)getActualLogicTimeScaleFps(flags) / LOGICFRAMES_PER_SECONDS_REAL; } //------------------------------------------------------------------------------------------------- -Real GameEngine::getActualLogicTimeScaleOverFpsRatio() +Real GameEngine::getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags) { // TheSuperHackers @info Clamps ratio to min 1, because the logic - // frame rate is (typically) capped by the render frame rate. - return min(1.0f, (Real)getActualLogicTimeScaleFps() / getUpdateFps()); + // frame rate is currently capped by the render frame rate. + return min(1.0f, (Real)getActualLogicTimeScaleFps(flags) / getUpdateFps()); +} + +Real GameEngine::getLogicTimeStepSeconds(LogicTimeQueryFlags flags) +{ + return SECONDS_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); +} + +Real GameEngine::getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags) +{ + return MSEC_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); } /** ----------------------------------------------------------------------------------------------- @@ -696,21 +749,85 @@ void GameEngine::resetSubsystems( void ) TheSubsystemList->resetAll(); } +/// ----------------------------------------------------------------------------------------------- +Bool GameEngine::canUpdateGameLogic() +{ + // Must be first. + TheGameLogic->preUpdate(); + + m_isTimeFrozen = isTimeFrozen(); + m_isGameHalted = isGameHalted(); + + if (TheNetwork != NULL) + { + return canUpdateNetworkGameLogic(); + } + else + { + return canUpdateRegularGameLogic(); + } +} + +Bool GameEngine::canUpdateNetworkGameLogic() +{ + DEBUG_ASSERTCRASH(TheNetwork != NULL, ("TheNetwork is NULL")); + + if (TheNetwork->isFrameDataReady()) + { + // Important: The Network is definitely no longer stalling. + m_isGameHalted = false; + + return true; + } + + return false; +} + +Bool GameEngine::canUpdateRegularGameLogic() +{ + const Bool enabled = isLogicTimeScaleEnabled(); + const Int logicTimeScaleFps = getLogicTimeScaleFps(); + const Int maxRenderFps = getFramesPerSecondLimit(); + +#if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) + const Bool useFastMode = TheGlobalData->m_TiVOFastMode; +#else //always allow this cheat key if we're in a replay game. + const Bool useFastMode = TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame(); +#endif + + if (useFastMode || !enabled || logicTimeScaleFps >= maxRenderFps) + { + // Logic time scale is uncapped or larger equal Render FPS. Update straight away. + return true; + } + else + { + // TheSuperHackers @tweak xezon 06/08/2025 + // The logic time step is now decoupled from the render update. + const Real targetFrameTime = 1.0f / logicTimeScaleFps; + m_logicTimeAccumulator += min(m_updateTime, targetFrameTime); + + if (m_logicTimeAccumulator >= targetFrameTime) + { + m_logicTimeAccumulator -= targetFrameTime; + return true; + } + } + + return false; +} + /// ----------------------------------------------------------------------------------------------- DECLARE_PERF_TIMER(GameEngine_update) /** ----------------------------------------------------------------------------------------------- * Update the game engine by updating the GameClient and GameLogic singletons. - * @todo Allow the client to run as fast as possible, but limit the execution - * of TheNetwork and TheGameLogic to a fixed framerate. */ void GameEngine::update( void ) { USE_PERF_TIMER(GameEngine_update) { - { - // VERIFY CRC needs to be in this code block. Please to not pull TheGameLogic->update() inside this block. VERIFY_CRC @@ -730,55 +847,22 @@ void GameEngine::update( void ) TheCDManager->UPDATE(); } - TheGameLogic->preUpdate(); + const Bool canUpdate = canUpdateGameLogic(); + const Bool canUpdateLogic = canUpdate && !m_isGameHalted && !m_isTimeFrozen; + const Bool canUpdateScript = canUpdate && !m_isGameHalted; - if (TheNetwork != NULL) + if (canUpdateLogic) { - if (TheNetwork->isFrameDataReady()) - { - TheGameClient->step(); - TheGameLogic->UPDATE(); - } + TheGameClient->step(); + TheGameLogic->UPDATE(); } - else + else if (canUpdateScript) { - if (!TheGameLogic->isGamePaused()) - { - const Bool enabled = isLogicTimeScaleEnabled(); - const Int logicTimeScaleFps = getLogicTimeScaleFps(); - const Int maxRenderFps = getFramesPerSecondLimit(); - -#if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) - Bool useFastMode = TheGlobalData->m_TiVOFastMode; -#else //always allow this cheat key if we're in a replay game. - Bool useFastMode = TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame(); -#endif - - if (useFastMode || !enabled || logicTimeScaleFps >= maxRenderFps) - { - // Logic time scale is uncapped or larger equal Render FPS. Update straight away. - TheGameClient->step(); - TheGameLogic->UPDATE(); - } - else - { - // TheSuperHackers @tweak xezon 06/08/2025 - // The logic time step is now decoupled from the render update. - const Real targetFrameTime = 1.0f / logicTimeScaleFps; - m_logicTimeAccumulator += min(m_updateTime, targetFrameTime); - - if (m_logicTimeAccumulator >= targetFrameTime) - { - m_logicTimeAccumulator -= targetFrameTime; - TheGameClient->step(); - TheGameLogic->UPDATE(); - } - } - } + // TheSuperHackers @info Still update the Script Engine to allow + // for scripted camera movements while the time is frozen. + TheScriptEngine->UPDATE(); } - } // end perfGather - } // Horrible reference, but we really, really need to know if we are windowed. diff --git a/Generals/Code/GameEngine/Source/Common/Recorder.cpp b/Generals/Code/GameEngine/Source/Common/Recorder.cpp index 2bd0e64a548..492dcc972c9 100644 --- a/Generals/Code/GameEngine/Source/Common/Recorder.cpp +++ b/Generals/Code/GameEngine/Source/Common/Recorder.cpp @@ -477,7 +477,7 @@ void RecorderClass::stopPlayback() { if (!m_doingAnalysis) { - TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); + TheGameLogic->exitGame(); } } diff --git a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp index a05829feef6..5721ef9f855 100644 --- a/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -77,6 +77,7 @@ #include "GameClient/Shadow.h" #include "GameClient/GameText.h" +#include "ww3d.h" #define VERY_TRANSPARENT_HEATVISION (0.001f) #define HEATVISION_FADE_SCALAR (0.8f) @@ -372,6 +373,7 @@ Drawable::Drawable( const ThingTemplate *thingTemplate, DrawableStatus statusBit m_flashCount = 0; m_locoInfo = NULL; + m_physicsXform = NULL; // sanity if( TheGameClient == NULL || thingTemplate == NULL ) @@ -527,6 +529,8 @@ Drawable::~Drawable() deleteInstance(m_locoInfo); m_locoInfo = NULL; + + delete m_physicsXform; } //------------------------------------------------------------------------------------------------- @@ -1250,24 +1254,19 @@ void Drawable::flashAsSelected( const RGBColor *color ) ///< drawable takes care //------------------------------------------------------------------------------------------------- void Drawable::applyPhysicsXform(Matrix3D* mtx) { - const Object *obj = getObject(); - - if( !obj || obj->isDisabledByType( DISABLED_HELD ) || !TheGlobalData->m_showClientPhysics ) + if (m_physicsXform != NULL) { - return; - } + // TheSuperHackers @tweak Update the physics transform on every WW Sync only. + // All calculations are originally catered to a 30 fps logic step. + if (WW3D::Get_Frame_Time() != 0) + { + calcPhysicsXform(*m_physicsXform); + } - Bool frozen = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - frozen = frozen || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); - if (frozen) - return; - PhysicsXformInfo info; - if (calcPhysicsXform(info)) - { - mtx->Translate(0.0f, 0.0f, info.m_totalZ); - mtx->Rotate_Y( info.m_totalPitch ); - mtx->Rotate_X( -info.m_totalRoll ); - mtx->Rotate_Z( info.m_totalYaw ); + mtx->Translate(0.0f, 0.0f, m_physicsXform->m_totalZ); + mtx->Rotate_Y( m_physicsXform->m_totalPitch ); + mtx->Rotate_X( -m_physicsXform->m_totalRoll ); + mtx->Rotate_Z( m_physicsXform->m_totalYaw ); } } @@ -1275,34 +1274,29 @@ void Drawable::applyPhysicsXform(Matrix3D* mtx) //------------------------------------------------------------------------------------------------- Bool Drawable::calcPhysicsXform(PhysicsXformInfo& info) { - const Object* obj = getObject(); - const AIUpdateInterface *ai = obj ? obj->getAIUpdateInterface() : NULL; Bool hasPhysicsXform = false; - if (ai) + + if (const Locomotor *locomotor = getLocomotor()) { - const Locomotor *locomotor = ai->getCurLocomotor(); - if (locomotor) + switch (locomotor->getAppearance()) { - switch (locomotor->getAppearance()) - { - case LOCO_WHEELS_FOUR: - calcPhysicsXformWheels(locomotor, info); - hasPhysicsXform = true; - break; - case LOCO_TREADS: - calcPhysicsXformTreads(locomotor, info); - hasPhysicsXform = true; - break; - case LOCO_HOVER: - case LOCO_WINGS: - calcPhysicsXformHoverOrWings(locomotor, info); - hasPhysicsXform = true; - break; - case LOCO_THRUST: - calcPhysicsXformThrust(locomotor, info); - hasPhysicsXform = true; - break; - } + case LOCO_WHEELS_FOUR: + calcPhysicsXformWheels(locomotor, info); + hasPhysicsXform = true; + break; + case LOCO_TREADS: + calcPhysicsXformTreads(locomotor, info); + hasPhysicsXform = true; + break; + case LOCO_HOVER: + case LOCO_WINGS: + calcPhysicsXformHoverOrWings(locomotor, info); + hasPhysicsXform = true; + break; + case LOCO_THRUST: + calcPhysicsXformThrust(locomotor, info); + hasPhysicsXform = true; + break; } } @@ -2179,7 +2173,7 @@ void Drawable::setStealthLook(StealthLookType look) //------------------------------------------------------------------------------------------------- /** default draw is to just call the database defined draw */ //------------------------------------------------------------------------------------------------- -void Drawable::draw( View *view ) +void Drawable::draw() { if ( getObject() && getObject()->isEffectivelyDead() ) @@ -2214,7 +2208,10 @@ void Drawable::draw( View *view ) #endif } - applyPhysicsXform(&transformMtx); + if (TheGlobalData->m_showClientPhysics && getObject() && !getObject()->isDisabledByType( DISABLED_HELD )) + { + applyPhysicsXform(&transformMtx); + } for (DrawModule** dm = getDrawModules(); *dm; ++dm) { @@ -3658,6 +3655,14 @@ void Drawable::friend_bindToObject( Object *obj ) ///< bind this drawable to an { (*dm)->onDrawableBoundToObject(); } + + PhysicsXformInfo physicsXform; + if (calcPhysicsXform(physicsXform)) + { + DEBUG_ASSERTCRASH(m_physicsXform == NULL, ("m_physicsXform is not NULL")); + m_physicsXform = new PhysicsXformInfo; + *m_physicsXform = physicsXform; + } } //------------------------------------------------------------------------------------------------- // when our Object changes teams, it calls us to let us know, so @@ -4676,6 +4681,19 @@ void Drawable::loadPostProcess( void ) } // end loadPostProcess +//------------------------------------------------------------------------------------------------- +const Locomotor* Drawable::getLocomotor() const +{ + if (const Object* obj = getObject()) + { + if (const AIUpdateInterface *ai = obj->getAIUpdateInterface()) + { + return ai->getCurLocomotor(); + } + } + return NULL; +} + //================================================================================================= //================================================================================================= #ifdef DIRTY_CONDITION_FLAGS diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/DownloadMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/DownloadMenu.cpp index 442c5513fd4..3d6311318fd 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/DownloadMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/DownloadMenu.cpp @@ -109,7 +109,7 @@ static void successQuitCallback( void ) // Clean up game data. No crashy-crash for you! if (TheGameLogic->isInGame()) - TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); + TheGameLogic->exitGame(); } static void successNoQuitCallback( void ) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp index 83a42e24272..6c4362bdd52 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp @@ -243,7 +243,7 @@ static void quitCallback( void ) TheGameEngine->setQuitting( TRUE ); if (TheGameLogic->isInGame()) - TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); + TheGameLogic->exitGame(); } diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index 31374a6aaa2..6d631038551 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -145,9 +145,7 @@ static void exitQuitMenu() GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); msg->appendBooleanArgument(TRUE); } - /*GameMessage *msg =*/ TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); - if ( !TheGameLogic->isInMultiplayerGame() ) - TheGameLogic->setGamePaused(FALSE); + TheGameLogic->exitGame(); // TheGameLogic->clearGameData(); // display the menu on top of the shell stack // TheShell->showShell(); diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index 594a5c7bbe3..3d79cc90509 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp @@ -535,7 +535,7 @@ void Shell::showShellMap(Bool useShellMap ) return; // we're in some other kind of game, clear it out foo! if(TheGameLogic->isInGame()) - TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); + TheGameLogic->exitGame(); TheWritableGlobalData->m_pendingFile = TheGlobalData->m_shellMapName; InitGameLogicRandom(0); @@ -547,7 +547,7 @@ void Shell::showShellMap(Bool useShellMap ) { // we're in a shell game, stop it! if(TheGameLogic->isInGame() && TheGameLogic->getGameMode() == GAME_SHELL) - TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); + TheGameLogic->exitGame(); // if the shell is active,we need a background if(!m_isShellActive) diff --git a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp index 543ba769e1a..b1d1e6d0433 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -602,17 +602,9 @@ void GameClient::update( void ) TheVideoPlayer->UPDATE(); } - Bool freezeTime = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - freezeTime = freezeTime || TheScriptEngine->isTimeFrozenDebug(); - freezeTime = freezeTime || TheScriptEngine->isTimeFrozenScript(); - freezeTime = freezeTime || TheGameLogic->isGamePaused(); + const Bool freezeTime = TheGameEngine->isTimeFrozen() || TheGameEngine->isGameHalted(); Int localPlayerIndex = ThePlayerList ? ThePlayerList->getLocalPlayer()->getPlayerIndex() : 0; - // hack to let client spin fast in network games but still do effects at the same pace. -MDC - static UnsignedInt lastFrame = ~0; - freezeTime = freezeTime || (lastFrame == m_frame); - lastFrame = m_frame; - if (!freezeTime) { #if ENABLE_CONFIGURABLE_SHROUD diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index ee6d794f70a..978e76218df 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -3328,7 +3328,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage #endif { TheGameLogic->setGamePaused(FALSE); - TheGameLogic->setGamePausedInFrame(TheGameLogic->getFrame() + 1); + TheGameLogic->setGamePausedInFrame(TheGameLogic->getFrame() + 1, TRUE); } break; } diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index fe282f7bc11..71833af2242 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -4807,8 +4807,7 @@ void ScriptEngine::update( void ) if (m_endGameTimer>0) { m_endGameTimer--; if (m_endGameTimer < 1) { - // clear out all the game data - /*GameMessage *msg =*/ TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); + TheGameLogic->exitGame(); //TheScriptActions->closeWindows(FALSE); // Close victory or defeat windows. } } diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 7aceaef728e..483f86677e9 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -241,6 +241,7 @@ GameLogic::GameLogic( void ) m_pauseInput = FALSE; m_inputEnabledMemory = TRUE; m_mouseVisibleMemory = TRUE; + m_logicTimeScaleEnabledMemory = FALSE; m_loadScreen = NULL; m_forceGameStartByTimeOut = FALSE; #ifdef DUMP_PERF_STATS @@ -406,6 +407,8 @@ void GameLogic::init( void ) m_pauseInput = FALSE; m_inputEnabledMemory = TRUE; m_mouseVisibleMemory = TRUE; + m_logicTimeScaleEnabledMemory = FALSE; + for(Int i = 0; i < MAX_SLOTS; ++i) { m_progressComplete[i] = FALSE; @@ -447,6 +450,8 @@ void GameLogic::reset( void ) m_pauseInput = FALSE; m_inputEnabledMemory = TRUE; m_mouseVisibleMemory = TRUE; + m_logicTimeScaleEnabledMemory = FALSE; + setFPMode(); // destroy all objects @@ -3153,7 +3158,6 @@ void GameLogic::update( void ) } // send the current time to the GameClient - DEBUG_ASSERTCRASH(TheGameLogic == this, ("hmm, TheGameLogic is not right")); UnsignedInt now = TheGameLogic->getFrame(); TheGameClient->setFrame(now); @@ -3162,22 +3166,6 @@ void GameLogic::update( void ) TheScriptEngine->UPDATE(); } - Bool freezeTime = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - freezeTime = freezeTime || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); - - if (freezeTime) - { - if (TheCommandList->containsMessageOfType(GameMessage::MSG_CLEAR_GAME_DATA)) - { - TheScriptEngine->forceUnfreezeTime(); - } - else - { - /// @todo - make sure this never happens during a network game. jba. - return; - } - } - // Note - TerrainLogic update needs to happen after ScriptEngine update, but before object updates. jba. // This way changes in bridges are noted in the script engine before being cleared in TerrainLogic->update { @@ -3657,6 +3645,17 @@ UnsignedInt GameLogic::getCRC( Int mode, AsciiString deepCRCFileName ) return theCRC; } +// ------------------------------------------------------------------------------------------------ +void GameLogic::exitGame() +{ + // TheSuperHackers @fix The logic update must not be halted to process the game exit message. + setGamePaused(FALSE); + TheScriptEngine->forceUnfreezeTime(); + TheScriptEngine->doUnfreezeTime(); + + TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); +} + // ------------------------------------------------------------------------------------------------ /** A new GameLogic object has been constructed, therefore create * a corresponding drawable and bind them together. */ @@ -3712,11 +3711,17 @@ Bool GameLogic::isGamePaused( void ) // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ -void GameLogic::setGamePausedInFrame( UnsignedInt frame ) +void GameLogic::setGamePausedInFrame( UnsignedInt frame, Bool disableLogicTimeScale ) { if (frame >= m_frame) { m_pauseFrame = frame; + + if (disableLogicTimeScale) + { + m_logicTimeScaleEnabledMemory = TheGameEngine->isLogicTimeScaleEnabled(); + TheGameEngine->enableLogicTimeScale(FALSE); + } } } @@ -3748,6 +3753,12 @@ void GameLogic::setGamePaused( Bool paused, Bool pauseMusic, Bool pauseInput ) void GameLogic::pauseGameLogic(Bool paused) { m_gamePaused = paused; + + if (!paused && m_logicTimeScaleEnabledMemory) + { + m_logicTimeScaleEnabledMemory = FALSE; + TheGameEngine->enableLogicTimeScale(TRUE); + } } // ------------------------------------------------------------------------------------------------ diff --git a/Generals/Code/GameEngine/Source/GameNetwork/Network.cpp b/Generals/Code/GameEngine/Source/GameNetwork/Network.cpp index e38ad87f5d1..a9fd9dcd926 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/Network.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/Network.cpp @@ -116,6 +116,7 @@ class Network : public NetworkInterface inline UnsignedInt getFrameRate(void) { return m_frameRate; } UnsignedInt getPacketArrivalCushion(void); ///< Returns the smallest packet arrival cushion since this was last called. Bool isFrameDataReady( void ); + virtual Bool isStalling(); void parseUserList( const GameInfo *game ); void startGame(void); ///< Sets the network game frame counter to -1 @@ -740,7 +741,7 @@ void Network::endOfGameCheck() { if (m_conMgr != NULL) { if (m_conMgr->canILeave()) { m_conMgr->disconnectLocalPlayer(); - TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); + TheGameLogic->exitGame(); m_localStatus = NETLOCALSTATUS_POSTGAME; DEBUG_LOG(("Network::endOfGameCheck - about to show the shell")); @@ -804,6 +805,13 @@ Bool Network::isFrameDataReady() { return (m_frameDataReady || (m_localStatus == NETLOCALSTATUS_LEFT)); } +Bool Network::isStalling() +{ + __int64 curTime; + QueryPerformanceCounter((LARGE_INTEGER *)&curTime); + return curTime >= m_nextFrameTime; +} + /** * returns the number of incoming bytes per second averaged over the last 30 sec. */ @@ -926,7 +934,7 @@ void Network::quitGame() { m_conMgr->quitGame(); } - TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); + TheGameLogic->exitGame(); m_localStatus = NETLOCALSTATUS_POSTGAME; DEBUG_LOG(("Network::quitGame - quitting game...")); } diff --git a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index 3e8ff91d767..972dfa7ca79 100644 --- a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -162,7 +162,6 @@ class W3DDisplay : public Display void calculateTerrainLOD(void); ///< Calculate terrain LOD. void renderLetterBox(UnsignedInt time); ///< draw letter box border void updateAverageFPS(void); ///< figure out the average fps over the last 30 frames. - static Bool isTimeFrozen(); Byte m_initialized; ///< TRUE when system is initialized LightClass *m_myLight[LightEnvironmentClass::MAX_LIGHTS]; ///< light hack for now diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp index f40b68fe306..f35f1977c23 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp @@ -88,7 +88,7 @@ void W3DOverlordTankDraw::doDrawModule(const Matrix3D* transformMtx) riderDraw->setColorTintEnvelope( *getDrawable()->getColorTintEnvelope() ); riderDraw->notifyDrawableDependencyCleared(); - riderDraw->draw( NULL );// What the hell? This param isn't used for anything + riderDraw->draw(); } } diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index 5a115cdf22b..71962a979f1 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -310,12 +310,13 @@ void W3DTankDraw::onRenderObjRecreated(void) //------------------------------------------------------------------------------------------------- void W3DTankDraw::doDrawModule(const Matrix3D* transformMtx) { + // TheSuperHackers @tweak Update the draw on every WW Sync only. + // All calculations are originally catered to a 30 fps logic step. + if (WW3D::Get_Frame_Time() == 0) + return; + const Real DEBRIS_THRESHOLD = 0.00001f; - Bool frozen = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - frozen = frozen || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); - if (frozen) - return; if (getRenderObject()==NULL) return; if (getRenderObject() != m_prevRenderObj) { updateTreadObjects(); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index 9baea57304c..ee7f97e1b11 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -515,9 +515,9 @@ void W3DTankTruckDraw::doDrawModule(const Matrix3D* transformMtx) if (!TheGlobalData->m_showClientPhysics) return; - Bool frozen = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - frozen = frozen || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); - if (frozen) + // TheSuperHackers @tweak Update the draw on every WW Sync only. + // All calculations are originally catered to a 30 fps logic step. + if (WW3D::Get_Frame_Time() == 0) return; const Real ACCEL_THRESHOLD = 0.01f; diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp index 38dcf25f24e..4807c3b93a3 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp @@ -388,12 +388,14 @@ void W3DTruckDraw::doDrawModule(const Matrix3D* transformMtx) if (!TheGlobalData->m_showClientPhysics) return; + const W3DTruckDrawModuleData *moduleData = getW3DTruckDrawModuleData(); - if (moduleData==NULL) return; // shouldn't ever happen. + if (moduleData==NULL) + return; // shouldn't ever happen. - Bool frozen = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - frozen = frozen || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); - if (frozen) + // TheSuperHackers @tweak Update the draw on every WW Sync only. + // All calculations are originally catered to a 30 fps logic step. + if (WW3D::Get_Frame_Time() == 0) return; const Real ACCEL_THRESHOLD = 0.01f; diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 720a9c4ee65..b6d19b675be 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -1606,52 +1606,9 @@ Int W3DDisplay::getLastFrameDrawCalls() return Debug_Statistics::Get_Draw_Calls(); } -Bool W3DDisplay::isTimeFrozen() -{ - if (TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished()) - return true; - - if (TheScriptEngine->isTimeFrozenDebug()) - return true; - - if (TheScriptEngine->isTimeFrozenScript()) - return true; - - if (TheGameLogic->isGamePaused()) - return true; - - return false; -} - -// TheSuperHackers @tweak xezon 12/08/2025 The WW3D Sync is no longer tied -// to the render update, but is advanced separately for every fixed time step. +//============================================================================= void W3DDisplay::step() { - // TheSuperHackers @info This will wrap in 1205 hours at 30 fps logic step. - static UnsignedInt syncTime = 0; - - extern HWND ApplicationHWnd; - if (ApplicationHWnd && ::IsIconic(ApplicationHWnd)) { - return; - } - - if (TheGlobalData->m_headless) - return; - - Bool freezeTime = isTimeFrozen(); - - if (!freezeTime) - { - syncTime += (UnsignedInt)TheW3DFrameLengthInMsec; - - if (TheScriptEngine->isTimeFast()) - { - return; - } - } - - WW3D::Sync( syncTime ); - stepViews(); } @@ -1738,12 +1695,7 @@ void W3DDisplay::draw( void ) // //PredictiveLODOptimizerClass::Optimize_LODs( 5000 ); - Bool freezeTime = isTimeFrozen(); - - // hack to let client spin fast in network games but still do effects at the same pace. -MDC - static UnsignedInt lastFrame = ~0; - freezeTime = freezeTime || (TheNetwork != NULL && lastFrame == TheGameClient->getFrame()); - lastFrame = TheGameClient->getFrame(); + Bool freezeTime = TheGameEngine->isTimeFrozen() || TheGameEngine->isGameHalted(); /// @todo: I'm assuming the first view is our main 3D view. W3DView *primaryW3DView=(W3DView *)getFirstView(); @@ -1779,6 +1731,8 @@ void W3DDisplay::draw( void ) } } + WW3D::Add_Frame_Time(TheGameEngine->getLogicTimeStepMilliseconds()); + static Int now; now=timeGetTime(); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index 5b4fa013718..c6c2806017e 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -555,7 +555,7 @@ void W3DView::reset( void ) static void drawDrawable( Drawable *draw, void *userData ) { - draw->draw( (View *)userData ); + draw->draw(); } // end drawDrawable @@ -911,8 +911,8 @@ Bool W3DView::updateCameraMovements() } else if (m_doingMoveCameraOnWaypointPath) { m_previousLookAtPosition = *getPosition(); // TheSuperHackers @tweak The scripted camera movement is now decoupled from the render update. - const Real logicTimeScaleOverFpsRatio = TheGameEngine->getActualLogicTimeScaleOverFpsRatio(); - moveAlongWaypointPath(TheW3DFrameLengthInMsec * logicTimeScaleOverFpsRatio); + // The scripted camera will still move when the time is frozen, but not when the game is halted. + moveAlongWaypointPath(TheGameEngine->getLogicTimeStepMilliseconds(GameEngine::IgnoreFrozenTime)); didUpdate = true; } if (m_doingScriptedCameraLock) @@ -936,13 +936,9 @@ void W3DView::updateView(void) UPDATE(); } -// TheSuperHackers @tweak xezon 12/08/2025 The drawable update is no longer tied to the -// render update, but it advanced separately for every fixed time step. This ensures that -// things like vehicle wheels no longer spin too fast on high frame rates or keep spinning -// on game pause. -// The camera shaker is also no longer tied to the render update. The shake does sharp shakes -// on every fixed time step, and is not intended to have linear interpolation during the -// render update. +// TheSuperHackers @tweak xezon 12/08/2025 The camera shaker is no longer tied to the render +// update. The shake does sharp shakes on every fixed time step, and is not intended to have +// linear interpolation during the render update. void W3DView::stepView() { // @@ -967,17 +963,6 @@ void W3DView::stepView() m_shakeOffset.x = 0.0f; m_shakeOffset.y = 0.0f; } - - if (TheScriptEngine->isTimeFast()) { - return; // don't draw - makes it faster :) jba. - } - - Region3D axisAlignedRegion; - getAxisAlignedViewRegion(axisAlignedRegion); - - // render all of the visible Drawables - /// @todo this needs to use a real region partition or something - TheGameClient->iterateDrawablesInRegion( &axisAlignedRegion, drawDrawable, this ); } //DECLARE_PERF_TIMER(W3DView_updateView) @@ -1248,6 +1233,13 @@ void W3DView::update(void) } if (recalcCamera) setCameraTransform(); + + Region3D axisAlignedRegion; + getAxisAlignedViewRegion(axisAlignedRegion); + + // render all of the visible Drawables + /// @todo this needs to use a real region partition or something + TheGameClient->iterateDrawablesInRegion( &axisAlignedRegion, drawDrawable, NULL ); } //------------------------------------------------------------------------------------------------- diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp index 507e92ebf24..cadd4fb4bfd 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp @@ -165,6 +165,7 @@ const char* DAZZLE_INI_FILENAME="DAZZLE.INI"; ** ***********************************************************************************/ +float WW3D::FractionalSyncMs = 0.0f; unsigned int WW3D::SyncTime = 0; unsigned int WW3D::PreviousSyncTime = 0; bool WW3D::IsSortingEnabled = true; @@ -1167,6 +1168,23 @@ unsigned int WW3D::Get_Last_Frame_Vertex_Count(void) return Debug_Statistics::Get_DX8_Vertices(); } +void WW3D::Add_Frame_Time(float milliseconds) +{ + FractionalSyncMs += milliseconds; + unsigned int integralSyncMs = (unsigned int)FractionalSyncMs; + +#if MSEC_PER_WWSYNC_FRAME + if (integralSyncMs < MSEC_PER_WWSYNC_FRAME) + { + Sync(SyncTime); + return; + } +#endif + + FractionalSyncMs -= integralSyncMs; + Sync(SyncTime + integralSyncMs); +} + /*********************************************************************************************** * WW3D::Sync -- Time sychronization * diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h index 4f5544f19ea..9c544db8163 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h @@ -162,7 +162,9 @@ class WW3D static void Flip_To_Primary(void); - + // TheSuperHackers @info Call this function to accumulate fractional render time. + // It will then call Sync with a new time on its own once an appropriate amount of time has passed. + static void Add_Frame_Time(float milliseconds); /* ** Timing ** By calling the Sync function, the application can move the ww3d library time forward. This @@ -321,6 +323,8 @@ class WW3D static void Allocate_Debug_Resources(void); static void Release_Debug_Resources(void); + static float FractionalSyncMs; + // Timing info: // The absolute synchronized frame time (in milliseconds) supplied by the // application at the start of every frame. Note that wraparound cases diff --git a/Generals/Libraries/Source/WWVegas/WWLib/WWDefines.h b/Generals/Libraries/Source/WWVegas/WWLib/WWDefines.h new file mode 100644 index 00000000000..d7acdd7b4ed --- /dev/null +++ b/Generals/Libraries/Source/WWVegas/WWLib/WWDefines.h @@ -0,0 +1,25 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 TheSuperHackers +** +** 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 . +*/ + +#pragma once + +// The WW3D Sync time. This was originally 33 ms, ~30 fps, integer. +// Changing or removing this will require tweaking all Drawable code that concerns logic time step, including locomotion physics. +#ifndef MSEC_PER_WWSYNC_FRAME +#define MSEC_PER_WWSYNC_FRAME (33) +#endif diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h index 86f524551e1..f354df5f8d4 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h @@ -53,11 +53,16 @@ class Radar; class WebBrowser; class ParticleSystemManager; -/** - * The implementation of the game engine - */ class GameEngine : public SubsystemInterface { +public: + + typedef UnsignedInt LogicTimeQueryFlags; + enum LogicTimeQueryFlags_ CPP_11(: LogicTimeQueryFlags) + { + IgnoreFrozenTime = 1<<0, // Ignore frozen time for the query + IgnoreHaltedGame = 1<<1, // Ignore halted game for the query + }; public: @@ -76,13 +81,18 @@ class GameEngine : public SubsystemInterface Real getUpdateTime(); ///< Get the last engine update delta time. Real getUpdateFps(); ///< Get the last engine update fps. + static Bool isTimeFrozen(); ///< Returns true if a script has frozen time. + static Bool isGameHalted(); ///< Returns true if the game is paused or the network is stalling. + virtual void setLogicTimeScaleFps( Int fps ); ///< Set the logic time scale fps and therefore scale the simulation time. Is capped by the max render fps and does not apply to network matches. virtual Int getLogicTimeScaleFps(); ///< Get the raw logic time scale fps value. virtual void enableLogicTimeScale( Bool enable ); ///< Enable the logic time scale setup. If disabled, the simulation time scale is bound to the render frame time or network update time. virtual Bool isLogicTimeScaleEnabled(); ///< Check whether the logic time scale setup is enabled. - Int getActualLogicTimeScaleFps(); ///< Get the real logic time scale fps, depending on the max render fps, network state and enabled state. - Real getActualLogicTimeScaleRatio(); ///< Get the real logic time scale ratio, depending on the max render fps, network state and enabled state. - Real getActualLogicTimeScaleOverFpsRatio(); ///< Get the real logic time scale over render fps ratio, used to scale down steps in render updates to match logic updates. + Int getActualLogicTimeScaleFps(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale fps, depending on the max render fps, network state and enabled state. + Real getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale ratio, depending on the max render fps, network state and enabled state. + Real getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags = 0); ///< Get the real logic time scale over render fps ratio, used to scale down steps in render updates to match logic updates. + Real getLogicTimeStepSeconds(LogicTimeQueryFlags flags = 0); ///< Get the logic time step in seconds + Real getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags = 0); ///< Get the logic time step in milliseconds virtual void setQuitting( Bool quitting ); ///< set quitting status virtual Bool getQuitting(void); ///< is app getting ready to quit. @@ -96,6 +106,10 @@ class GameEngine : public SubsystemInterface virtual void resetSubsystems( void ); + Bool canUpdateGameLogic(); + Bool canUpdateNetworkGameLogic(); + Bool canUpdateRegularGameLogic(); + virtual FileSystem *createFileSystem( void ); ///< Factory for FileSystem classes virtual LocalFileSystem *createLocalFileSystem( void ) = 0; ///< Factory for LocalFileSystem classes virtual ArchiveFileSystem *createArchiveFileSystem( void ) = 0; ///< Factory for ArchiveFileSystem classes @@ -116,11 +130,14 @@ class GameEngine : public SubsystemInterface Real m_updateTime; ///< Last engine update delta time Real m_logicTimeAccumulator; ///< Frame time accumulated towards submitting a new logic frame - Bool m_quitting; ///< true when we need to quit the game - Bool m_isActive; ///< app has OS focus. + Bool m_quitting; ///< true when we need to quit the game + Bool m_isActive; ///< app has OS focus. Bool m_enableLogicTimeScale; + Bool m_isTimeFrozen; + Bool m_isGameHalted; }; + inline void GameEngine::setQuitting( Bool quitting ) { m_quitting = quitting; } inline Bool GameEngine::getQuitting(void) { return m_quitting; } diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index 2b39bd6ea2b..d922060b405 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h @@ -406,7 +406,7 @@ class Drawable : public Thing, const Matrix3D *getTransformMatrix( void ) const; ///< return the world transform - void draw( View *view ); ///< render the drawable to the given view + void draw(); ///< render the drawable to the given view void updateDrawable(); ///< update the drawable void drawIconUI( void ); ///< draw "icon"(s) needed on drawable (health bars, veterency, etc) @@ -640,6 +640,8 @@ class Drawable : public Thing, private: + const Locomotor* getLocomotor() const; + // note, these are lazily allocated! TintEnvelope* m_selectionFlashEnvelope; ///< used for selection flash, works WITH m_colorTintEnvelope TintEnvelope* m_colorTintEnvelope; ///< house color flashing, etc... works WITH m_selectionFlashEnvelope @@ -685,6 +687,8 @@ class Drawable : public Thing, DrawableLocoInfo* m_locoInfo; // lazily allocated + PhysicsXformInfo* m_physicsXform; + DynamicAudioEventRTS* m_ambientSound; ///< sound module for ambient sound (lazily allocated) Module** m_modules[NUM_DRAWABLE_MODULE_TYPES]; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index c6771816aee..3ce5706eac2 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -212,6 +212,7 @@ class GameLogic : public SubsystemInterface, public Snapshot void updateObjectsChangedTriggerAreas(void) {m_frameObjectsChangedTriggerAreas = m_frame;} UnsignedInt getFrameObjectsChangedTriggerAreas(void) {return m_frameObjectsChangedTriggerAreas;} + void exitGame(); void clearGameData(Bool showScoreScreen = TRUE); ///< Clear the game data void closeWindows( void ); @@ -220,7 +221,7 @@ class GameLogic : public SubsystemInterface, public Snapshot void bindObjectAndDrawable(Object* obj, Drawable* draw); - void setGamePausedInFrame( UnsignedInt frame ); + void setGamePausedInFrame( UnsignedInt frame, Bool disableLogicTimeScale ); UnsignedInt getGamePauseFrame() const { return m_pauseFrame; } void setGamePaused( Bool paused, Bool pauseMusic = TRUE, Bool pauseInput = TRUE ); Bool isGamePaused( void ); @@ -375,6 +376,7 @@ class GameLogic : public SubsystemInterface, public Snapshot Bool m_pauseInput; Bool m_inputEnabledMemory;// Latches used to remember what to restore to after we unpause Bool m_mouseVisibleMemory; + Bool m_logicTimeScaleEnabledMemory; Bool m_progressComplete[MAX_SLOTS]; enum { PROGRESS_COMPLETE_TIMEOUT = 60000 }; ///< Timeout we wait for when we've completed our Load diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/NetworkInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/NetworkInterface.h index 1bc29f705a0..8d454add86b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/NetworkInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/NetworkInterface.h @@ -68,6 +68,7 @@ class NetworkInterface : public SubsystemInterface virtual void setLocalAddress(UnsignedInt ip, UnsignedInt port) = 0; ///< Tell the network what local ip and port to bind to. virtual Bool isFrameDataReady( void ) = 0; ///< Are the commands for the next frame available? + virtual Bool isStalling() = 0; virtual void parseUserList( const GameInfo *game ) = 0; ///< Parse a userlist, creating connections virtual void startGame(void) = 0; ///< Sets the network game frame counter to -1 virtual UnsignedInt getRunAhead(void) = 0; ///< Get the current RunAhead value diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 2b4f8ea7ba7..5eca7f3218f 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -258,6 +258,8 @@ GameEngine::GameEngine( void ) m_quitting = FALSE; m_isActive = FALSE; m_enableLogicTimeScale = FALSE; + m_isTimeFrozen = FALSE; + m_isGameHalted = FALSE; _Module.Init(NULL, ApplicationHInstance, NULL); } @@ -336,6 +338,45 @@ Real GameEngine::getUpdateFps() return 1.0f / m_updateTime; } +//------------------------------------------------------------------------------------------------- +Bool GameEngine::isTimeFrozen() +{ + // TheSuperHackers @fix The time can no longer be frozen in Network games. It would disconnect the player. + if (TheNetwork != NULL) + return false; + + if (TheTacticalView != NULL) + { + if (TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished()) + return true; + } + + if (TheScriptEngine != NULL) + { + if (TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript()) + return true; + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +Bool GameEngine::isGameHalted() +{ + if (TheNetwork != NULL) + { + if (TheNetwork->isStalling()) + return true; + } + else + { + if (TheGameLogic != NULL && TheGameLogic->isGamePaused()) + return true; + } + + return false; +} + //------------------------------------------------------------------------------------------------- void GameEngine::setLogicTimeScaleFps( Int fps ) { @@ -361,41 +402,53 @@ Bool GameEngine::isLogicTimeScaleEnabled() } //------------------------------------------------------------------------------------------------- -Int GameEngine::getActualLogicTimeScaleFps( void ) +Int GameEngine::getActualLogicTimeScaleFps(LogicTimeQueryFlags flags) { + if (m_isTimeFrozen && (flags & IgnoreFrozenTime) == 0) + { + return 0; + } + + if (m_isGameHalted && (flags & IgnoreHaltedGame) == 0) + { + return 0; + } + if (TheNetwork != NULL) { return TheNetwork->getFrameRate(); } - else - { - const Bool enabled = isLogicTimeScaleEnabled(); - const Int logicTimeScaleFps = getLogicTimeScaleFps(); - const Int maxFps = getFramesPerSecondLimit(); - if (!enabled || logicTimeScaleFps >= maxFps) - { - return getFramesPerSecondLimit(); - } - else - { - return logicTimeScaleFps; - } + if (isLogicTimeScaleEnabled()) + { + return min(getLogicTimeScaleFps(), getFramesPerSecondLimit()); } + + return getFramesPerSecondLimit(); } //------------------------------------------------------------------------------------------------- -Real GameEngine::getActualLogicTimeScaleRatio() +Real GameEngine::getActualLogicTimeScaleRatio(LogicTimeQueryFlags flags) { - return (Real)getActualLogicTimeScaleFps() / LOGICFRAMES_PER_SECONDS_REAL; + return (Real)getActualLogicTimeScaleFps(flags) / LOGICFRAMES_PER_SECONDS_REAL; } //------------------------------------------------------------------------------------------------- -Real GameEngine::getActualLogicTimeScaleOverFpsRatio() +Real GameEngine::getActualLogicTimeScaleOverFpsRatio(LogicTimeQueryFlags flags) { // TheSuperHackers @info Clamps ratio to min 1, because the logic - // frame rate is (typically) capped by the render frame rate. - return min(1.0f, (Real)getActualLogicTimeScaleFps() / getUpdateFps()); + // frame rate is currently capped by the render frame rate. + return min(1.0f, (Real)getActualLogicTimeScaleFps(flags) / getUpdateFps()); +} + +Real GameEngine::getLogicTimeStepSeconds(LogicTimeQueryFlags flags) +{ + return SECONDS_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); +} + +Real GameEngine::getLogicTimeStepMilliseconds(LogicTimeQueryFlags flags) +{ + return MSEC_PER_LOGICFRAME_REAL * getActualLogicTimeScaleOverFpsRatio(flags); } /** ----------------------------------------------------------------------------------------------- @@ -871,21 +924,85 @@ void GameEngine::resetSubsystems( void ) TheSubsystemList->resetAll(); } +/// ----------------------------------------------------------------------------------------------- +Bool GameEngine::canUpdateGameLogic() +{ + // Must be first. + TheGameLogic->preUpdate(); + + m_isTimeFrozen = isTimeFrozen(); + m_isGameHalted = isGameHalted(); + + if (TheNetwork != NULL) + { + return canUpdateNetworkGameLogic(); + } + else + { + return canUpdateRegularGameLogic(); + } +} + +Bool GameEngine::canUpdateNetworkGameLogic() +{ + DEBUG_ASSERTCRASH(TheNetwork != NULL, ("TheNetwork is NULL")); + + if (TheNetwork->isFrameDataReady()) + { + // Important: The Network is definitely no longer stalling. + m_isGameHalted = false; + + return true; + } + + return false; +} + +Bool GameEngine::canUpdateRegularGameLogic() +{ + const Bool enabled = isLogicTimeScaleEnabled(); + const Int logicTimeScaleFps = getLogicTimeScaleFps(); + const Int maxRenderFps = getFramesPerSecondLimit(); + +#if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) + const Bool useFastMode = TheGlobalData->m_TiVOFastMode; +#else //always allow this cheat key if we're in a replay game. + const Bool useFastMode = TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame(); +#endif + + if (useFastMode || !enabled || logicTimeScaleFps >= maxRenderFps) + { + // Logic time scale is uncapped or larger equal Render FPS. Update straight away. + return true; + } + else + { + // TheSuperHackers @tweak xezon 06/08/2025 + // The logic time step is now decoupled from the render update. + const Real targetFrameTime = 1.0f / logicTimeScaleFps; + m_logicTimeAccumulator += min(m_updateTime, targetFrameTime); + + if (m_logicTimeAccumulator >= targetFrameTime) + { + m_logicTimeAccumulator -= targetFrameTime; + return true; + } + } + + return false; +} + /// ----------------------------------------------------------------------------------------------- DECLARE_PERF_TIMER(GameEngine_update) /** ----------------------------------------------------------------------------------------------- * Update the game engine by updating the GameClient and GameLogic singletons. - * @todo Allow the client to run as fast as possible, but limit the execution - * of TheNetwork and TheGameLogic to a fixed framerate. */ void GameEngine::update( void ) { USE_PERF_TIMER(GameEngine_update) { - { - // VERIFY CRC needs to be in this code block. Please to not pull TheGameLogic->update() inside this block. VERIFY_CRC @@ -905,55 +1022,22 @@ void GameEngine::update( void ) TheCDManager->UPDATE(); } - TheGameLogic->preUpdate(); + const Bool canUpdate = canUpdateGameLogic(); + const Bool canUpdateLogic = canUpdate && !m_isGameHalted && !m_isTimeFrozen; + const Bool canUpdateScript = canUpdate && !m_isGameHalted; - if (TheNetwork != NULL) + if (canUpdateLogic) { - if (TheNetwork->isFrameDataReady()) - { - TheGameClient->step(); - TheGameLogic->UPDATE(); - } + TheGameClient->step(); + TheGameLogic->UPDATE(); } - else + else if (canUpdateScript) { - if (!TheGameLogic->isGamePaused()) - { - const Bool enabled = isLogicTimeScaleEnabled(); - const Int logicTimeScaleFps = getLogicTimeScaleFps(); - const Int maxRenderFps = getFramesPerSecondLimit(); - -#if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) - Bool useFastMode = TheGlobalData->m_TiVOFastMode; -#else //always allow this cheat key if we're in a replay game. - Bool useFastMode = TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame(); -#endif - - if (useFastMode || !enabled || logicTimeScaleFps >= maxRenderFps) - { - // Logic time scale is uncapped or larger equal Render FPS. Update straight away. - TheGameClient->step(); - TheGameLogic->UPDATE(); - } - else - { - // TheSuperHackers @tweak xezon 06/08/2025 - // The logic time step is now decoupled from the render update. - const Real targetFrameTime = 1.0f / logicTimeScaleFps; - m_logicTimeAccumulator += min(m_updateTime, targetFrameTime); - - if (m_logicTimeAccumulator >= targetFrameTime) - { - m_logicTimeAccumulator -= targetFrameTime; - TheGameClient->step(); - TheGameLogic->UPDATE(); - } - } - } + // TheSuperHackers @info Still update the Script Engine to allow + // for scripted camera movements while the time is frozen. + TheScriptEngine->UPDATE(); } - } // end perfGather - } // Horrible reference, but we really, really need to know if we are windowed. diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index ac1f04bc357..bbc17895d80 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -477,7 +477,7 @@ void RecorderClass::stopPlayback() { if (!m_doingAnalysis) { - TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); + TheGameLogic->exitGame(); } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 0ad661c609a..8393a5880bc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -78,6 +78,8 @@ #include "GameClient/Shadow.h" #include "GameClient/GameText.h" +#include "ww3d.h" + //#define KRIS_BRUTAL_HACK_FOR_AIRCRAFT_CARRIER_DEBUGGING #ifdef KRIS_BRUTAL_HACK_FOR_AIRCRAFT_CARRIER_DEBUGGING #include "GameLogic/Module/ParkingPlaceBehavior.h" @@ -408,6 +410,7 @@ Drawable::Drawable( const ThingTemplate *thingTemplate, DrawableStatus statusBit m_flashCount = 0; m_locoInfo = NULL; + m_physicsXform = NULL; // sanity if( TheGameClient == NULL || thingTemplate == NULL ) @@ -577,6 +580,8 @@ Drawable::~Drawable() deleteInstance(m_locoInfo); m_locoInfo = NULL; + + delete m_physicsXform; } //------------------------------------------------------------------------------------------------- @@ -1377,25 +1382,19 @@ void Drawable::flashAsSelected( const RGBColor *color ) ///< drawable takes care //------------------------------------------------------------------------------------------------- void Drawable::applyPhysicsXform(Matrix3D* mtx) { - const Object *obj = getObject(); - - if( !obj || obj->isDisabledByType( DISABLED_HELD ) || !TheGlobalData->m_showClientPhysics ) - { - return; - } - - Bool frozen = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - frozen = frozen || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); - if (frozen) - return; - PhysicsXformInfo info; - if (calcPhysicsXform(info)) + if (m_physicsXform != NULL) { - mtx->Translate(0.0f, 0.0f, info.m_totalZ); - mtx->Rotate_Y( info.m_totalPitch ); - mtx->Rotate_X( -info.m_totalRoll ); - mtx->Rotate_Z( info.m_totalYaw ); + // TheSuperHackers @tweak Update the physics transform on every WW Sync only. + // All calculations are originally catered to a 30 fps logic step. + if (WW3D::Get_Frame_Time() != 0) + { + calcPhysicsXform(*m_physicsXform); + } + mtx->Translate(0.0f, 0.0f, m_physicsXform->m_totalZ); + mtx->Rotate_Y( m_physicsXform->m_totalPitch ); + mtx->Rotate_X( -m_physicsXform->m_totalRoll ); + mtx->Rotate_Z( m_physicsXform->m_totalYaw ); } } @@ -1403,38 +1402,33 @@ void Drawable::applyPhysicsXform(Matrix3D* mtx) //------------------------------------------------------------------------------------------------- Bool Drawable::calcPhysicsXform(PhysicsXformInfo& info) { - const Object* obj = getObject(); - const AIUpdateInterface *ai = obj ? obj->getAIUpdateInterface() : NULL; Bool hasPhysicsXform = false; - if (ai) + + if (const Locomotor *locomotor = getLocomotor()) { - const Locomotor *locomotor = ai->getCurLocomotor(); - if (locomotor) + switch (locomotor->getAppearance()) { - switch (locomotor->getAppearance()) - { - case LOCO_WHEELS_FOUR: - calcPhysicsXformWheels(locomotor, info); - hasPhysicsXform = true; - break; - case LOCO_MOTORCYCLE: - calcPhysicsXformMotorcycle( locomotor, info ); - hasPhysicsXform = TRUE; - break; - case LOCO_TREADS: - calcPhysicsXformTreads(locomotor, info); - hasPhysicsXform = true; - break; - case LOCO_HOVER: - case LOCO_WINGS: - calcPhysicsXformHoverOrWings(locomotor, info); - hasPhysicsXform = true; - break; - case LOCO_THRUST: - calcPhysicsXformThrust(locomotor, info); - hasPhysicsXform = true; - break; - } + case LOCO_WHEELS_FOUR: + calcPhysicsXformWheels(locomotor, info); + hasPhysicsXform = true; + break; + case LOCO_MOTORCYCLE: + calcPhysicsXformMotorcycle( locomotor, info ); + hasPhysicsXform = true; + break; + case LOCO_TREADS: + calcPhysicsXformTreads(locomotor, info); + hasPhysicsXform = true; + break; + case LOCO_HOVER: + case LOCO_WINGS: + calcPhysicsXformHoverOrWings(locomotor, info); + hasPhysicsXform = true; + break; + case LOCO_THRUST: + calcPhysicsXformThrust(locomotor, info); + hasPhysicsXform = true; + break; } } @@ -2623,9 +2617,8 @@ void Drawable::setStealthLook(StealthLookType look) //------------------------------------------------------------------------------------------------- /** default draw is to just call the database defined draw */ //------------------------------------------------------------------------------------------------- -void Drawable::draw( View *view ) +void Drawable::draw() { - if ( testTintStatus( TINT_STATUS_FRENZY ) == FALSE ) { if ( getObject() && getObject()->isEffectivelyDead() ) @@ -2660,7 +2653,10 @@ void Drawable::draw( View *view ) #endif } - applyPhysicsXform(&transformMtx); + if (TheGlobalData->m_showClientPhysics && getObject() && !getObject()->isDisabledByType( DISABLED_HELD )) + { + applyPhysicsXform(&transformMtx); + } for (DrawModule** dm = getDrawModules(); *dm; ++dm) { @@ -4173,6 +4169,14 @@ void Drawable::friend_bindToObject( Object *obj ) ///< bind this drawable to an { (*dm)->onDrawableBoundToObject(); } + + PhysicsXformInfo physicsXform; + if (calcPhysicsXform(physicsXform)) + { + DEBUG_ASSERTCRASH(m_physicsXform == NULL, ("m_physicsXform is not NULL")); + m_physicsXform = new PhysicsXformInfo; + *m_physicsXform = physicsXform; + } } //------------------------------------------------------------------------------------------------- // when our Object changes teams, it calls us to let us know, so @@ -5433,6 +5437,19 @@ void Drawable::loadPostProcess( void ) } // end loadPostProcess +//------------------------------------------------------------------------------------------------- +const Locomotor* Drawable::getLocomotor() const +{ + if (const Object* obj = getObject()) + { + if (const AIUpdateInterface *ai = obj->getAIUpdateInterface()) + { + return ai->getCurLocomotor(); + } + } + return NULL; +} + //================================================================================================= //================================================================================================= #ifdef DIRTY_CONDITION_FLAGS diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/DownloadMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/DownloadMenu.cpp index 89595d8bf72..44bed1f0eaa 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/DownloadMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/DownloadMenu.cpp @@ -109,7 +109,7 @@ static void successQuitCallback( void ) // Clean up game data. No crashy-crash for you! if (TheGameLogic->isInGame()) - TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); + TheGameLogic->exitGame(); } static void successNoQuitCallback( void ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp index a42675511e1..2a5f0ea68c5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/MainMenu.cpp @@ -265,7 +265,7 @@ static void quitCallback( void ) } if (TheGameLogic->isInGame()) - TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); + TheGameLogic->exitGame(); } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp index 05cead033be..7f1d075e08b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/QuitMenu.cpp @@ -145,9 +145,7 @@ static void exitQuitMenu() GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); msg->appendBooleanArgument(TRUE); } - /*GameMessage *msg =*/ TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); - if ( !TheGameLogic->isInMultiplayerGame() ) - TheGameLogic->setGamePaused(FALSE); + TheGameLogic->exitGame(); // TheGameLogic->clearGameData(); // display the menu on top of the shell stack // TheShell->showShell(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index 178c90049e0..2edbec54aa5 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp @@ -542,7 +542,7 @@ void Shell::showShellMap(Bool useShellMap ) return; // we're in some other kind of game, clear it out foo! if(TheGameLogic->isInGame()) - TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); + TheGameLogic->exitGame(); TheWritableGlobalData->m_pendingFile = TheGlobalData->m_shellMapName; InitGameLogicRandom(0); @@ -554,7 +554,7 @@ void Shell::showShellMap(Bool useShellMap ) { // we're in a shell game, stop it! if(TheGameLogic->isInGame() && TheGameLogic->getGameMode() == GAME_SHELL) - TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); + TheGameLogic->exitGame(); // if the shell is active,we need a background if(!m_isShellActive) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp index a39542afa02..5d680cac0aa 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp @@ -640,17 +640,9 @@ void GameClient::update( void ) TheVideoPlayer->UPDATE(); } - Bool freezeTime = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - freezeTime = freezeTime || TheScriptEngine->isTimeFrozenDebug(); - freezeTime = freezeTime || TheScriptEngine->isTimeFrozenScript(); - freezeTime = freezeTime || TheGameLogic->isGamePaused(); + const Bool freezeTime = TheGameEngine->isTimeFrozen() || TheGameEngine->isGameHalted(); Int localPlayerIndex = ThePlayerList ? ThePlayerList->getLocalPlayer()->getPlayerIndex() : 0; - // hack to let client spin fast in network games but still do effects at the same pace. -MDC - static UnsignedInt lastFrame = ~0; - freezeTime = freezeTime || (lastFrame == m_frame); - lastFrame = m_frame; - if (!freezeTime) { #if ENABLE_CONFIGURABLE_SHROUD diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 3f994adb3c4..ee6424345b0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -3478,7 +3478,7 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage #endif { TheGameLogic->setGamePaused(FALSE); - TheGameLogic->setGamePausedInFrame(TheGameLogic->getFrame() + 1); + TheGameLogic->setGamePausedInFrame(TheGameLogic->getFrame() + 1, TRUE); } break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index db8eefc62d8..5c77b99f168 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -5542,8 +5542,7 @@ void ScriptEngine::update( void ) if (m_endGameTimer>0) { m_endGameTimer--; if (m_endGameTimer < 1) { - // clear out all the game data - /*GameMessage *msg =*/ TheMessageStream->appendMessage( GameMessage::MSG_CLEAR_GAME_DATA ); + TheGameLogic->exitGame(); //TheScriptActions->closeWindows(FALSE); // Close victory or defeat windows. } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 9cf79663e6d..80ebd4559e9 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -252,6 +252,7 @@ GameLogic::GameLogic( void ) m_pauseInput = FALSE; m_inputEnabledMemory = TRUE; m_mouseVisibleMemory = TRUE; + m_logicTimeScaleEnabledMemory = FALSE; m_loadScreen = NULL; m_forceGameStartByTimeOut = FALSE; #ifdef DUMP_PERF_STATS @@ -421,6 +422,8 @@ void GameLogic::init( void ) m_pauseInput = FALSE; m_inputEnabledMemory = TRUE; m_mouseVisibleMemory = TRUE; + m_logicTimeScaleEnabledMemory = FALSE; + for(Int i = 0; i < MAX_SLOTS; ++i) { m_progressComplete[i] = FALSE; @@ -460,6 +463,8 @@ void GameLogic::reset( void ) m_pauseInput = FALSE; m_inputEnabledMemory = TRUE; m_mouseVisibleMemory = TRUE; + m_logicTimeScaleEnabledMemory = FALSE; + setFPMode(); // destroy all objects @@ -3684,7 +3689,6 @@ void GameLogic::update( void ) } // send the current time to the GameClient - DEBUG_ASSERTCRASH(TheGameLogic == this, ("hmm, TheGameLogic is not right")); UnsignedInt now = TheGameLogic->getFrame(); TheGameClient->setFrame(now); @@ -3693,22 +3697,6 @@ void GameLogic::update( void ) TheScriptEngine->UPDATE(); } - Bool freezeTime = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - freezeTime = freezeTime || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); - - if (freezeTime) - { - if (TheCommandList->containsMessageOfType(GameMessage::MSG_CLEAR_GAME_DATA)) - { - TheScriptEngine->forceUnfreezeTime(); - } - else - { - /// @todo - make sure this never happens during a network game. jba. - return; - } - } - // Note - TerrainLogic update needs to happen after ScriptEngine update, but before object updates. jba. // This way changes in bridges are noted in the script engine before being cleared in TerrainLogic->update { @@ -4209,6 +4197,17 @@ UnsignedInt GameLogic::getCRC( Int mode, AsciiString deepCRCFileName ) return theCRC; } +// ------------------------------------------------------------------------------------------------ +void GameLogic::exitGame() +{ + // TheSuperHackers @fix The logic update must not be halted to process the game exit message. + setGamePaused(FALSE); + TheScriptEngine->forceUnfreezeTime(); + TheScriptEngine->doUnfreezeTime(); + + TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); +} + // ------------------------------------------------------------------------------------------------ /** A new GameLogic object has been constructed, therefore create * a corresponding drawable and bind them together. */ @@ -4264,11 +4263,17 @@ Bool GameLogic::isGamePaused( void ) // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ -void GameLogic::setGamePausedInFrame( UnsignedInt frame ) +void GameLogic::setGamePausedInFrame( UnsignedInt frame, Bool disableLogicTimeScale ) { if (frame >= m_frame) { m_pauseFrame = frame; + + if (disableLogicTimeScale) + { + m_logicTimeScaleEnabledMemory = TheGameEngine->isLogicTimeScaleEnabled(); + TheGameEngine->enableLogicTimeScale(FALSE); + } } } @@ -4300,6 +4305,12 @@ void GameLogic::setGamePaused( Bool paused, Bool pauseMusic, Bool pauseInput ) void GameLogic::pauseGameLogic(Bool paused) { m_gamePaused = paused; + + if (!paused && m_logicTimeScaleEnabledMemory) + { + m_logicTimeScaleEnabledMemory = FALSE; + TheGameEngine->enableLogicTimeScale(TRUE); + } } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/Network.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/Network.cpp index 58e800d861b..a8c009acc4d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/Network.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/Network.cpp @@ -116,6 +116,7 @@ class Network : public NetworkInterface inline UnsignedInt getFrameRate(void) { return m_frameRate; } UnsignedInt getPacketArrivalCushion(void); ///< Returns the smallest packet arrival cushion since this was last called. Bool isFrameDataReady( void ); + virtual Bool isStalling(); void parseUserList( const GameInfo *game ); void startGame(void); ///< Sets the network game frame counter to -1 @@ -740,7 +741,7 @@ void Network::endOfGameCheck() { if (m_conMgr != NULL) { if (m_conMgr->canILeave()) { m_conMgr->disconnectLocalPlayer(); - TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); + TheGameLogic->exitGame(); m_localStatus = NETLOCALSTATUS_POSTGAME; DEBUG_LOG(("Network::endOfGameCheck - about to show the shell")); @@ -804,6 +805,13 @@ Bool Network::isFrameDataReady() { return (m_frameDataReady || (m_localStatus == NETLOCALSTATUS_LEFT)); } +Bool Network::isStalling() +{ + __int64 curTime; + QueryPerformanceCounter((LARGE_INTEGER *)&curTime); + return curTime >= m_nextFrameTime; +} + /** * returns the number of incoming bytes per second averaged over the last 30 sec. */ @@ -930,7 +938,7 @@ void Network::quitGame() { GameMessage *msg = TheMessageStream->appendMessage(GameMessage::MSG_SELF_DESTRUCT); msg->appendBooleanArgument(TRUE); - TheMessageStream->appendMessage(GameMessage::MSG_CLEAR_GAME_DATA); + TheGameLogic->exitGame(); m_localStatus = NETLOCALSTATUS_POSTGAME; DEBUG_LOG(("Network::quitGame - quitting game...")); } diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index 80c07119dae..79a5dfa0416 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -163,7 +163,6 @@ class W3DDisplay : public Display void calculateTerrainLOD(void); ///< Calculate terrain LOD. void renderLetterBox(UnsignedInt time); ///< draw letter box border void updateAverageFPS(void); ///< figure out the average fps over the last 30 frames. - static Bool isTimeFrozen(); Byte m_initialized; ///< TRUE when system is initialized LightClass *m_myLight[LightEnvironmentClass::MAX_LIGHTS]; ///< light hack for now diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp index 39c1cbe3250..8aba0a4de3c 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp @@ -101,7 +101,7 @@ void W3DOverlordAircraftDraw::doDrawModule(const Matrix3D* transformMtx) riderDraw->setColorTintEnvelope( *env ); riderDraw->notifyDrawableDependencyCleared(); - riderDraw->draw( NULL );// What the hell? This param isn't used for anything + riderDraw->draw(); } DEBUG_ASSERTCRASH( riderDraw, ("OverlordAircraftDraw finds no rider's drawable") ); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp index 039cd926f12..530d8682a18 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp @@ -88,7 +88,7 @@ void W3DOverlordTankDraw::doDrawModule(const Matrix3D* transformMtx) riderDraw->setColorTintEnvelope( *getDrawable()->getColorTintEnvelope() ); riderDraw->notifyDrawableDependencyCleared(); - riderDraw->draw( NULL );// What the hell? This param isn't used for anything + riderDraw->draw(); } } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTruckDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTruckDraw.cpp index b8c26fe2842..471cb67dbb6 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTruckDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTruckDraw.cpp @@ -88,7 +88,7 @@ void W3DOverlordTruckDraw::doDrawModule(const Matrix3D* transformMtx) riderDraw->setColorTintEnvelope( *getDrawable()->getColorTintEnvelope() ); riderDraw->notifyDrawableDependencyCleared(); - riderDraw->draw( NULL );// What the hell? This param isn't used for anything + riderDraw->draw(); } } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp index ce402892c5a..f1dd1ae0205 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankDraw.cpp @@ -310,12 +310,13 @@ void W3DTankDraw::onRenderObjRecreated(void) //------------------------------------------------------------------------------------------------- void W3DTankDraw::doDrawModule(const Matrix3D* transformMtx) { + // TheSuperHackers @tweak Update the draw on every WW Sync only. + // All calculations are originally catered to a 30 fps logic step. + if (WW3D::Get_Frame_Time() == 0) + return; + const Real DEBRIS_THRESHOLD = 0.00001f; - Bool frozen = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - frozen = frozen || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); - if (frozen) - return; if (getRenderObject()==NULL) return; if (getRenderObject() != m_prevRenderObj) { updateTreadObjects(); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp index d65e739cc4d..cb26acc9ec2 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTankTruckDraw.cpp @@ -515,9 +515,9 @@ void W3DTankTruckDraw::doDrawModule(const Matrix3D* transformMtx) if (!TheGlobalData->m_showClientPhysics) return; - Bool frozen = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - frozen = frozen || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); - if (frozen) + // TheSuperHackers @tweak Update the draw on every WW Sync only. + // All calculations are originally catered to a 30 fps logic step. + if (WW3D::Get_Frame_Time() == 0) return; const Real ACCEL_THRESHOLD = 0.01f; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp index 24aaad8ec6a..7a68b8e71e5 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DTruckDraw.cpp @@ -388,12 +388,14 @@ void W3DTruckDraw::doDrawModule(const Matrix3D* transformMtx) if (!TheGlobalData->m_showClientPhysics) return; + const W3DTruckDrawModuleData *moduleData = getW3DTruckDrawModuleData(); - if (moduleData==NULL) return; // shouldn't ever happen. + if (moduleData==NULL) + return; // shouldn't ever happen. - Bool frozen = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished(); - frozen = frozen || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript(); - if (frozen) + // TheSuperHackers @tweak Update the draw on every WW Sync only. + // All calculations are originally catered to a 30 fps logic step. + if (WW3D::Get_Frame_Time() == 0) return; const Real ACCEL_THRESHOLD = 0.01f; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index f0fb3f2900a..f73d5af2214 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -1677,52 +1677,9 @@ Int W3DDisplay::getLastFrameDrawCalls() return Debug_Statistics::Get_Draw_Calls(); } -Bool W3DDisplay::isTimeFrozen() -{ - if (TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished()) - return true; - - if (TheScriptEngine->isTimeFrozenDebug()) - return true; - - if (TheScriptEngine->isTimeFrozenScript()) - return true; - - if (TheGameLogic->isGamePaused()) - return true; - - return false; -} - -// TheSuperHackers @tweak xezon 12/08/2025 The WW3D Sync is no longer tied -// to the render update, but is advanced separately for every fixed time step. +//============================================================================= void W3DDisplay::step() { - // TheSuperHackers @info This will wrap in 1205 hours at 30 fps logic step. - static UnsignedInt syncTime = 0; - - extern HWND ApplicationHWnd; - if (ApplicationHWnd && ::IsIconic(ApplicationHWnd)) { - return; - } - - if (TheGlobalData->m_headless) - return; - - Bool freezeTime = isTimeFrozen(); - - if (!freezeTime) - { - syncTime += (UnsignedInt)TheW3DFrameLengthInMsec; - - if (TheScriptEngine->isTimeFast()) - { - return; - } - } - - WW3D::Sync( syncTime ); - stepViews(); } @@ -1819,12 +1776,7 @@ void W3DDisplay::draw( void ) // //PredictiveLODOptimizerClass::Optimize_LODs( 5000 ); - Bool freezeTime = isTimeFrozen(); - - // hack to let client spin fast in network games but still do effects at the same pace. -MDC - static UnsignedInt lastFrame = ~0; - freezeTime = freezeTime || (TheNetwork != NULL && lastFrame == TheGameClient->getFrame()); - lastFrame = TheGameClient->getFrame(); + Bool freezeTime = TheGameEngine->isTimeFrozen() || TheGameEngine->isGameHalted(); /// @todo: I'm assuming the first view is our main 3D view. W3DView *primaryW3DView=(W3DView *)getFirstView(); @@ -1860,6 +1812,8 @@ void W3DDisplay::draw( void ) } } + WW3D::Add_Frame_Time(TheGameEngine->getLogicTimeStepMilliseconds()); + static Int now; now=timeGetTime(); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp index 41dce19ad42..35e570dce08 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DView.cpp @@ -410,8 +410,7 @@ void W3DView::buildCameraTransform( Matrix3D *transform ) //WST 11/12/2002 New camera shaker system // TheSuperHackers @tweak The camera shaker is now decoupled from the render update. - const Real logicTimeScaleOverFpsRatio = TheGameEngine->getActualLogicTimeScaleOverFpsRatio(); - CameraShakerSystem.Timestep(TheW3DFrameLengthInMsec * logicTimeScaleOverFpsRatio); + CameraShakerSystem.Timestep(TheGameEngine->getLogicTimeStepMilliseconds()); CameraShakerSystem.Update_Camera_Shaker(sourcePos, &m_shakerAngles); transform->Rotate_X(m_shakerAngles.X); transform->Rotate_Y(m_shakerAngles.Y); @@ -701,7 +700,7 @@ void W3DView::reset( void ) static void drawDrawable( Drawable *draw, void *userData ) { - draw->draw( (View *)userData ); + draw->draw(); } // end drawDrawable @@ -1057,8 +1056,8 @@ Bool W3DView::updateCameraMovements() } else if (m_doingMoveCameraOnWaypointPath) { m_previousLookAtPosition = *getPosition(); // TheSuperHackers @tweak The scripted camera movement is now decoupled from the render update. - const Real logicTimeScaleOverFpsRatio = TheGameEngine->getActualLogicTimeScaleOverFpsRatio(); - moveAlongWaypointPath(TheW3DFrameLengthInMsec * logicTimeScaleOverFpsRatio); + // The scripted camera will still move when the time is frozen, but not when the game is halted. + moveAlongWaypointPath(TheGameEngine->getLogicTimeStepMilliseconds(GameEngine::IgnoreFrozenTime)); didUpdate = true; } if (m_doingScriptedCameraLock) @@ -1082,13 +1081,9 @@ void W3DView::updateView(void) UPDATE(); } -// TheSuperHackers @tweak xezon 12/08/2025 The drawable update is no longer tied to the -// render update, but it advanced separately for every fixed time step. This ensures that -// things like vehicle wheels no longer spin too fast on high frame rates or keep spinning -// on game pause. -// The camera shaker is also no longer tied to the render update. The shake does sharp shakes -// on every fixed time step, and is not intended to have linear interpolation during the -// render update. +// TheSuperHackers @tweak xezon 12/08/2025 The camera shaker is no longer tied to the render +// update. The shake does sharp shakes on every fixed time step, and is not intended to have +// linear interpolation during the render update. void W3DView::stepView() { // @@ -1113,17 +1108,6 @@ void W3DView::stepView() m_shakeOffset.x = 0.0f; m_shakeOffset.y = 0.0f; } - - if (TheScriptEngine->isTimeFast()) { - return; // don't draw - makes it faster :) jba. - } - - Region3D axisAlignedRegion; - getAxisAlignedViewRegion(axisAlignedRegion); - - // render all of the visible Drawables - /// @todo this needs to use a real region partition or something - TheGameClient->iterateDrawablesInRegion( &axisAlignedRegion, drawDrawable, this ); } //DECLARE_PERF_TIMER(W3DView_updateView) @@ -1408,9 +1392,16 @@ void W3DView::update(void) #ifdef DO_SEISMIC_SIMULATIONS - // Give the terrain a chance to refresh animaing (Seismic) regions, if any. + // Give the terrain a chance to refresh animating (Seismic) regions, if any. TheTerrainVisual->updateSeismicSimulations(); #endif + + Region3D axisAlignedRegion; + getAxisAlignedViewRegion(axisAlignedRegion); + + // render all of the visible Drawables + /// @todo this needs to use a real region partition or something + TheGameClient->iterateDrawablesInRegion( &axisAlignedRegion, drawDrawable, NULL ); } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp index 2168ea42357..fd88ad00cc1 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp @@ -166,6 +166,7 @@ const char* DAZZLE_INI_FILENAME="DAZZLE.INI"; ** ***********************************************************************************/ +float WW3D::FractionalSyncMs = 0.0f; unsigned int WW3D::SyncTime = 0; unsigned int WW3D::PreviousSyncTime = 0; bool WW3D::IsSortingEnabled = true; @@ -1162,6 +1163,23 @@ unsigned int WW3D::Get_Last_Frame_Vertex_Count(void) return Debug_Statistics::Get_DX8_Vertices(); } +void WW3D::Add_Frame_Time(float milliseconds) +{ + FractionalSyncMs += milliseconds; + unsigned int integralSyncMs = (unsigned int)FractionalSyncMs; + +#if MSEC_PER_WWSYNC_FRAME + if (integralSyncMs < MSEC_PER_WWSYNC_FRAME) + { + Sync(SyncTime); + return; + } +#endif + + FractionalSyncMs -= integralSyncMs; + Sync(SyncTime + integralSyncMs); +} + /*********************************************************************************************** * WW3D::Sync -- Time sychronization * diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h index bf453bc9d04..af2261961ea 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h @@ -162,7 +162,9 @@ class WW3D static void Flip_To_Primary(void); - + // TheSuperHackers @info Call this function to accumulate fractional render time. + // It will then call Sync with a new time on its own once an appropriate amount of time has passed. + static void Add_Frame_Time(float milliseconds); /* ** Timing ** By calling the Sync function, the application can move the ww3d library time forward. This @@ -321,6 +323,8 @@ class WW3D static void Allocate_Debug_Resources(void); static void Release_Debug_Resources(void); + static float FractionalSyncMs; + // Timing info: // The absolute synchronized frame time (in milliseconds) supplied by the // application at the start of every frame. Note that wraparound cases