diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp index a0c29874778..fa63226ed0f 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTreeBuffer.cpp @@ -468,7 +468,8 @@ void W3DTreeBuffer::updateTexture() GDIFileStream2 theStream(theFile); InputStream *pStr = &theStream; Bool halfTile; - Int numTiles = WorldHeightMap::countTiles(pStr, &halfTile); + Bool isLegacyGrid = false; + Int numTiles = WorldHeightMap::countTiles(pStr, &halfTile, 0, &isLegacyGrid); Int width; for (width = 10; width >= 1; width--) { if (numTiles >= width*width) { @@ -496,7 +497,7 @@ void W3DTreeBuffer::updateTexture() m_treeTypes[i].m_tileWidth = width; m_treeTypes[i].m_numTiles = numTiles; m_treeTypes[i].m_halfTile = halfTile; - WorldHeightMap::readTiles(pStr, m_sourceTiles+m_treeTypes[i].m_firstTile, width); + WorldHeightMap::readTiles(pStr, m_sourceTiles+m_treeTypes[i].m_firstTile, width, isLegacyGrid); m_numTiles += numTiles; } else { m_treeTypes[i].m_firstTile = 0; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp index 53938618c14..fe2ce1d75df 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8renderer.cpp @@ -1285,59 +1285,6 @@ void DX8SkinFVFCategoryContainer::Log(bool only_visible) // ---------------------------------------------------------------------------- -#include -#include - -void LogSkinRender(DX8TextureCategoryClass* category, int pass) { - static std::ofstream logfile("/Users/felipebraz/PhpstormProjects/pessoal/GeneralsX/logs/skin_render.log", std::ios::app); - if (logfile.is_open()) { - logfile << "--- Skin Render Pass " << pass << " ---\n"; - VertexMaterialClass *vmaterial = const_cast(category->Peek_Material()); - if (vmaterial) { - logfile << " Material Name: " << (vmaterial->Get_Name() ? vmaterial->Get_Name() : "null") << "\n"; - Vector3 amb, diff, emiss; - vmaterial->Get_Ambient(&amb); - vmaterial->Get_Diffuse(&diff); - vmaterial->Get_Emissive(&emiss); - logfile << " Ambient: (" << amb.X << ", " << amb.Y << ", " << amb.Z << ")\n"; - logfile << " Diffuse: (" << diff.X << ", " << diff.Y << ", " << diff.Z << ")\n"; - logfile << " Emissive: (" << emiss.X << ", " << emiss.Y << ", " << emiss.Z << ")\n"; - logfile << " UseLighting: " << vmaterial->Get_Lighting() << "\n"; - logfile << " DiffuseSrc: " << vmaterial->Get_Diffuse_Color_Source() << "\n"; - logfile << " AmbientSrc: " << vmaterial->Get_Ambient_Color_Source() << "\n"; - } else { - logfile << " Material: null\n"; - } - ShaderClass theShader = category->Get_Shader(); - logfile << " Shader bits: " << std::hex << theShader.Get_Bits() << std::dec << "\n"; - logfile << " Texturing: " << theShader.Get_Texturing() << "\n"; - logfile << " PrimaryGradient: " << theShader.Get_Primary_Gradient() << "\n"; - logfile << " SrcBlend: " << theShader.Get_Src_Blend_Func() << "\n"; - logfile << " DstBlend: " << theShader.Get_Dst_Blend_Func() << "\n"; - TextureClass* tex0 = category->Peek_Texture(0); - logfile << " Texture 0: " << (tex0 ? tex0->Get_Texture_Name().str() : "null") << "\n"; - TextureClass* tex1 = category->Peek_Texture(1); - logfile << " Texture 1: " << (tex1 ? tex1->Get_Texture_Name().str() : "null") << "\n"; - - PolyRenderTaskClass * prt = category->render_task_head; - if (prt) { - MeshClass * mesh = prt->Peek_Mesh(); - logfile << " First Mesh in category: " << (mesh ? mesh->Get_Name() : "null") << "\n"; - if (mesh && mesh->Peek_Model()) { - int vc = mesh->Peek_Model()->Get_Vertex_Count(); - logfile << " Vertex Count: " << vc << "\n"; - const Vector3* normals = mesh->Peek_Model()->Get_Vertex_Normal_Array(); - if (normals) { - logfile << " First normal: (" << normals[0].X << ", " << normals[0].Y << ", " << normals[0].Z << ")\n"; - } else { - logfile << " Normals: null\n"; - } - } - } - logfile << "\n"; - } -} - void DX8SkinFVFCategoryContainer::Render() { SNAPSHOT_SAY(("DX8SkinFVFCategoryContainer::Render()")); @@ -1474,7 +1421,6 @@ void DX8SkinFVFCategoryContainer::Render() TextureCategoryListIterator it(&visible_texture_category_list[pass]); while (!it.Is_Done()) { - LogSkinRender(it.Peek_Obj(), pass); it.Peek_Obj()->Render(); it.Next(); } diff --git a/Generals/Code/GameEngine/Include/Common/GameEngine.h b/Generals/Code/GameEngine/Include/Common/GameEngine.h index 67a45eb5f2e..5317e494519 100644 --- a/Generals/Code/GameEngine/Include/Common/GameEngine.h +++ b/Generals/Code/GameEngine/Include/Common/GameEngine.h @@ -80,9 +80,9 @@ class GameEngine : public SubsystemInterface virtual void resetSubsystems(); - Bool canUpdateGameLogic(); + Bool canUpdateGameLogic(UnsignedInt logicTimeQueryFlags = 0); Bool canUpdateNetworkGameLogic(); - Bool canUpdateRegularGameLogic(); + Bool canUpdateRegularGameLogic(UnsignedInt logicTimeQueryFlags); virtual FileSystem *createFileSystem(); ///< Factory for FileSystem classes virtual LocalFileSystem *createLocalFileSystem() = 0; ///< Factory for LocalFileSystem classes diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp index 29497077ae9..0586440ca7c 100644 --- a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp @@ -751,7 +751,7 @@ void GameEngine::resetSubsystems() } /// ----------------------------------------------------------------------------------------------- -Bool GameEngine::canUpdateGameLogic() +Bool GameEngine::canUpdateGameLogic(UnsignedInt logicTimeQueryFlags) { // This updates the paused game status of the game logic. TheGameLogic->preUpdate(); @@ -765,7 +765,7 @@ Bool GameEngine::canUpdateGameLogic() } else { - return canUpdateRegularGameLogic(); + return canUpdateRegularGameLogic(logicTimeQueryFlags); } } @@ -786,10 +786,16 @@ Bool GameEngine::canUpdateNetworkGameLogic() } /// ----------------------------------------------------------------------------------------------- -Bool GameEngine::canUpdateRegularGameLogic() +Bool GameEngine::canUpdateRegularGameLogic(UnsignedInt logicTimeQueryFlags) { + const Int logicTimeScaleFps = TheFramePacer->getActualLogicTimeScaleFps(logicTimeQueryFlags); + + if (logicTimeScaleFps <= 0) + { + return false; + } + const Bool enabled = TheFramePacer->isLogicTimeScaleEnabled(); - const Int logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); const Int maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) @@ -849,7 +855,7 @@ void GameEngine::update() } // end VERIFY_CRC block // TheSuperHackers @info Ignores frozen time because the script engine needs updating in the logic update regardless. - if (canUpdateGameLogic()) + if (canUpdateGameLogic(FramePacer::IgnoreFrozenTime)) { TheGameLogic->UPDATE(); diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h index e612c98f6d3..fedb5f4bbd1 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameEngine.h @@ -82,9 +82,9 @@ class GameEngine : public SubsystemInterface virtual void resetSubsystems(); - Bool canUpdateGameLogic(); + Bool canUpdateGameLogic(UnsignedInt logicTimeQueryFlags = 0); Bool canUpdateNetworkGameLogic(); - Bool canUpdateRegularGameLogic(); + Bool canUpdateRegularGameLogic(UnsignedInt logicTimeQueryFlags); virtual FileSystem *createFileSystem(); ///< Factory for FileSystem classes virtual LocalFileSystem *createLocalFileSystem() = 0; ///< Factory for LocalFileSystem classes diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 3aeba60eb5b..656e97428f7 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -914,7 +914,7 @@ void GameEngine::resetSubsystems() } /// ----------------------------------------------------------------------------------------------- -Bool GameEngine::canUpdateGameLogic() +Bool GameEngine::canUpdateGameLogic(UnsignedInt logicTimeQueryFlags) { // This updates the paused game status of the game logic. TheGameLogic->preUpdate(); @@ -928,7 +928,7 @@ Bool GameEngine::canUpdateGameLogic() } else { - return canUpdateRegularGameLogic(); + return canUpdateRegularGameLogic(logicTimeQueryFlags); } } @@ -949,10 +949,16 @@ Bool GameEngine::canUpdateNetworkGameLogic() } /// ----------------------------------------------------------------------------------------------- -Bool GameEngine::canUpdateRegularGameLogic() +Bool GameEngine::canUpdateRegularGameLogic(UnsignedInt logicTimeQueryFlags) { + const Int logicTimeScaleFps = TheFramePacer->getActualLogicTimeScaleFps(logicTimeQueryFlags); + + if (logicTimeScaleFps <= 0) + { + return false; + } + const Bool enabled = TheFramePacer->isLogicTimeScaleEnabled(); - const Int logicTimeScaleFps = TheFramePacer->getLogicTimeScaleFps(); const Int maxRenderFps = TheFramePacer->getFramesPerSecondLimit(); #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) @@ -1012,7 +1018,7 @@ void GameEngine::update() } // TheSuperHackers @info Ignores frozen time because the script engine needs updating in the logic update regardless. - if (canUpdateGameLogic()) + if (canUpdateGameLogic(FramePacer::IgnoreFrozenTime)) { TheGameLogic->UPDATE(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp index 6f88eff654d..4d6de2cd737 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp @@ -97,11 +97,10 @@ SpecialPowerModule::SpecialPowerModule( Thing *thing, const ModuleData *moduleDa : BehaviorModule( thing, moduleData ) { -#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix fbraz 13/07/2026: Always initialize to 0. + // Disabling RETAIL_COMPATIBLE_CRC previously forced this to 0xFFFFFFFF, which caused unpaused + // targetless special powers (like Spy Satellite and Battle Plans) to never become ready. m_availableOnFrame = 0; -#else - m_availableOnFrame = 0xFFFFFFFF; -#endif m_pausedCount = 0; m_pausedOnFrame = 0; m_pausedPercent = 0.0f; diff --git a/docs/WORKLOG/2026-07-DIARY.md b/docs/WORKLOG/2026-07-DIARY.md index ffdc5d3aa57..846a9977566 100644 --- a/docs/WORKLOG/2026-07-DIARY.md +++ b/docs/WORKLOG/2026-07-DIARY.md @@ -1,5 +1,12 @@ # July 2026 +## 13/07/2026 +### Fixed SpecialPowerModule and Tree Render Segfault (Beta-13 Regressions) +- **Pause Bug Fix**: Adapted upstream's solution (TheSuperHackers commit `fa2761324`) which passes `logicTimeQueryFlags` into `canUpdateGameLogic` and `getActualLogicTimeScaleFps`, returning false if the frame pacing is 0. This completely avoids logic time accumulation while the game is halted (Esc menu), providing a more robust fix than our initial `!TheFramePacer->isGameHalted()` loop hack. Applied to both Generals and Zero Hour. +- **Skin Render Log Cleanup**: Removed legacy debug logging function `LogSkinRender` and its usage in `dx8renderer.cpp`, which was needlessly writing to `logs/skin_render.log`. +- **SpecialPowerModule Fix**: Universalized `m_availableOnFrame` initialization to `0` instead of `0xFFFFFFFF` when `RETAIL_COMPATIBLE_CRC` is disabled. This resolved the regression where USA Command Center (Spy Satellite) and Strategy Center (Battle Plans) buttons were visually enabled but non-functional due to infinite frame recharge math upon unpausing. +- **Tree Render Segfault Fix**: Fixed `W3DTreeBuffer::updateTexture()` by properly capturing the `isLegacyGrid` flag from `WorldHeightMap::countTiles` and passing it to `WorldHeightMap::readTiles`. Previously, passing default `false` for legacy tree sheets caused `readTiles` to skip reading, leaving `m_sourceTiles` uninitialized and leading to a segfault (`EXC_BAD_ACCESS`) during scene rendering after the intro. + ## 12/07/2026 ### Default CNC_GENERALS_PATH Fallback - Defined `CNC_GENERALS_PATH` to default to `~/GeneralsX/Generals` when the variable is empty in the `run.sh` scripts of Zero Hour to address issue #205. This affects the Linux flatpak, linux run/deploy scripts, and macOS bundle/run scripts.