diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index 67c123c0d4b..262d24be078 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -105,6 +105,10 @@ #define PRESERVE_RETAIL_SCRIPTED_CAMERA (1) // Retain scripted camera behavior present in retail Generals 1.08 and Zero Hour 1.04 #endif +#ifndef PRESERVE_RETAIL_PARTICLES +#define PRESERVE_RETAIL_PARTICLES (1) // Preserve original look of particles present in retail Generals 1.08 and Zero Hour 1.04 +#endif + #ifndef RETAIL_COMPATIBLE_CRC #if defined(GENERALS_ONLINE) #define RETAIL_COMPATIBLE_CRC (0) diff --git a/Core/GameEngine/Include/GameClient/ParticleSys.h b/Core/GameEngine/Include/GameClient/ParticleSys.h index acd504f6838..f11ec05cd83 100644 --- a/Core/GameEngine/Include/GameClient/ParticleSys.h +++ b/Core/GameEngine/Include/GameClient/ParticleSys.h @@ -57,9 +57,11 @@ enum ParticleSystemID CPP_11(: Int) INVALID_PARTICLE_SYSTEM_ID = 0 }; -#define MAX_VOLUME_PARTICLE_DEPTH ( 16 ) -#define DEFAULT_VOLUME_PARTICLE_DEPTH ( 0 )//The Default is not to do the volume thing! -#define OPTIMUM_VOLUME_PARTICLE_DEPTH ( 6 ) +constexpr const UnsignedInt INVALID_VOLUME_PARTICLE_DEPTH = 0; //The volume depth is not initialized +constexpr const UnsignedInt DEFAULT_VOLUME_PARTICLE_DEPTH = 1; //The Default is not to do the volume thing! +constexpr const UnsignedInt MIN_VOLUME_PARTICLE_DEPTH = 2; +constexpr const UnsignedInt OPTIMUM_VOLUME_PARTICLE_DEPTH = 6; +constexpr const UnsignedInt MAX_VOLUME_PARTICLE_DEPTH = 16; // TheSuperHackers @info The X and Y angles are not necessary for particles because there are only 2 placement modes: // Billboard (always facing camera) and Ground Aligned, which overwrite any rotations on the X and Y axis by design. @@ -511,7 +513,9 @@ class ParticleSystemTemplate : public MemoryPoolObject, protected ParticleSystem public: ParticleSystemTemplate( const AsciiString &name ); - AsciiString getName() const { return m_name; } + void validate(); + + const AsciiString& getName() const { return m_name; } // This function was made const because of update modules' module data being all const. ParticleSystem *createSlaveSystem( Bool createSlaves = TRUE ) const ; ///< if returns non-null, it is a slave system for use @@ -602,15 +606,17 @@ class ParticleSystem : public MemoryPoolObject, void setInitialDelay( UnsignedInt delay ) { m_delayLeft = delay; } - AsciiString getParticleTypeName() { return m_particleTypeName; } ///< return the name of the particles - Bool isUsingDrawables() { return (m_particleType == DRAWABLE) ? true : false; } - Bool isUsingStreak() { return (m_particleType == STREAK) ? true : false; } - Bool isUsingSmudge() { return (m_particleType == SMUDGE) ? true : false; } - UnsignedInt getVolumeParticleDepth() { return ( m_particleType == VOLUME_PARTICLE ) ? OPTIMUM_VOLUME_PARTICLE_DEPTH : 0; } + const AsciiString& getParticleTypeName() const { return m_particleTypeName; } ///< return the name of the particles + Bool isUsingParticles() const { return m_particleType == PARTICLE; } + Bool isUsingDrawables() const { return m_particleType == DRAWABLE; } + Bool isUsingStreak() const { return m_particleType == STREAK; } + Bool isUsingSmudge() const { return m_particleType == SMUDGE; } + Bool isUsingVolumeParticles() const { return m_particleType == VOLUME_PARTICLE; } + UnsignedInt getVolumeParticleDepth() const { return m_volumeParticleDepth; } - Bool shouldBillboard() { return !m_isGroundAligned; } + Bool shouldBillboard() const { return !m_isGroundAligned; } - ParticleShaderType getShaderType() { return m_shaderType; } + ParticleShaderType getShaderType() const { return m_shaderType; } void setSlave( ParticleSystem *slave ); ///< set a slave system for us ParticleSystem *getSlave() { return m_slaveSystem; } diff --git a/Core/GameEngine/Include/GameNetwork/FirewallHelper.h b/Core/GameEngine/Include/GameNetwork/FirewallHelper.h index 352b1a040bc..a1d4bade996 100644 --- a/Core/GameEngine/Include/GameNetwork/FirewallHelper.h +++ b/Core/GameEngine/Include/GameNetwork/FirewallHelper.h @@ -190,7 +190,7 @@ class FirewallHelperClass { void flagNeedToRefresh(Bool flag); - static void getManglerName(Int manglerIndex, Char *nameBuf); + static AsciiString getManglerName(Int manglerIndex); Bool sendToManglerFromPort(UnsignedInt address, UnsignedShort port, UnsignedShort packetID, Bool blitzme = FALSE); UnsignedShort getManglerResponse(UnsignedShort packetID, Int time = 0); Bool openSpareSocket(UnsignedShort port); diff --git a/Core/GameEngine/Include/GameNetwork/GameInfo.h b/Core/GameEngine/Include/GameNetwork/GameInfo.h index 5483bbeba58..06a44239451 100644 --- a/Core/GameEngine/Include/GameNetwork/GameInfo.h +++ b/Core/GameEngine/Include/GameNetwork/GameInfo.h @@ -302,6 +302,7 @@ class SkirmishGameInfo : public GameInfo, public Snapshot for (Int i = 0; i< MAX_SLOTS; ++i) setSlotPointer(i, &m_skirmishSlot[i]); } + virtual ~SkirmishGameInfo() override {} }; extern SkirmishGameInfo *TheSkirmishGameInfo; diff --git a/Core/GameEngine/Include/GameNetwork/GameSpy/StagingRoomGameInfo.h b/Core/GameEngine/Include/GameNetwork/GameSpy/StagingRoomGameInfo.h index d17569bc807..5ed92588a8b 100644 --- a/Core/GameEngine/Include/GameNetwork/GameSpy/StagingRoomGameInfo.h +++ b/Core/GameEngine/Include/GameNetwork/GameSpy/StagingRoomGameInfo.h @@ -96,6 +96,7 @@ class GameSpyStagingRoom : public GameInfo public: GameSpyStagingRoom(); + virtual ~GameSpyStagingRoom() override {} virtual void reset() override; void cleanUpSlotPointers(); diff --git a/Core/GameEngine/Include/GameNetwork/LANGameInfo.h b/Core/GameEngine/Include/GameNetwork/LANGameInfo.h index 2d0cdbd202a..1296d47dcb4 100644 --- a/Core/GameEngine/Include/GameNetwork/LANGameInfo.h +++ b/Core/GameEngine/Include/GameNetwork/LANGameInfo.h @@ -79,6 +79,7 @@ class LANGameInfo : public GameInfo public: LANGameInfo(); + virtual ~LANGameInfo() override {} void setSlot( Int slotNum, LANGameSlot slotInfo ); ///< Set the slot state (human, open, AI, etc) LANGameSlot* getLANSlot( Int slotNum ); ///< Get the slot const LANGameSlot* getConstLANSlot( Int slotNum ) const; ///< Get the slot diff --git a/Core/GameEngine/Source/Common/AddonCompat.cpp b/Core/GameEngine/Source/Common/AddonCompat.cpp index 6d88feb47d6..a877ec1edda 100644 --- a/Core/GameEngine/Source/Common/AddonCompat.cpp +++ b/Core/GameEngine/Source/Common/AddonCompat.cpp @@ -29,6 +29,7 @@ Bool HasFullviewportDat() if (File* file = TheFileSystem->openFile("GenTool/fullviewport.dat", File::READ | File::BINARY)) { file->read(&value, 1); + file->close(); } return value != '0'; } diff --git a/Core/GameEngine/Source/Common/INI/INI.cpp b/Core/GameEngine/Source/Common/INI/INI.cpp index c03028c6d4c..1727a289bd9 100644 --- a/Core/GameEngine/Source/Common/INI/INI.cpp +++ b/Core/GameEngine/Source/Common/INI/INI.cpp @@ -916,7 +916,7 @@ void INI::parsePercentToReal( INI* ini, void * /*instance*/, void *store, const //------------------------------------------------------------------------------------------------- void INI::parseBitString8( INI* ini, void * /*instance*/, void *store, const void* userData ) { - UnsignedInt tmp; + UnsignedInt tmp = *(Byte*)store; INI::parseBitString32(ini, nullptr, &tmp, userData); if (tmp & 0xffffff00) { diff --git a/Core/GameEngine/Source/Common/INI/INIParticleSys.cpp b/Core/GameEngine/Source/Common/INI/INIParticleSys.cpp index fd0b84d5182..e72ae819664 100644 --- a/Core/GameEngine/Source/Common/INI/INIParticleSys.cpp +++ b/Core/GameEngine/Source/Common/INI/INIParticleSys.cpp @@ -54,4 +54,5 @@ void INI::parseParticleSystemDefinition( INI* ini ) // parse the ini definition ini->initFromINI( sysTemplate, sysTemplate->getFieldParse() ); + sysTemplate->validate(); } diff --git a/Core/GameEngine/Source/Common/System/Debug.cpp b/Core/GameEngine/Source/Common/System/Debug.cpp index 2ae4d88a77f..3bd8225a0f6 100644 --- a/Core/GameEngine/Source/Common/System/Debug.cpp +++ b/Core/GameEngine/Source/Common/System/Debug.cpp @@ -811,16 +811,22 @@ void ReleaseCrash(const char *reason) #if defined(RTS_DEBUG) /* static */ char buff[8192]; // not so static so we can be threadsafe snprintf(buff, 8192, "Sorry, a serious error occurred. (%s)", reason); - ::MessageBox(nullptr, buff, "Technical Difficulties...", MB_OK|MB_SYSTEMMODAL|MB_ICONERROR); + if (!(TheGlobalData && TheGlobalData->m_headless)) + { + ::MessageBox(nullptr, buff, "Technical Difficulties...", MB_OK|MB_SYSTEMMODAL|MB_ICONERROR); + } #else // crash error messaged changed 3/6/03 BGC // ::MessageBox(nullptr, "Sorry, a serious error occurred.", "Technical Difficulties...", MB_OK|MB_TASKMODAL|MB_ICONERROR); // ::MessageBox(nullptr, "You have encountered a serious error. Serious errors can be caused by many things including viruses, overheated hardware and hardware that does not meet the minimum specifications for the game. Please visit the forums at www.generals.ea.com for suggested courses of action or consult your manual for Technical Support contact information.", "Technical Difficulties...", MB_OK|MB_TASKMODAL|MB_ICONERROR); // crash error message changed again 8/22/03 M Lorenzen... made this message box modal to the system so it will appear on top of any task-modal windows, splash-screen, etc. - ::MessageBox(nullptr, "You have encountered a serious error. Serious errors can be caused by many things including viruses, overheated hardware and hardware that does not meet the minimum specifications for the game. Please visit the forums at www.generals.ea.com for suggested courses of action or consult your manual for Technical Support contact information.", - "Technical Difficulties...", - MB_OK|MB_SYSTEMMODAL|MB_ICONERROR); + if (!(TheGlobalData && TheGlobalData->m_headless)) + { + ::MessageBox(nullptr, "You have encountered a serious error. Serious errors can be caused by many things including viruses, overheated hardware and hardware that does not meet the minimum specifications for the game. Please visit the forums at www.generals.ea.com for suggested courses of action or consult your manual for Technical Support contact information.", + "Technical Difficulties...", + MB_OK|MB_SYSTEMMODAL|MB_ICONERROR); + } #endif @@ -850,7 +856,10 @@ void ReleaseCrashLocalized(const AsciiString& p, const AsciiString& m) } } - ::MessageBoxW(nullptr, mesg.str(), prompt.str(), MB_OK | MB_SYSTEMMODAL | MB_ICONERROR); + if (!(TheGlobalData && TheGlobalData->m_headless)) + { + ::MessageBoxW(nullptr, mesg.str(), prompt.str(), MB_OK | MB_SYSTEMMODAL | MB_ICONERROR); + } char prevbuf[ _MAX_PATH ]; char curbuf[ _MAX_PATH ]; diff --git a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp index 6d072c854ad..eb85eaa1887 100644 --- a/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/ControlBar/ControlBar.cpp @@ -2742,13 +2742,15 @@ void ControlBar::showRallyPoint(const Coord3D* loc) if (m_rallyPointDrawableID == INVALID_DRAWABLE_ID) { const ThingTemplate* ttn = TheThingFactory->findTemplate("RallyPointMarker"); - marker = TheThingFactory->newDrawable(ttn); - DEBUG_ASSERTCRASH(marker, ("showRallyPoint: Unable to create rally point drawable")); - if (marker) + if (!ttn) { - marker->setDrawableStatus(DRAWABLE_STATUS_NO_SAVE); - m_rallyPointDrawableID = marker->getID(); + return; } + + marker = TheThingFactory->newDrawable(ttn); + DEBUG_ASSERTCRASH(marker, ("showRallyPoint: Unable to create rally point drawable")); + marker->setDrawableStatus(DRAWABLE_STATUS_NO_SAVE); + m_rallyPointDrawableID = marker->getID(); } else marker = TheGameClient->findDrawableByID(m_rallyPointDrawableID); @@ -3602,6 +3604,7 @@ void ControlBar::updateSpecialPowerShortcut() } else if( !hasValidShortcutButton && !m_specialPowerShortcutParent->winIsHidden() + && m_animateWindowManagerForGenShortcuts && m_animateWindowManagerForGenShortcuts->isFinished() ) { animateSpecialPowerShortcut(FALSE); diff --git a/Core/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp b/Core/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp index b9058daab1e..458bde534ae 100644 --- a/Core/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/GUICallbacks/ControlBarCallback.cpp @@ -486,9 +486,29 @@ WindowMsgHandledType ControlBarSystem( GameWindow *window, UnsignedInt msg, } -extern void showReplayControls(); -extern void hideReplayControls(); -extern void toggleReplayControls(); +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void showReplayControls() +{ + if (TheInGameUI != nullptr) + TheInGameUI->showReplayControls(); +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void hideReplayControls() +{ + if (TheInGameUI != nullptr) + TheInGameUI->hideReplayControls(); +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void toggleReplayControls() +{ + if (TheInGameUI != nullptr) + TheInGameUI->toggleReplayControls(); +} //------------------------------------------------------------------------------------------------- /** Force the control bar to be shown */ diff --git a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp index 09f22640cf4..2236c4121f7 100644 --- a/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp +++ b/Core/GameEngine/Source/GameClient/GUI/GameWindowTransitions.cpp @@ -533,7 +533,13 @@ void GameWindowTransitionsHandler::reverse( AsciiString groupName ) void GameWindowTransitionsHandler::remove( AsciiString groupName, Bool skipPending ) { - TransitionGroup *g = findGroup(groupName); + TransitionGroup* g = findGroup(groupName); + + if (!g) + { + return; + } + if(m_pendingGroup == g) { if(skipPending) diff --git a/Core/GameEngine/Source/GameClient/MapUtil.cpp b/Core/GameEngine/Source/GameClient/MapUtil.cpp index ef776c2e1f7..5ea1a305944 100644 --- a/Core/GameEngine/Source/GameClient/MapUtil.cpp +++ b/Core/GameEngine/Source/GameClient/MapUtil.cpp @@ -1142,53 +1142,38 @@ const MapMetaData *MapCache::findMap(AsciiString mapName) // ------------------------------------------------------------------------------------------------ /** Embed the pristine map into the xfer stream */ // ------------------------------------------------------------------------------------------------ -static void copyFromBigToDir( const AsciiString& infile, const AsciiString& outfile ) +static Bool copyFromBigToDir( const AsciiString& infile, const AsciiString& outfile ) { - // open the map file - File *file = TheFileSystem->openFile( infile.str(), File::READ | File::BINARY ); if( file == nullptr ) { DEBUG_CRASH(( "copyFromBigToDir - Error opening source file '%s'", infile.str() )); - throw SC_INVALID_DATA; + return FALSE; } - // how big is the map file - Int fileSize = file->seek( 0, File::END ); - - - // rewind to beginning of file + const Int fileSize = file->seek( 0, File::END ); file->seek( 0, File::START ); - // allocate buffer big enough to hold the entire map file char *buffer = NEW char[ fileSize ]; - if( buffer == nullptr ) - { - DEBUG_CRASH(( "copyFromBigToDir - Unable to allocate buffer for file '%s'", infile.str() )); - throw SC_INVALID_DATA; - } - - // copy the file to the buffer - if( file->read( buffer, fileSize ) < fileSize ) - { - DEBUG_CRASH(( "copyFromBigToDir - Error reading from file '%s'", infile.str() )); - throw SC_INVALID_DATA; - } - // close the BIG file + const Bool readOk = file->read( buffer, fileSize ) == fileSize; file->close(); + DEBUG_ASSERTCRASH( readOk, ( "copyFromBigToDir - Error reading from file '%s'", infile.str() ) ); - File *filenew = TheFileSystem->openFile( outfile.str(), File::WRITE | File::CREATE | File::BINARY ); - - if( !filenew || filenew->write(buffer, fileSize) < fileSize) + Bool writeOk = FALSE; + if( readOk ) { - DEBUG_CRASH(( "copyFromBigToDir - Error writing to file '%s'", outfile.str() )); - throw SC_INVALID_DATA; + File *filenew = TheFileSystem->openFile( outfile.str(), File::WRITE | File::CREATE | File::BINARY ); + if( filenew != nullptr ) + { + writeOk = filenew->write( buffer, fileSize ) == fileSize; + filenew->close(); + } + DEBUG_ASSERTCRASH( writeOk, ( "copyFromBigToDir - Error writing to file '%s'", outfile.str() ) ); } - filenew->close(); - - // delete the buffer delete [] buffer; + + return writeOk; } Image *getMapPreviewImage( AsciiString mapName ) @@ -1233,16 +1218,7 @@ Image *getMapPreviewImage( AsciiString mapName ) mapPreviewDir.concat(name); - Bool success = false; - try - { - copyFromBigToDir(tgaName, mapPreviewDir); - success = true; - } - catch (...) - { - success = false; // no rethrow - } + const Bool success = copyFromBigToDir(tgaName, mapPreviewDir); if (success) { diff --git a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp index 6d5c15380c5..7091f10e018 100644 --- a/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp +++ b/Core/GameEngine/Source/GameClient/System/ParticleSys.cpp @@ -803,7 +803,7 @@ ParticleSystemInfo::ParticleSystemInfo() m_windMotionEndAngleMax = TWO_PI; m_windMotionEndAngle = m_windMotionEndAngleMin; m_windMotionMovingToEndAngle = TRUE; - m_volumeParticleDepth = DEFAULT_VOLUME_PARTICLE_DEPTH; + m_volumeParticleDepth = INVALID_VOLUME_PARTICLE_DEPTH; } @@ -1122,7 +1122,7 @@ ParticleSystem::ParticleSystem( const ParticleSystemTemplate *sysTemplate, ///@todo: further formalize this parameter with an UnsignedInt field in the editor - m_volumeParticleDepth = DEFAULT_VOLUME_PARTICLE_DEPTH; + m_volumeParticleDepth = sysTemplate->m_volumeParticleDepth; m_driftVelocity = sysTemplate->m_driftVelocity; @@ -2741,6 +2741,9 @@ const FieldParse ParticleSystemTemplate::m_fieldParseTable[] = { "SizeRate", INI::parseGameClientRandomVariable, nullptr, offsetof( ParticleSystemTemplate, m_sizeRate ) }, { "SizeRateDamping", INI::parseGameClientRandomVariable, nullptr, offsetof( ParticleSystemTemplate, m_sizeRateDamping ) }, + // TheSuperHackers @feature Volume particle depth is now exposed for configuration + { "VolParticleDepth", INI::parseUnsignedInt, nullptr, offsetof(ParticleSystemTemplate, m_volumeParticleDepth) }, + { "Alpha1", ParticleSystemTemplate::parseRandomKeyframe, nullptr, offsetof( ParticleSystemTemplate, m_alphaKey[0] ) }, { "Alpha2", ParticleSystemTemplate::parseRandomKeyframe, nullptr, offsetof( ParticleSystemTemplate, m_alphaKey[1] ) }, { "Alpha3", ParticleSystemTemplate::parseRandomKeyframe, nullptr, offsetof( ParticleSystemTemplate, m_alphaKey[2] ) }, @@ -2910,6 +2913,31 @@ ParticleSystemTemplate::~ParticleSystemTemplate() } // ------------------------------------------------------------------------------------------------ +void ParticleSystemTemplate::validate() +{ + // TheSuperHackers @info Initialise all volume particles that lack ini configuration to the optimum depth of 6 + // In retail, volume particle depth was not configurable through ini and was hard coded to a particle depth of 6 + if (m_particleType == ParticleSystemInfo::VOLUME_PARTICLE) + { + if (m_volumeParticleDepth == INVALID_VOLUME_PARTICLE_DEPTH) + m_volumeParticleDepth = OPTIMUM_VOLUME_PARTICLE_DEPTH; + } + else + { + m_volumeParticleDepth = DEFAULT_VOLUME_PARTICLE_DEPTH; + } + +#if PRESERVE_RETAIL_PARTICLES + // TheSuperHackers @info Hack to allow isUsingSmudge() functionality with retail smudge particles + // The retail data template for smudge particles is not correctly configured with the smudge particle type + if (m_particleType != ParticleSystemInfo::SMUDGE && m_particleTypeName.startsWithNoCase("SMUDGE.")) + { + m_particleType = ParticleSystemInfo::SMUDGE; + } +#endif +} + +// ------------------------------------------------------------------------------------------------ /** If returns non-null, it is a slave system for use ... the create slaves parameter * tells *this* slave system whether or not it should create any slaves itself * automatically during its own constructor */ @@ -3082,8 +3110,8 @@ void ParticleSystemManager::update() if (sys->isUsingDrawables()) continue; - // temporary hack that checks if texture name starts with "SMUD" - if so, we can assume it's a smudge type - if (/*sys->isUsingSmudge()*/ *((DWORD *)sys->getParticleTypeName().str()) == 0x44554D53) + // Handle smudge type particles + if (sys->isUsingSmudge()) { for (Particle *p = sys->getFirstParticle(); p; p = p->m_systemNext) { diff --git a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index e4773dc4cc8..f6270786838 100644 --- a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -1125,9 +1125,11 @@ void PathfindCellInfo::forceCleanPathFindCellInfos() void Pathfinder::forceCleanCells() { - UnicodeString pathfinderFailoverMessage = TheGameText->FETCH_OR_SUBSTITUTE("GUI:PathfindingCrashPrevented", L"A pathfinding crash was prevented, now switching to the crash fixed pathfinding."); + UnicodeString pathfinderFailoverMessage = TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:PathfindingCrashPrevented", L"A pathfinding crash was prevented at frame %u, now switching to the crash fixed pathfinding.", TheGameLogic->getFrame()); TheInGameUI->message(pathfinderFailoverMessage); + printf("%ls\n", pathfinderFailoverMessage.str()); + TheAudio->addAudioEvent(&TheAudio->getMiscAudio()->m_allCheerSound); PathfindCellInfo::forceCleanPathFindCellInfos(); @@ -1727,6 +1729,13 @@ void PathfindCell::forwardInsertionSortRetailCompatible(PathfindCellList& list) UnsignedInt cellCount = 0; while (currentCell && cellCount < PATHFIND_CELLS_PER_FRAME && currentCell->m_info->m_totalCost <= m_info->m_totalCost) { + // Prevent a retail crash where a pathfindCell has an m_info with a dangling nextOpen pointer + if (currentCell->m_info->m_nextOpen && !currentCell->m_info->m_nextOpen->m_cell->m_info) + { + currentCell->m_info->m_nextOpen->m_cell = nullptr; + currentCell->m_info->m_nextOpen = nullptr; + } + cellCount++; previousCell = currentCell; currentCell = currentCell->getNextOpen(); @@ -1976,7 +1985,19 @@ void PathfindCell::putOnClosedList( PathfindCellList &list ) m_info->m_prevOpen = nullptr; m_info->m_nextOpen = list.m_head ? list.m_head->m_info : nullptr; if (list.m_head) +#if RETAIL_COMPATIBLE_PATHFINDING + // TheSuperHackers @info This is only here to catch a crash point in the retail compatible pathfinding + // This crash mode occurs due to the closed list head not having an m_info associated with it + // A node cannot be put onto the closed list without an m_info under normal conditions + { + if (list.m_head->m_info) + { + list.m_head->m_info->m_prevOpen = this->m_info; + } + } +#else list.m_head->m_info->m_prevOpen = this->m_info; +#endif list.m_head = this; } @@ -7562,24 +7583,16 @@ void Pathfinder::processHierarchicalCell( const ICoord2D &scanCell, const ICoord return; } - newCell->allocateInfo(scanCell); -#if RETAIL_COMPATIBLE_PATHFINDING - if (!s_useFixedPathfinding) - { - if (!newCell->getClosed() && !newCell->getOpen()) { - newCell->putOnClosedList(m_closedList); - } - } - else +#if RTS_GENERALS && RETAIL_COMPATIBLE_PATHFINDING + // TheSuperHackers @bugfix Caball009 14/09/2026 Check newCell->m_info before accessing it to prevent a possible crash. + // The Zero Hour / non-retail compatible pathfinding code performs this check earlier in the function. + if (newCell->allocateInfo(scanCell) && !newCell->getOpen() && !newCell->getClosed()) #endif { - if (newCell->hasInfo() && !newCell->getClosed() && !newCell->getOpen()) { - newCell->putOnClosedList(m_closedList); - } + newCell->putOnClosedList(m_closedList); } - adjNewCell->allocateInfo(adjacentCell); - if( adjNewCell->hasInfo() ) + if (adjNewCell->allocateInfo(adjacentCell)) { cellCount++; diff --git a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp index f347db5a83f..6f08930bd3c 100644 --- a/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp +++ b/Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp @@ -2509,6 +2509,8 @@ void ConnectionManager::sendDisconnectChat(UnicodeString text) { sendLocalCommandDirect(msg, 0xff ^ (1 << m_localSlot)); processDisconnectChat(msg); + + msg->detach(); } UnsignedShort ConnectionManager::sendFileAnnounce(AsciiString path, UnsignedByte playerMask) diff --git a/Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp b/Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp index 04e5f7166b0..0960ef001ad 100644 --- a/Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp +++ b/Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp @@ -597,12 +597,16 @@ Short FirewallHelperClass::getSourcePortAllocationDelta() { return m_sourcePortAllocationDelta; } -/* static */ void FirewallHelperClass::getManglerName(Int manglerIndex, Char *nameBuf) +/* static */ AsciiString FirewallHelperClass::getManglerName(Int manglerIndex) { AsciiString host; UnsignedShort port; - TheGameSpyConfig->getManglerLocation(manglerIndex, host, port); - strcpy(nameBuf, host.str()); + if (TheGameSpyConfig->getManglerLocation(manglerIndex, host, port)) + { + return host; + } + + return AsciiString::TheEmptyString; } Bool FirewallHelperClass::detectionBeginUpdate() { @@ -1529,6 +1533,8 @@ Bool FirewallHelperClass::openSpareSocket(UnsignedShort port) { if (m_spareSockets[i].udp->Bind((UnsignedInt)0, port) != 0) { DEBUG_CRASH(("FirewallHelperClass::openSpareSocket - Failed to init spare socket")); + delete m_spareSockets[i].udp; + m_spareSockets[i].udp = nullptr; return FALSE; } diff --git a/Core/GameEngine/Source/GameNetwork/GameInfo.cpp b/Core/GameEngine/Source/GameNetwork/GameInfo.cpp index d7bd46bd336..a7011f3b44d 100644 --- a/Core/GameEngine/Source/GameNetwork/GameInfo.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameInfo.cpp @@ -68,6 +68,7 @@ void GameSlot::reset() m_NATBehavior = FirewallHelperClass::FIREWALL_TYPE_SIMPLE; m_lastFrameInGame = 0; m_disconnected = FALSE; + m_IP = 0; m_port = 0; m_isMuted = FALSE; m_hasSavedOriginalSetup = FALSE; diff --git a/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp b/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp index 551071fd104..0db1233b077 100644 --- a/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp +++ b/Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp @@ -776,6 +776,7 @@ int asyncGethostbyname(char * szName) /* Thread finished */ stat = 0; s_asyncDNSLookupInProgress = FALSE; + CloseHandle(s_asyncDNSThreadHandle); s_asyncDNSThreadHandle = nullptr; return( (s_asyncDNSThreadSucceeded)?LOOKUP_SUCCEEDED:LOOKUP_FAILED ); } @@ -833,6 +834,7 @@ void StopAsyncDNSCheck() MAYBE_UNUSED Int res = TerminateThread(s_asyncDNSThreadHandle, 0); (void)res; DEBUG_ASSERTCRASH(res, ("Could not terminate the Async DNS Lookup thread!")); // Thread still not killed! + CloseHandle(s_asyncDNSThreadHandle); } s_asyncDNSThreadHandle = nullptr; s_asyncDNSLookupInProgress = FALSE; diff --git a/Core/GameEngine/Source/GameNetwork/LANAPI.cpp b/Core/GameEngine/Source/GameNetwork/LANAPI.cpp index ef244dd8675..e5d5accb78f 100644 --- a/Core/GameEngine/Source/GameNetwork/LANAPI.cpp +++ b/Core/GameEngine/Source/GameNetwork/LANAPI.cpp @@ -693,6 +693,7 @@ void LANAPI::RequestGameLeave() // Exit out immediately if we're hosting OnPlayerLeave(m_name); removeGame(m_currentGame); + delete m_currentGame; m_currentGame = nullptr; m_inLobby = true; } diff --git a/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp b/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp index 701ef3e987e..5075b7cfba0 100644 --- a/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp +++ b/Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp @@ -233,6 +233,7 @@ void LANAPI::OnGameStart() DEBUG_LOG(("After transfer, we didn't really have the map. Bailing...")); OnPlayerLeave(m_name); removeGame(m_currentGame); + delete m_currentGame; m_currentGame = nullptr; m_inLobby = TRUE; diff --git a/Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp b/Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp index 7f88b835f86..6aa2956df0d 100644 --- a/Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp +++ b/Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp @@ -567,6 +567,7 @@ void LANAPI::handleRequestLobbyLeave( LANMessage *msg, UnsignedInt senderIP ) if (player->getIP() == senderIP) { removePlayer(player); + delete player; OnPlayerList(m_lobbyPlayers); break; } diff --git a/Core/GameEngine/Source/GameNetwork/NAT.cpp b/Core/GameEngine/Source/GameNetwork/NAT.cpp index fdee9824c64..99989abb296 100644 --- a/Core/GameEngine/Source/GameNetwork/NAT.cpp +++ b/Core/GameEngine/Source/GameNetwork/NAT.cpp @@ -802,13 +802,15 @@ void NAT::sendMangledSourcePort() { // next port allocation. // get the address of the mangler we need to talk to. - Char manglerName[256]; - FirewallHelperClass::getManglerName(1, manglerName); - DEBUG_LOG(("NAT::sendMangledSourcePort - about to call gethostbyname for mangler at %s", manglerName)); - struct hostent *hostInfo = gethostbyname(manglerName); + AsciiString manglerName = FirewallHelperClass::getManglerName(1); + DEBUG_LOG(("NAT::sendMangledSourcePort - about to call gethostbyname for mangler at %s", manglerName.str())); + struct hostent* hostInfo = nullptr; + if (!manglerName.isEmpty()) { + hostInfo = gethostbyname(manglerName.str()); + } if (hostInfo == nullptr) { - DEBUG_LOG(("NAT::sendMangledSourcePort - gethostbyname failed for mangler address %s", manglerName)); + DEBUG_LOG(("NAT::sendMangledSourcePort - gethostbyname failed for mangler address %s", manglerName.str())); // can't find the mangler, we're screwed so just send the source port. sendMangledPortNumberToTarget(sourcePort, targetSlot); m_sourcePorts[m_targetNodeNumber] = sourcePort; @@ -818,7 +820,7 @@ void NAT::sendMangledSourcePort() { memcpy(&m_manglerAddress, &(hostInfo->h_addr_list[0][0]), 4); m_manglerAddress = ntohl(m_manglerAddress); - DEBUG_LOG(("NAT::sendMangledSourcePort - mangler %s address is %d.%d.%d.%d", manglerName, + DEBUG_LOG(("NAT::sendMangledSourcePort - mangler %s address is %d.%d.%d.%d", manglerName.str(), PRINTF_IP_AS_4_INTS(m_manglerAddress))); DEBUG_LOG(("NAT::sendMangledSourcePort - NAT behavior = 0x%08x", fwType)); diff --git a/Core/GameEngine/Source/GameNetwork/udp.cpp b/Core/GameEngine/Source/GameNetwork/udp.cpp index e5f30ccb795..e3ab907928b 100644 --- a/Core/GameEngine/Source/GameNetwork/udp.cpp +++ b/Core/GameEngine/Source/GameNetwork/udp.cpp @@ -174,6 +174,8 @@ Int UDP::Bind(UnsignedInt IP,UnsignedShort Port) { status=GetStatus(); //CERR("Bind failure (" << status << ") IP " << IP << " PORT " << Port ) + closesocket(fd); + fd=0; return(status); } diff --git a/Core/GameEngineDevice/CMakeLists.txt b/Core/GameEngineDevice/CMakeLists.txt index 6added5403b..54721693913 100644 --- a/Core/GameEngineDevice/CMakeLists.txt +++ b/Core/GameEngineDevice/CMakeLists.txt @@ -54,7 +54,7 @@ set(GAMEENGINEDEVICE_SRC # Include/W3DDevice/GameClient/W3DInGameUI.h # Include/W3DDevice/GameClient/W3DMirror.h Include/W3DDevice/GameClient/W3DMouse.h -# Include/W3DDevice/GameClient/W3DParticleSys.h + Include/W3DDevice/GameClient/W3DParticleSys.h # Include/W3DDevice/GameClient/W3DPoly.h Include/W3DDevice/GameClient/W3DProfilerFrameCapture.h # Include/W3DDevice/GameClient/W3DProjectedShadow.h diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h similarity index 84% rename from GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h rename to Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h index 41481701474..cf17783902e 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h @@ -50,13 +50,21 @@ class W3DParticleSystemManager : public ParticleSystemManager virtual Int getOnScreenParticleCount() override { return m_onScreenParticleCount; } private: + Bool finishedBatch(const ParticleSystem& system, const RefCountPtr& texture); + void initializeBatch(const ParticleSystem& system, const RefCountPtr& texture); + void flushParticleBatch(RenderInfoClass& rinfo, UnsignedInt& pointCount); + enum { MAX_POINTS_PER_GROUP = 512 }; + RefCountPtr m_batchTexture; ///< the texture used as the drawing surface for batched particle draws PointGroupClass *m_pointGroup; ///< the point group that contains all of the particles StreakLineClass *m_streakLine; ///< the streak class that contains all of the streaks ShareBufferClass *m_posBuffer; ///< array of particle positions ShareBufferClass *m_RGBABuffer; ///< array of particle color and alpha ShareBufferClass *m_sizeBuffer; ///< array of particle sizes ShareBufferClass *m_angleBuffer; ///< array of particle orientations + + ParticleSystemInfo::ParticleShaderType m_batchShaderType; Bool m_readyToRender; ///< if true, it is OK to render + Bool m_batchBillboard; }; diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DScreenshot.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DScreenshot.h index e6edec10cd8..184a6573839 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DScreenshot.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/W3DScreenshot.h @@ -1,6 +1,6 @@ /* ** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 TheSuperHackers +** Copyright 2026 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 diff --git a/Core/GameEngineDevice/Include/W3DDevice/GameClient/WorldHeightMap.h b/Core/GameEngineDevice/Include/W3DDevice/GameClient/WorldHeightMap.h index 846c11bbd42..8532f6b2eab 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/GameClient/WorldHeightMap.h +++ b/Core/GameEngineDevice/Include/W3DDevice/GameClient/WorldHeightMap.h @@ -238,6 +238,7 @@ class WorldHeightMap : public RefCountClass, public: // height map info. static Int getMinHeightValue() {return K_MIN_HEIGHT;} static Int getMaxHeightValue() {return K_MAX_HEIGHT;} + static Bool isValidMapSize(Int width, Int height, Int borderSize); ///< Within MAX_MAP_DIMENSION and MAX_MAP_AREA, width and height include the border UnsignedByte *getDataPtr() {return m_data;} diff --git a/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp b/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp index c6b2f6e9198..2e4ffaab527 100644 --- a/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp +++ b/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp @@ -1727,7 +1727,7 @@ void MilesAudioManager::selectProvider( UnsignedInt providerNdx ) { providerNdx = getProviderIndex( "Miles Fast 2D Positional Audio" ); } - success = AIL_open_3D_provider( m_provider3D[providerNdx].id ) == 0; + success = providerNdx < m_providerCount && AIL_open_3D_provider( m_provider3D[providerNdx].id ) == 0; //if (providerNdx < m_providerCount) //{ @@ -1741,7 +1741,7 @@ void MilesAudioManager::selectProvider( UnsignedInt providerNdx ) m_selectedProvider = PROVIDER_ERROR; // try to select a failsafe providerNdx = getProviderIndex( "Miles Fast 2D Positional Audio" ); - success = AIL_open_3D_provider( m_provider3D[providerNdx].id ) == 0; + success = providerNdx < m_providerCount && AIL_open_3D_provider( m_provider3D[providerNdx].id ) == 0; } if ( success ) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index 6b9d2bd3095..ad45c4757d0 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -2495,12 +2495,13 @@ void W3DModelDraw::handleClientTurretPositioning() */ void W3DModelDraw::handleClientRecoil() { - const W3DModelDrawModuleData* d = getW3DModelDrawModuleData(); - if (!(m_curState->m_validStuff & ModelConditionInfo::BARRELS_VALID)) + if (!m_curState || !(m_curState->m_validStuff & ModelConditionInfo::BARRELS_VALID)) { return; } + const W3DModelDrawModuleData* d = getW3DModelDrawModuleData(); + // do recoil, if any for (int wslot = 0; wslot < WEAPONSLOT_COUNT; ++wslot) { diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp index b1b792b3ac9..e4823359592 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp @@ -46,6 +46,9 @@ W3DParticleSystemManager::W3DParticleSystemManager() { + m_batchBillboard = true; + m_batchShaderType = ParticleSystemInfo::INVALID_SHADER; + m_pointGroup = nullptr; m_streakLine = nullptr; m_posBuffer = nullptr; @@ -144,6 +147,9 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) TheSmudgeManager->resetDraw(); } + // Number of particles/points being rendered. + UnsignedInt pointCount = 0; + ParticleSystemManager::ParticleSystemList &particleSysList = TheParticleSystemManager->getAllParticleSystems(); for( ParticleSystemManager::ParticleSystemListIt it = particleSysList.begin(); it != particleSysList.end(); ++it) { @@ -156,40 +162,72 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) if (sys->isUsingDrawables()) continue; - //temporary hack that checks if texture name starts with "SMUD" - if so, we can assume it's a smudge type - if (/*sys->isUsingSmudge()*/ *((DWORD *)sys->getParticleTypeName().str()) == 0x44554D53) + // TheSuperHackers @performance Mauller 16/08/2026 Skip processing particle system if no particles are in view. + UnsignedInt particleCount = 0; + for (Particle* vp = sys->getFirstParticle(); vp; vp = vp->m_systemNext) { - if (drawSmudge) + const Coord3D* pos = vp->getPosition(); + const Real psize = vp->getSize(); + + //Test if particle is at the screen or terrain edges. + if (WWMath::Fabs(pos->x - bcX) > (beX + psize) || + WWMath::Fabs(pos->y - bcY) > (beY + psize) || + WWMath::Fabs(pos->z - bcZ) > (beZ + psize)) { - for (Particle *p = sys->getFirstParticle(); p; p = p->m_systemNext) - { - const Coord3D *pos = p->getPosition(); - Real psize = p->getSize(); + vp->setIsCulled(true); + continue; + } - //Cull particle to edges of screen and terrain. - if (WWMath::Fabs( pos->x - bcX ) > ( beX + psize ) ) - continue; + vp->setIsCulled(false); + particleCount++; + } - if (WWMath::Fabs( pos->y - bcY ) > ( beY + psize ) ) - continue; + // Particle system has no particles on screen + if (particleCount == 0) + continue; - if (WWMath::Fabs( pos->z - bcZ ) > ( beZ + psize ) ) - continue; + // Handle smudge type particles + if (sys->isUsingSmudge()) + { + if (!drawSmudge) + continue; - if (Smudge *smudge = TheSmudgeManager->findSmudge(p)) - { - // The particle is in view. Draw the smudge! - smudge->m_draw = true; - } + for (Particle *p = sys->getFirstParticle(); p; p = p->m_systemNext) + { + if (p->isCulled()) + continue; + + if (Smudge *smudge = TheSmudgeManager->findSmudge(p)) + { + // The particle is in view. Draw the smudge! + smudge->m_draw = true; } } continue; } - /// @todo lorenzen sez: declare these outside the sys loop, and put some in registers - // initialize them here still, of course + // TheSuperHackers @performance Ronin/Mauller 09/08/2026 Implement batched rendering for similar particles. + // Particles with the same properties will now be batched onto a single texture surface before being drawn. + // If a different particle type appears before the batch is filled, the previous batch will be drawn first. + RefCountPtr texture; + texture.Assign_No_Add_Ref(W3DDisplay::m_assetManager->Get_Texture(sys->getParticleTypeName().str())); + + const Bool canBatch = sys->isUsingParticles(); + const Bool batchDone = finishedBatch(*sys, texture); + if (!canBatch || batchDone) + { + flushParticleBatch(rinfo, pointCount); + } + + // setup a new particle batch texture if prior batch was flushed. + if (canBatch && m_batchTexture == nullptr) + { + initializeBatch(*sys, texture); + } + + UnsignedInt startCount = pointCount; + // build W3D particle buffer - Int count = 0; Vector3 *posArray = m_posBuffer->Get_Array(); Real *sizeArray = m_sizeBuffer->Get_Array(); Vector4 *RGBAArray = m_RGBABuffer->Get_Array(); @@ -203,53 +241,60 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) //set-up all the per-particle for (Particle *p = sys->getFirstParticle(); p; p = p->m_systemNext) { - pos = p->getPosition(); - psize = p->getSize(); - - //Cull particle to edges of screen and terrain. - if (WWMath::Fabs(pos->x - bcX) > (beX + psize)) + if (p->isCulled()) continue; - if (WWMath::Fabs(pos->y - bcY) > (beY + psize)) - continue; - - if (WWMath::Fabs(pos->z - bcZ) > (beZ + psize)) - continue; + pos = p->getPosition(); + psize = p->getSize(); m_fieldParticleCount += ( sys->getPriority() == AREA_EFFECT && sys->m_isGroundAligned != FALSE ); //@todo lorenzen sez: use pointer arithmetic for these arrays - personalities[count] = p->getPersonality(); + personalities[pointCount] = p->getPersonality(); - posArray[count].X = pos->x; - posArray[count].Y = pos->y; - posArray[count].Z = pos->z; + posArray[pointCount].X = pos->x; + posArray[pointCount].Y = pos->y; + posArray[pointCount].Z = pos->z; - sizeArray[count] = psize; + sizeArray[pointCount] = psize; color = p->getColor(); - RGBAArray[count].X = color->red; - RGBAArray[count].Y = color->green; - RGBAArray[count].Z = color->blue; - RGBAArray[count].W = p->getAlpha(); + RGBAArray[pointCount].X = color->red; + RGBAArray[pointCount].Y = color->green; + RGBAArray[pointCount].Z = color->blue; + RGBAArray[pointCount].W = p->getAlpha(); - angleArray[count] = (uint8)(p->getAngle() * 255.0f / (2.0f * PI)); + angleArray[pointCount] = (uint8)(p->getAngle() * 255.0f / (2.0f * PI)); - if (++count == MAX_POINTS_PER_GROUP) - break; + if (++pointCount == MAX_POINTS_PER_GROUP) + { + if (!canBatch) + { + break; + } + + // TheSuperHackers @info The Buffer is full mid-system so draw what we have and carry on with the SAME system. + // This prevents particles being dropped. Bank the stats first as the flush resets count to 0. + m_onScreenParticleCount += (pointCount - startCount); + flushParticleBatch(rinfo, pointCount); + initializeBatch(*sys, texture); + startCount = 0; + } } - if ( count == 0 ) + if (pointCount == startCount) + { continue; //this system has no particles to render + } - TextureClass *texture = W3DDisplay::m_assetManager->Get_Texture( sys->getParticleTypeName().str() ); + const UnsignedInt volumeParticleDepth = sys->getVolumeParticleDepth(); - if ( m_streakLine && sys->isUsingStreak() && (count >= 2) ) + // Handle drawing streak type particles. + if ( sys->isUsingStreak() && (pointCount >= 2) ) { m_streakLine->Reset_Line(); - m_streakLine->Set_Texture( texture ); - texture->Release_Ref();//release reference since it's held by streakline + m_streakLine->Set_Texture( texture.Peek() ); switch( sys->getShaderType() ) { case ParticleSystemInfo::ADDITIVE: @@ -268,14 +313,14 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) //UPDATE THE STREAK'S ARRAYS m_streakLine->Set_LocsWidthsColors( - count, + pointCount, m_posBuffer->Get_Array(), m_sizeBuffer->Get_Array(), m_RGBABuffer->Get_Array(), &personalities[0] ); - //WWASSERT( m_streakLine->Get_Num_Points() == count ); + //WWASSERT( m_streakLine->Get_Num_Points() == pointCount ); // This is the happy place for this! RGBAArray[0].X = 0;//eliminates the scissor edge on the trailing edge of the streak @@ -286,60 +331,57 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) //RENDER STREAK! m_streakLine->Render( rinfo ); - + m_onScreenParticleCount += (pointCount - startCount); + pointCount = startCount; } - else + // Handle lone streak type particles by drawing them as regular particles. + else if (sys->isUsingStreak() && (pointCount == 1)) { + m_onScreenParticleCount += (pointCount - startCount); + initializeBatch(*sys, texture); + flushParticleBatch(rinfo, pointCount); + startCount = 0; + } + // Handle volumetric type particle systems. + else if( sys->isUsingVolumeParticles() && volumeParticleDepth > DEFAULT_VOLUME_PARTICLE_DEPTH ) + { + m_pointGroup->Set_Texture( texture.Peek() ); + m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space - WWASSERT( m_pointGroup ); - - if ( m_pointGroup ) // this catches the particle and volumeparticle cases + switch( sys->getShaderType() ) { - // render all the systems' particles - m_pointGroup->Set_Texture( texture ); - texture->Release_Ref();//release reference since it's held by pointGroup - m_pointGroup->Set_Flag( PointGroupClass::TRANSFORM, true ); // transform to screen space - - switch( sys->getShaderType() ) - { - case ParticleSystemInfo::ADDITIVE: - m_pointGroup->Set_Shader( ShaderClass::_PresetAdditiveSpriteShader ); - break; - case ParticleSystemInfo::ALPHA: - m_pointGroup->Set_Shader( ShaderClass::_PresetAlphaSpriteShader ); - break; - case ParticleSystemInfo::ALPHA_TEST: - m_pointGroup->Set_Shader( ShaderClass::_PresetATestSpriteShader ); - break; - case ParticleSystemInfo::MULTIPLY: - m_pointGroup->Set_Shader( ShaderClass::_PresetMultiplicativeSpriteShader ); - break; - } - - /// @todo Use both QUADS and TRIS for particles - m_pointGroup->Set_Point_Mode( PointGroupClass::QUADS ); - m_pointGroup->Set_Arrays( m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, count ); - m_pointGroup->Set_Billboard(sys->shouldBillboard()); + case ParticleSystemInfo::ADDITIVE: + m_pointGroup->Set_Shader( ShaderClass::_PresetAdditiveSpriteShader ); + break; + case ParticleSystemInfo::ALPHA: + m_pointGroup->Set_Shader( ShaderClass::_PresetAlphaSpriteShader ); + break; + case ParticleSystemInfo::ALPHA_TEST: + m_pointGroup->Set_Shader( ShaderClass::_PresetATestSpriteShader ); + break; + case ParticleSystemInfo::MULTIPLY: + m_pointGroup->Set_Shader( ShaderClass::_PresetMultiplicativeSpriteShader ); + break; + } - /// @todo Support animated texture particles - /// @todo lorenzen sez: unimplemented code wastes cpu cycles - m_pointGroup->Set_Point_Frame( 0 ); + /// @todo Use both QUADS and TRIS for particles + m_pointGroup->Set_Point_Mode( PointGroupClass::QUADS ); + m_pointGroup->Set_Arrays( m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, pointCount ); + m_pointGroup->Set_Billboard(sys->shouldBillboard()); - //RENDER IT! - if( sys->getVolumeParticleDepth() > 1 ) - { - m_pointGroup->RenderVolumeParticle( rinfo, sys->getVolumeParticleDepth() ); - } - else - m_pointGroup->Render( rinfo ); + /// @todo Support animated texture particles + /// @todo lorenzen sez: unimplemented code wastes cpu cycles + m_pointGroup->Set_Point_Frame( 0 ); - } + m_pointGroup->RenderVolumeParticle( rinfo, volumeParticleDepth); + m_onScreenParticleCount += (pointCount - startCount); + pointCount = startCount; } /// @todo lorenzen sez: this should be debug only: //add particle count to total - m_onScreenParticleCount += count; + m_onScreenParticleCount += (pointCount - startCount); /* // draw the wind vector for this particle system on the screen @@ -361,6 +403,9 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) } + // TheSuperHackers @info Flush the last batch if one is pending. + flushParticleBatch(rinfo, pointCount); + /// @todo lorenzen sez: this should be debug only: TheParticleSystemManager->setOnScreenParticleCount(m_onScreenParticleCount); @@ -374,3 +419,54 @@ void W3DParticleSystemManager::doParticles(RenderInfoClass &rinfo) ((W3DSmudgeManager *)TheSmudgeManager)->render(rinfo); } } + +Bool W3DParticleSystemManager::finishedBatch(const ParticleSystem& system, const RefCountPtr& texture) +{ + return texture.Peek() != m_batchTexture.Peek() || + system.getShaderType() != m_batchShaderType || + system.shouldBillboard() != m_batchBillboard; +} + +void W3DParticleSystemManager::initializeBatch(const ParticleSystem& system, const RefCountPtr& texture) +{ + m_batchTexture = texture; + m_batchShaderType = system.getShaderType(); + m_batchBillboard = system.shouldBillboard(); +} + +void W3DParticleSystemManager::flushParticleBatch(RenderInfoClass& rinfo, UnsignedInt& pointCount) +{ + if (pointCount > 0) + { + m_pointGroup->Set_Texture(m_batchTexture.Peek()); + + switch (m_batchShaderType) + { + case ParticleSystemInfo::ADDITIVE: + m_pointGroup->Set_Shader(ShaderClass::_PresetAdditiveSpriteShader); + break; + case ParticleSystemInfo::ALPHA: + m_pointGroup->Set_Shader(ShaderClass::_PresetAlphaSpriteShader); + break; + case ParticleSystemInfo::ALPHA_TEST: + m_pointGroup->Set_Shader(ShaderClass::_PresetATestSpriteShader); + break; + case ParticleSystemInfo::MULTIPLY: + m_pointGroup->Set_Shader(ShaderClass::_PresetMultiplicativeSpriteShader); + break; + } + + m_pointGroup->Set_Flag(PointGroupClass::TRANSFORM, true); + m_pointGroup->Set_Point_Mode(PointGroupClass::QUADS); + m_pointGroup->Set_Arrays(m_posBuffer, m_RGBABuffer, nullptr, m_sizeBuffer, m_angleBuffer, nullptr, pointCount); + m_pointGroup->Set_Billboard(m_batchBillboard); + m_pointGroup->Set_Point_Frame(0); + m_pointGroup->Render(rinfo); + + pointCount = 0; + } + + m_batchTexture.Clear(); + m_batchBillboard = false; + m_batchShaderType = ParticleSystemInfo::INVALID_SHADER; +} diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp index 0d0d851eb63..7492aaa7c5c 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DScreenshot.cpp @@ -1,6 +1,6 @@ /* ** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 TheSuperHackers +** Copyright 2026 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 @@ -185,7 +185,7 @@ void W3D_TakeCompressedScreenshot(ScreenshotFormat format, Int jpegQuality) return; } - SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format))); + SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format)); DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), nullptr, 0, surfaceCopy->Peek_D3D_Surface(), nullptr); surface->Release_Ref(); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp index ad5d6386a98..d004e808ef3 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DTerrainVisual.cpp @@ -644,7 +644,7 @@ Bool W3DTerrainVisual::load( AsciiString filename ) #ifdef DO_UNIT_TIMINGS #pragma MESSAGE("********************* WARNING- Doing UNIT TIMINGS. ") #else - if (m_waterRenderObject) + if (m_waterRenderObject && W3DDisplay::m_3DScene) { W3DDisplay::m_3DScene->Add_Render_Object( m_waterRenderObject); m_waterRenderObject->enableWaterGrid(false); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp index 4d4946c2b3a..130e2e3d82e 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Water/W3DWater.cpp @@ -2726,10 +2726,17 @@ void WaterRenderObjClass::drawRiverWater(PolygonTrigger *pTrig) static Bool doWobble = true; if (m_disableRiver) return; - m_drawingRiver = true; //allocate 2 triangles per side with 3 indices per triangle - DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,(rectangleCount+1)*2*3); + const Int indexCount = (rectangleCount+1)*2*3; + // TheSuperHackers @bugfix arcticdolphin 12/10/2026 Skip rivers that do not fit the 16 bit dynamic buffers. + DEBUG_ASSERTCRASH(indexCount <= DynamicIBAccessClass::MAX_INDEX_COUNT, ("River has too many points to draw")); + if (indexCount > DynamicIBAccessClass::MAX_INDEX_COUNT) + return; + + m_drawingRiver = true; + + DynamicIBAccessClass ib_access(BUFFER_TYPE_DYNAMIC_DX8,indexCount); { DynamicIBAccessClass::WriteLockClass lockib(&ib_access); UnsignedShort *curIb = lockib.Get_Index_Array(); diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp index 1893a5376d4..0f0b19a3cb6 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/WorldHeightMap.cpp @@ -827,6 +827,24 @@ Bool WorldHeightMap::ParseObjectsDataChunk(DataChunkInput &file, DataChunkInfo * return (file.parse(userData)); } +Bool WorldHeightMap::isValidMapSize(Int width, Int height, Int borderSize) +{ + if (width <= 0 || width > MAX_MAP_DIMENSION) + return false; + if (height <= 0 || height > MAX_MAP_DIMENSION) + return false; + if (width * height > MAX_MAP_AREA) + return false; + if (borderSize < 0 || borderSize > (width - 1) / 2 || borderSize > (height - 1) / 2) + return false; + return true; +} + +static Bool isValidHeightDataSize(DataChunkInput &file, Int dataSize, Int width, Int height) +{ + return dataSize > 0 && dataSize == width * height && (UnsignedInt)dataSize <= file.getChunkDataSizeLeft(); +} + /** * WorldHeightMap::ParseHeightMapData - read a height map chunk. * Format is the newer CHUNKY format. @@ -857,6 +875,10 @@ Bool WorldHeightMap::ParseHeightMapData(DataChunkInput &file, DataChunkInfo *inf m_borderSize = 0; } + if (!isValidMapSize(m_width, m_height, m_borderSize)) { + throw ERROR_CORRUPT_FILE_FORMAT; + } + if (info->version >= K_HEIGHT_MAP_VERSION_4) { Int numBorders = file.readInt(); m_boundaries.resize(numBorders); @@ -871,11 +893,12 @@ Bool WorldHeightMap::ParseHeightMapData(DataChunkInput &file, DataChunkInfo *inf } m_dataSize = file.readInt(); - m_data = MSGNEW("WorldHeightMap_ParseHeightMapData") UnsignedByte[m_dataSize]; - if (m_dataSize <= 0 || (m_dataSize != (m_width*m_height))) { + if (!isValidHeightDataSize(file, m_dataSize, m_width, m_height)) { throw ERROR_CORRUPT_FILE_FORMAT ; } + m_data = MSGNEW("WorldHeightMap_ParseHeightMapData") UnsignedByte[m_dataSize]; + Int numBytesX = (m_width+7)/8; //how many bytes to fit all bitflags Int numBytesY = m_height; m_seismicUpdateWidth=numBytesX; @@ -931,6 +954,10 @@ Bool WorldHeightMap::ParseSizeOnly(DataChunkInput &file, DataChunkInfo *info, vo m_borderSize = 0; } + if (!isValidMapSize(m_width, m_height, m_borderSize)) { + throw ERROR_CORRUPT_FILE_FORMAT; + } + if (info->version >= K_HEIGHT_MAP_VERSION_4) { Int numBorders = file.readInt(); m_boundaries.resize(numBorders); @@ -945,10 +972,11 @@ Bool WorldHeightMap::ParseSizeOnly(DataChunkInput &file, DataChunkInfo *info, vo } m_dataSize = file.readInt(); - m_data = MSGNEW("WorldHeightMap_ParseSizeOnly") UnsignedByte[m_dataSize]; - if (m_dataSize <= 0 || (m_dataSize != (m_width*m_height))) { + if (!isValidHeightDataSize(file, m_dataSize, m_width, m_height)) { throw ERROR_CORRUPT_FILE_FORMAT ; } + + m_data = MSGNEW("WorldHeightMap_ParseSizeOnly") UnsignedByte[m_dataSize]; file.readArrayOfBytes((char *)m_data, m_dataSize); // Resize me. if (info->version == K_HEIGHT_MAP_VERSION_1) { @@ -1074,6 +1102,7 @@ Bool WorldHeightMap::ParseBlendTileData(DataChunkInput &file, DataChunkInfo *inf m_cellCliffState[j*m_flipStateWidth + i] = data[j*byteWidth + i]; } } + delete[] data; } else { file.readArrayOfBytes((char*)m_cellCliffState, m_height*m_flipStateWidth); } diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/stb_image_write_impl.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/stb_image_write_impl.cpp index 2264ba2c403..cf30d9ba0cd 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/stb_image_write_impl.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/stb_image_write_impl.cpp @@ -1,6 +1,6 @@ /* -** Command & Conquer Generals(tm) -** Copyright 2025 TheSuperHackers +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 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 diff --git a/Core/Libraries/Source/WWVegas/WW3D2/agg_def.h b/Core/Libraries/Source/WWVegas/WW3D2/agg_def.h index 97228dfb374..85c994c4d69 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/agg_def.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/agg_def.h @@ -103,7 +103,12 @@ class AggregateDefClass virtual WW3DErrorType Load_W3D (ChunkLoadClass &chunk_load); virtual WW3DErrorType Save_W3D (ChunkSaveClass &chunk_save); const char * Get_Name () const { return m_pName; } - void Set_Name (const char *pname) { SAFE_FREE (m_pName); m_pName = ::_strdup (pname); } + void Set_Name (const char* pname) + { + char* name = ::_strdup(pname); + SAFE_FREE(m_pName); + m_pName = name; + } RenderObjClass * Create (); AggregateDefClass * Clone () const { return W3DNEW AggregateDefClass (*this); } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h index 3ae18aabe10..f69e6997e27 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/dx8indexbuffer.h @@ -119,6 +119,8 @@ class DynamicIBAccessClass void Allocate_DX8_Dynamic_Buffer(); public: + enum { MAX_INDEX_COUNT = 0xffff }; ///< index_count is an unsigned short + DynamicIBAccessClass(unsigned short type, unsigned short index_count); ~DynamicIBAccessClass(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp b/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp index 4b95df9a8ce..53dc80ddeeb 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/htree.cpp @@ -209,9 +209,10 @@ int HTreeClass::Load_W3D(ChunkLoadClass & cload) */ memcpy(Name,header.Name,W3D_NAME_LEN); NumPivots = header.NumPivots; - if (NumPivots > 0) { - Pivot = MSGW3DNEWARRAY("HTreeClass::Pivot") PivotClass[NumPivots]; + if (NumPivots < 1) { + return LOAD_ERROR; } + Pivot = MSGW3DNEWARRAY("HTreeClass::Pivot") PivotClass[NumPivots]; /* ** Now, read in all of the other chunks for this hierarchy. diff --git a/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp b/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp index 4bbbd6f0534..6bb8ae3bcfe 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/pointgr.cpp @@ -1106,6 +1106,8 @@ void PointGroupClass::Update_Arrays( case TRIS_SIZE_NOORIENT: { + WWASSERT(point_size); + // Scale vertex offsets and add them to point locations to get vertex locations for (i = 0; i < active_points; i++) { vertex_loc[vert + 0] = point_loc[i] + @@ -1121,6 +1123,8 @@ void PointGroupClass::Update_Arrays( case TRIS_NOSIZE_ORIENT: { + WWASSERT(point_orientation); + // Scale vertex offsets and add them to point locations to get vertex locations for (i = 0; i < active_points; i++) { vertex_loc[vert + 0] = point_loc[i] + @@ -1136,6 +1140,8 @@ void PointGroupClass::Update_Arrays( case TRIS_SIZE_ORIENT: { + WWASSERT(point_size && point_orientation); + // Scale vertex offsets and add them to point locations to get vertex locations for (i = 0; i < active_points; i++) { vertex_loc[vert + 0] = point_loc[i] + @@ -1171,6 +1177,8 @@ void PointGroupClass::Update_Arrays( case QUADS_SIZE_NOORIENT: { + WWASSERT(point_size); + // Scale vertex offsets and add them to point locations to get vertex locations for (i = 0; i < active_points; i++) { vertex_loc[vert + 0] = point_loc[i] + @@ -1188,6 +1196,8 @@ void PointGroupClass::Update_Arrays( case QUADS_NOSIZE_ORIENT: { + WWASSERT(point_orientation); + // Scale vertex offsets and add them to point locations to get vertex locations for (i = 0; i < active_points; i++) { vertex_loc[vert + 0] = point_loc[i] + @@ -1205,6 +1215,8 @@ void PointGroupClass::Update_Arrays( case QUADS_SIZE_ORIENT: { + WWASSERT(point_size && point_orientation); + Matrix4x4 view; Vector4 result; if (!Billboard) { @@ -1312,6 +1324,8 @@ void PointGroupClass::Update_Arrays( case SCREEN_SIZE_NOORIENT: case SCREEN_SIZE_ORIENT: { + WWASSERT(point_size); + // Offsets need to be scaled to the current screen resolution // First find x and y scale factors (sizes in pixels need to be @@ -1644,11 +1658,12 @@ void PointGroupClass::_Shutdown() * 12/03/2002 Mark Lorenzen Created. * * * *========================================================================*/ +#define DEFAULT_VOLUME_PARTICLE_DEPTH ( 1 ) #define MAX_VOLUME_PARTICLE_DEPTH ( 16 ) void PointGroupClass::RenderVolumeParticle(RenderInfoClass &rinfo, unsigned int depth ) { - if ( depth <= 1 ) //oops,wrong number + if ( depth <= DEFAULT_VOLUME_PARTICLE_DEPTH) //oops,wrong number { Render( rinfo ); return; @@ -1789,7 +1804,8 @@ void PointGroupClass::RenderVolumeParticle(RenderInfoClass &rinfo, unsigned int // 3 times per particle when we can do it once float recipDepth = 0.1f / (float)depth; - float shiftInc = ( t * *current_size * recipDepth ); + const float pointSize = current_size ? *current_size : DefaultPointSize; + float shiftInc = t * pointSize * recipDepth; Vector3 volumeLayerShift; Vector3 cameraPosition = rinfo.Camera.Get_Position(); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp index 64001c0bc07..11567667e60 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.cpp @@ -61,7 +61,6 @@ Render2DSentenceClass::Render2DSentenceClass () : TextureStartX (0), CurSurface (nullptr), CurrTextureSize (0), - MonoSpaced (false), IsClippedEnabled (false), ClipRect (0, 0, 0, 0), BaseLocation (0, 0), @@ -147,8 +146,6 @@ Render2DSentenceClass::Reset () } Cursor.Set (0, 0); - MonoSpaced = false; - ParseHotKey = false; Release_Pending_Surfaces (); Reset_Sentence_Data (); diff --git a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h index 15426c1e950..c41867c198f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h +++ b/Core/Libraries/Source/WWVegas/WW3D2/render2dsentence.h @@ -189,6 +189,7 @@ class Render2DSentenceClass { // void Build_Sentence (const WCHAR *text, int *hkX, int *hkY); void Draw_Sentence (uint32 color = 0xFFFFFFFF); + bool Has_Sentence_Data () const { return SentenceData.Count () > 0; } // // Texture hint @@ -196,8 +197,6 @@ class Render2DSentenceClass { void Set_Texture_Size_Hint( int hint ) { TextureSizeHint = hint; } int Get_Texture_Size_Hint() const { return TextureSizeHint; } - void Set_Mono_Spaced( bool onoff ) { MonoSpaced = onoff; } - private: // @@ -253,7 +252,6 @@ class Render2DSentenceClass { int CurrTextureSize; int TextureSizeHint; SurfaceClass * CurSurface; - bool MonoSpaced; float WrapWidth; bool Centered; // Determines whether or not to center each line RectClass ClipRect; diff --git a/Core/Libraries/Source/WWVegas/WW3D2/texturefilter.cpp b/Core/Libraries/Source/WWVegas/WW3D2/texturefilter.cpp index 777f32a0bae..ca60c8e026f 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/texturefilter.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/texturefilter.cpp @@ -139,7 +139,6 @@ void TextureFilterClass::_Init_Filters(TextureFilterMode texture_filter, Anisotr // TheSuperHackers @feature Mauller 08/03/2026 Add full support for all texture filtering modes; // None, Point, Bilinear, Trilinear, Anisotropic. - BOOL FilterSupported = false; switch (texture_filter) { default: @@ -171,15 +170,17 @@ void TextureFilterClass::_Init_Filters(TextureFilterMode texture_filter, Anisotr case TEXTURE_FILTER_BILINEAR: - FilterSupported = (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) && - (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR); - - if (FilterSupported) { + if (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) { _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; } else { _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; + } + + if (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR) { + _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; + } + else { _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; } @@ -188,15 +189,17 @@ void TextureFilterClass::_Init_Filters(TextureFilterMode texture_filter, Anisotr case TEXTURE_FILTER_TRILINEAR: - FilterSupported = (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) && - (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR); - - if (FilterSupported) { + if (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) { _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; } else { _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; + } + + if (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR) { + _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; + } + else { _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; } @@ -212,18 +215,28 @@ void TextureFilterClass::_Init_Filters(TextureFilterMode texture_filter, Anisotr case TEXTURE_FILTER_ANISOTROPIC: - FilterSupported = (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFANISOTROPIC) && - (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFANISOTROPIC); - - if (FilterSupported) { - _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_ANISOTROPIC; - _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_ANISOTROPIC; - + if (dx8caps.TextureFilterCaps & (D3DPTFILTERCAPS_MINFANISOTROPIC | D3DPTFILTERCAPS_MAGFANISOTROPIC)) { // Set the Anisotropic filtering level for all stages _Set_Max_Anisotropy(anisotropy_level); } + + if (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFANISOTROPIC) { + _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_ANISOTROPIC; + } + else if (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MINFLINEAR) { + _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; + } else { _MinTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; + } + + if (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFANISOTROPIC) { + _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_ANISOTROPIC; + } + else if (dx8caps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFLINEAR) { + _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_LINEAR; + } + else { _MagTextureFilters[0][FILTER_TYPE_BEST]=D3DTEXF_POINT; } diff --git a/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.cpp b/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.cpp index 7a0bd0ad12a..edd132300f4 100644 --- a/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.cpp +++ b/Core/Libraries/Source/WWVegas/WWAudio/SoundSceneObj.cpp @@ -93,6 +93,7 @@ SoundSceneObjClass::SoundSceneObjClass () m_PhysWrapper (nullptr), m_pCallback (nullptr), m_AttachedObject (nullptr), + m_AttachedBone (-1), m_UserData (0), m_UserObj (nullptr), m_ID (SOUND_OBJ_DEFAULT_ID), @@ -110,18 +111,20 @@ SoundSceneObjClass::SoundSceneObjClass () // //////////////////////////////////////////////////////////////////////////////////////////////// SoundSceneObjClass::SoundSceneObjClass (const SoundSceneObjClass &src) - : m_Scene (nullptr), + : PersistClass(static_cast(src)), + m_Scene (src.m_Scene), m_PhysWrapper (nullptr), - m_pCallback (nullptr), + m_pCallback (src.m_pCallback), m_AttachedObject (nullptr), + m_AttachedBone (src.m_AttachedBone), m_UserData (0), m_UserObj (nullptr), m_ID (SOUND_OBJ_DEFAULT_ID), - m_RegisteredEvents (AudioCallbackClass::EVENT_NONE) + m_RegisteredEvents (src.m_RegisteredEvents) { m_ID = m_NextAvailableID ++; - (*this) = src; + REF_PTR_SET (m_AttachedObject, src.m_AttachedObject); Register_Sound_Object (this); } diff --git a/Core/Libraries/Source/WWVegas/WWSaveLoad/parameter.h b/Core/Libraries/Source/WWVegas/WWSaveLoad/parameter.h index 18ba03ecf3a..c7838c49bad 100644 --- a/Core/Libraries/Source/WWVegas/WWSaveLoad/parameter.h +++ b/Core/Libraries/Source/WWVegas/WWSaveLoad/parameter.h @@ -208,16 +208,11 @@ ParameterClass::Get_Name () const // Set_Name ////////////////////////////////////////////////////////////////////////////////// inline void -ParameterClass::Set_Name (const char *new_name) +ParameterClass::Set_Name (const char* new_name) { - if (m_Name != nullptr) { - ::free ((void *)m_Name); - m_Name = nullptr; - } - - if (new_name != nullptr) { - m_Name = ::strdup (new_name); - } + char* name = new_name ? ::strdup(new_name) : nullptr; + ::free((void*)m_Name); + m_Name = name; } diff --git a/Generals/Code/GameEngine/Include/Common/MapReaderWriterInfo.h b/Generals/Code/GameEngine/Include/Common/MapReaderWriterInfo.h index c370ac5b3c0..0a3b698bcdc 100644 --- a/Generals/Code/GameEngine/Include/Common/MapReaderWriterInfo.h +++ b/Generals/Code/GameEngine/Include/Common/MapReaderWriterInfo.h @@ -53,6 +53,11 @@ #define K_LIGHTING_VERSION_3 3 // Added 2 additional global lights for terrain. #define K_WORLDDICT_VERSION_1 1 #define K_MAPPREVIEW_VERSION_1 1 + +// TheSuperHackers @bugfix arcticdolphin 12/10/2026 Map size limits. +#define MAX_MAP_DIMENSION 8192 // The shroud texture spans the map and shoreline tiles pack each coordinate into 16 bits +#define MAX_MAP_AREA (4096*4096) // Terrain, pathfinding and partition arrays allocate per cell in a 32 bit process + /** Virtual helper class, so that we can write map data using FILE* or CFile. */ class OutputStream { public: diff --git a/Generals/Code/GameEngine/Include/Common/Recorder.h b/Generals/Code/GameEngine/Include/Common/Recorder.h index bd7b030e146..110e58bbc3f 100644 --- a/Generals/Code/GameEngine/Include/Common/Recorder.h +++ b/Generals/Code/GameEngine/Include/Common/Recorder.h @@ -42,6 +42,7 @@ class ReplayGameInfo : public GameInfo for (Int i = 0; i< MAX_SLOTS; ++i) setSlotPointer(i, &m_ReplaySlot[i]); } + virtual ~ReplayGameInfo() override {} }; enum RecorderModeType CPP_11(: Int) { diff --git a/Generals/Code/GameEngine/Include/GameClient/GameWindowManager.h b/Generals/Code/GameEngine/Include/GameClient/GameWindowManager.h index 4ce12aa8964..6b7989099e2 100644 --- a/Generals/Code/GameEngine/Include/GameClient/GameWindowManager.h +++ b/Generals/Code/GameEngine/Include/GameClient/GameWindowManager.h @@ -274,8 +274,7 @@ friend class GameWindow; virtual GameWindow *winGetCapture(); ///< current mouse capture settings virtual Int winSetModal( GameWindow *window ); ///< put at top of modal stack - virtual Int winUnsetModal( GameWindow *window ); /**< take window off modal stack, if window is - not at top of stack and error will occur */ + virtual Int winUnsetModal( GameWindow *window ); ///< take window off the modal stack from anywhere in the stack //--------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////// @@ -331,7 +330,6 @@ friend class GameWindow; protected: void processDestroyList(); ///< process windows waiting to be killed - void removeWindowFromModalStack( GameWindow *window ); Int drawWindow( GameWindow *window ); ///< draw this window diff --git a/Generals/Code/GameEngine/Include/GameClient/InGameUI.h b/Generals/Code/GameEngine/Include/GameClient/InGameUI.h index 59e753c1d05..204dd76e368 100644 --- a/Generals/Code/GameEngine/Include/GameClient/InGameUI.h +++ b/Generals/Code/GameEngine/Include/GameClient/InGameUI.h @@ -570,6 +570,10 @@ friend class Drawable; // for selection/deselection transactions void setDrawRMBScrollAnchor(Bool b) { m_drawRMBScrollAnchor = b; } void setMoveRMBScrollAnchor(Bool b) { m_moveRMBScrollAnchor = b; } + void showReplayControls(); + void hideReplayControls(); + void toggleReplayControls(); + private: virtual Int getIdleWorkerCount(); virtual Object *findIdleWorker( Object *obj); @@ -682,7 +686,9 @@ friend class Drawable; // for selection/deselection transactions void expireHint( HintType type, UnsignedInt hintIndex ); void createControlBar(); ///< create the control bar user interface + void destroyControlBar(); void createReplayControl(); ///< create the replay control window + void destroyReplayControl(); void setMouseCursor(Mouse::MouseCursor c); @@ -948,6 +954,9 @@ friend class Drawable; // for selection/deselection transactions Bool m_clientQuiet; ///< When the user clicks exit,restart, etc. this is set true ///< to skip some client sounds/fx during shutdown + GameWindow *m_controlBarWindow; + GameWindow *m_replayWindow; + // World Animation Data WorldAnimationList m_worldAnimationList; ///< the list of world animations diff --git a/Generals/Code/GameEngine/Include/GameLogic/Scripts.h b/Generals/Code/GameEngine/Include/GameLogic/Scripts.h index c2a85ecfd0f..4f061b065b5 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Scripts.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Scripts.h @@ -142,7 +142,7 @@ class ScriptGroup : public MemoryPoolObject, public Snapshot void setWarnings(Bool warnings) { m_hasWarnings = warnings;} void setNextGroup(ScriptGroup *pGr) {m_nextGroup = pGr;} - AsciiString getName() const { return m_groupName;} + const AsciiString& getName() const { return m_groupName;} Bool isActive() const { return m_isGroupActive;} Bool isSubroutine() const { return m_isGroupSubroutine;} Bool hasWarnings() const { return m_hasWarnings;} @@ -661,7 +661,7 @@ class Script : public MemoryPoolObject, public Snapshot Real getCurTime() {return m_curTime;} Int getDelayEvalSeconds() {return m_delayEvaluationSeconds;} - AsciiString getName() const { return m_scriptName;} + const AsciiString& getName() const { return m_scriptName;} AsciiString getComment() const {return m_comment;} AsciiString getActionComment() const {return m_actionComment;} AsciiString getConditionComment() const {return m_conditionComment;} @@ -691,7 +691,7 @@ class Script : public MemoryPoolObject, public Snapshot OrCondition *findPreviousOrCondition( OrCondition *curOr ); // Support routines for ScriptEngine - - AsciiString getConditionTeamName() {return m_conditionTeamName;} + const AsciiString& getConditionTeamName() const {return m_conditionTeamName;} void setConditionTeamName(AsciiString teamName) {m_conditionTeamName = teamName;} }; diff --git a/Generals/Code/GameEngine/Include/GameLogic/SidesList.h b/Generals/Code/GameEngine/Include/GameLogic/SidesList.h index c91c2aee3ce..5ba8d205448 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/SidesList.h +++ b/Generals/Code/GameEngine/Include/GameLogic/SidesList.h @@ -330,7 +330,7 @@ class BuildListInfo : public MemoryPoolObject, public Snapshot const Coord2D *getRallyOffset() const {return &m_rallyPointOffset;} Real getAngle() const {return m_angle;} Bool isInitiallyBuilt() {return m_isInitiallyBuilt;} - AsciiString getScript() {return m_script;} + const AsciiString& getScript() const {return m_script;} Int getHealth() {return m_health;} Bool getWhiner() {return m_whiner;} Bool getUnsellable() {return m_unsellable;} diff --git a/Generals/Code/GameEngine/Include/GameNetwork/GameSpyGameInfo.h b/Generals/Code/GameEngine/Include/GameNetwork/GameSpyGameInfo.h index 5d136972f72..9664361f0c4 100644 --- a/Generals/Code/GameEngine/Include/GameNetwork/GameSpyGameInfo.h +++ b/Generals/Code/GameEngine/Include/GameNetwork/GameSpyGameInfo.h @@ -67,6 +67,7 @@ class GameSpyGameInfo : public GameInfo public: GameSpyGameInfo(); + virtual ~GameSpyGameInfo() override {} inline void setServer(SBServer server) { m_server = server; } inline SBServer getServer() { return m_server; } diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp index 9fbf531b166..f836424db2c 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp @@ -96,22 +96,13 @@ void GameWindowManager::processDestroyList() next = doDestroy->m_next; - // Check to see if this window is "special" - if( m_mouseCaptor == doDestroy ) - winRelease( doDestroy ); - - if( m_keyboardFocus == doDestroy ) - winSetFocus( nullptr ); - - if( m_currMouseRgn == doDestroy ) - m_currMouseRgn = nullptr; - - if( m_grabWindow == doDestroy ) - m_grabWindow = nullptr; - // send the destroy message to the window we're about to kill winSendSystemMsg( doDestroy, GWM_DESTROY, 0, 0 ); + DEBUG_ASSERTCRASH( m_mouseCaptor != doDestroy && m_keyboardFocus != doDestroy + && m_currMouseRgn != doDestroy && m_grabWindow != doDestroy, + ("processDestroyList: manager still points at a window being destroyed") ); + DEBUG_ASSERTCRASH(doDestroy->winGetUserData() == nullptr, ("Win user data is expected to be deleted now")); // free the memory @@ -1516,7 +1507,7 @@ Int GameWindowManager::winDestroy( GameWindow *window ) if( m_keyboardFocus == window ) winSetFocus( nullptr ); - removeWindowFromModalStack( window ); + winUnsetModal( window ); if( m_currMouseRgn == window ) m_currMouseRgn = nullptr; @@ -1600,6 +1591,24 @@ Int GameWindowManager::winSetModal( GameWindow *window ) DEBUG_LOG(( "WinSetModal: Non Root window attempted to go modal." )); return WIN_ERR_INVALID_PARAMETER; // return error if not } + + // TheSuperHackers @bugfix arcticdolphin 08/09/2026 If already modal, move to the top instead of duplicating. + ModalWindow *previous = nullptr; + for( ModalWindow *existing = m_modalHead; existing != nullptr; previous = existing, existing = existing->next ) + { + if( existing->window != window ) + continue; + + if( previous != nullptr ) + { + previous->next = existing->next; + existing->next = m_modalHead; + m_modalHead = existing; + } + + return WIN_ERR_OK; + } + // Allocate new Modal Window Entry modal = newInstance(ModalWindow); if( modal == nullptr ) @@ -1618,54 +1627,36 @@ Int GameWindowManager::winSetModal( GameWindow *window ) } //------------------------------------------------------------------------------------------------- -/** pops window off of the modal stack. If this window is not the top - * of the modal stack an error will occur. */ +/** takes the window off the modal stack from anywhere in the stack */ //------------------------------------------------------------------------------------------------- +// TheSuperHackers @bugfix arcticdolphin 07/09/2026 Remove the window from anywhere in the modal stack, not just the top, so a destroyed window cannot leave a dangling entry behind. Int GameWindowManager::winUnsetModal( GameWindow *window ) { - ModalWindow *next; - if( window == nullptr ) return WIN_ERR_INVALID_WINDOW; - // verify entry is at top of list - if( (m_modalHead == nullptr) || (m_modalHead->window != window) ) - { - - // return error if not - DEBUG_LOG(( "WinUnsetModal: Invalid window attempting to unset modal (%d)", - window->winGetWindowId() )); - return WIN_ERR_GENERAL_FAILURE; - - } - - // remove from top of list - next = m_modalHead->next; - deleteInstance(m_modalHead); - m_modalHead = next; - - return WIN_ERR_OK; - -} + ModalWindow *previous = nullptr; + ModalWindow *modal = m_modalHead; -// TheSuperHackers @bugfix arcticdolphin 27/08/2026 Remove destroyed windows from the entire modal stack. -void GameWindowManager::removeWindowFromModalStack( GameWindow *window ) -{ - ModalWindow **link = &m_modalHead; - - while( *link ) + while( modal != nullptr ) { - ModalWindow *modal = *link; - - if( modal->window != window ) + if( modal->window == window ) { - link = &modal->next; - continue; + if( previous != nullptr ) + previous->next = modal->next; + else + m_modalHead = modal->next; + + deleteInstance(modal); + return WIN_ERR_OK; } - *link = modal->next; - deleteInstance(modal); + previous = modal; + modal = modal->next; } + + return WIN_ERR_GENERAL_FAILURE; + } //------------------------------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index e234b654278..9fc3840da7d 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -2747,7 +2747,9 @@ GameWindow *GameWindowManager::winCreateFromScript( AsciiString filenameString, { DEBUG_LOG(( "WinCreateFromScript: Error parsing layout block" )); - return FALSE; + inFile->close(); + inFile = nullptr; + return nullptr; } diff --git a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp index b79818490a2..ddf469d66c1 100644 --- a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -133,8 +133,6 @@ static UnicodeString formatIncomeValue(UnsignedInt cashPerMin) /// The InGameUI singleton instance. InGameUI *TheInGameUI = nullptr; -GameWindow *m_replayWindow = nullptr; - // ------------------------------------------------------------------------------------------------ struct KindOfSelectionData { @@ -250,38 +248,6 @@ static Bool similarUnitSelection( Drawable *test, void *userData ) return FALSE; } -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void showReplayControls() -{ - if (m_replayWindow) - { - Bool show = TheGameLogic->isInReplayGame(); - m_replayWindow->winHide(!show); - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void hideReplayControls() -{ - if (m_replayWindow) - { - m_replayWindow->winHide(TRUE); - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void toggleReplayControls() -{ - if (m_replayWindow) - { - Bool show = TheGameLogic->isInReplayGame() && m_replayWindow->winIsHidden(); - m_replayWindow->winHide(!show); - } -} - // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ SuperweaponInfo::SuperweaponInfo( @@ -1123,6 +1089,7 @@ InGameUI::InGameUI() } + m_controlBarWindow = nullptr; m_replayWindow = nullptr; m_messagesOn = TRUE; @@ -1275,6 +1242,9 @@ InGameUI::~InGameUI() // clear world animations clearWorldAnimations(); resetIdleWorker(); + + destroyReplayControl(); + destroyControlBar(); } //------------------------------------------------------------------------------------------------- @@ -4128,7 +4098,7 @@ void InGameUI::expireHint( HintType type, UnsignedInt hintIndex ) void InGameUI::createControlBar() { - TheWindowManager->winCreateFromScript( "ControlBar.wnd" ); + m_controlBarWindow = TheWindowManager->winCreateFromScript( "ControlBar.wnd" ); HideControlBar(); /* // hide all windows created from this layout @@ -4139,6 +4109,16 @@ void InGameUI::createControlBar() } +void InGameUI::destroyControlBar() +{ + if (m_controlBarWindow != nullptr) + { + TheWindowManager->winDestroy(m_controlBarWindow); + m_controlBarWindow = nullptr; + m_idleWorkerWin = nullptr; // This window belongs to the Control Bar and is implicitly deleted as well. + } +} + //------------------------------------------------------------------------------------------------- /** Create the replay control GUI */ //------------------------------------------------------------------------------------------------- @@ -4156,6 +4136,41 @@ void InGameUI::createReplayControl() } +void InGameUI::destroyReplayControl() +{ + if (m_replayWindow != nullptr) + { + TheWindowManager->winDestroy(m_replayWindow); + m_replayWindow = nullptr; + } +} + +void InGameUI::showReplayControls() +{ + if (m_replayWindow) + { + Bool show = TheGameLogic->isInReplayGame(); + m_replayWindow->winHide(!show); + } +} + +void InGameUI::hideReplayControls() +{ + if (m_replayWindow) + { + m_replayWindow->winHide(TRUE); + } +} + +void InGameUI::toggleReplayControls() +{ + if (m_replayWindow) + { + Bool show = TheGameLogic->isInReplayGame() && m_replayWindow->winIsHidden(); + m_replayWindow->winHide(!show); + } +} + // ------------------------------------------------------------------------------------------------ // InGameUI::playMovie // ------------------------------------------------------------------------------------------------ @@ -5894,12 +5909,7 @@ void InGameUI::resetIdleWorker() void InGameUI::recreateControlBar() { - GameWindow *win = TheWindowManager->winGetWindowFromId(nullptr, TheNameKeyGenerator->nameToKey("ControlBar.wnd")); - if(win) - deleteInstance(win); - - m_idleWorkerWin = nullptr; - + destroyControlBar(); createControlBar(); delete TheControlBar; diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 41fa741a6c7..4e4b8f94689 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -9023,6 +9023,7 @@ static const std::string F_SIZE = "Size"; static const std::string F_STARTSIZERATE ="StartSizeRate"; static const std::string F_SIZERATE = "SizeRate"; static const std::string F_SIZERATEDAMP = "SizeRateDamping"; +static const std::string F_VOLPARTICLEDEPTH = "VolParticleDepth"; static const std::string F_ALPHA1 = "Alpha1"; static const std::string F_ALPHA2 = "Alpha2"; @@ -9185,6 +9186,9 @@ void _writeSingleParticleSystem( File *out, ParticleSystemTemplate *templ ) sprintf(buff2, FORMAT_STRING, templ->m_sizeRateDamping.getMaximumValue()); thisEntry.append(SEP_HEAD).append(F_SIZERATEDAMP).append(EQ_WITH_SPACES).append(buff1).append(SEP_SPACE).append(buff2).append(SEP_EOL); + sprintf(buff1, "%d", templ->m_volumeParticleDepth); + thisEntry.append(SEP_HEAD).append(F_VOLPARTICLEDEPTH).append(EQ_WITH_SPACES).append(buff1).append(SEP_EOL); + sprintf(buff1, FORMAT_STRING, templ->m_alphaKey[0].var.getMinimumValue()); sprintf(buff2, FORMAT_STRING, templ->m_alphaKey[0].var.getMaximumValue()); sprintf(buff3, "%d", templ->m_alphaKey[0].frame); diff --git a/Generals/Code/GameEngineDevice/CMakeLists.txt b/Generals/Code/GameEngineDevice/CMakeLists.txt index dea3ff6c9ae..41464eb4443 100644 --- a/Generals/Code/GameEngineDevice/CMakeLists.txt +++ b/Generals/Code/GameEngineDevice/CMakeLists.txt @@ -47,7 +47,7 @@ set(GAMEENGINEDEVICE_SRC Include/W3DDevice/GameClient/W3DInGameUI.h Include/W3DDevice/GameClient/W3DMirror.h # Include/W3DDevice/GameClient/W3DMouse.h - Include/W3DDevice/GameClient/W3DParticleSys.h +# Include/W3DDevice/GameClient/W3DParticleSys.h Include/W3DDevice/GameClient/W3DPoly.h Include/W3DDevice/GameClient/W3DProjectedShadow.h Include/W3DDevice/GameClient/W3DRoadBuffer.h diff --git a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index aa3f0b79d2c..1701363414d 100644 --- a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -212,6 +212,7 @@ class W3DDisplay : public Display DisplayString *m_displayStrings[DisplayStringCount]; DisplayString *m_benchmarkDisplayString; + DisplayString *m_cinematicDisplayString; W3DDebugDisplay *m_nativeDebugDisplay; ///< W3D specific debug display interface diff --git a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplayString.h b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplayString.h index 80123dd0919..e57784c8f70 100644 --- a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplayString.h +++ b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplayString.h @@ -97,6 +97,7 @@ class W3DDisplayString : public DisplayString Render2DSentenceClass m_textRendererHotKey; ///< for drawing text Bool m_textChanged; ///< when contents of string change this is TRUE Bool m_fontChanged; ///< when font has chagned this is TRUE + Bool m_clipChanged; ///< when clip region has changed this is TRUE UnicodeString m_hotkey; ///< holds the current hotkey marker. Bool m_useHotKey; ICoord2D m_hotKeyPos; diff --git a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h deleted file mode 100644 index 32cb43582f6..00000000000 --- a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -** Command & Conquer Generals(tm) -** Copyright 2025 Electronic Arts Inc. -** -** 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 . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: W3DParticleSys.h ///////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "GameClient/ParticleSys.h" -#include "WW3D2/pointgr.h" -#include "WW3D2/streak.h" -#include "WW3D2/rinfo.h" -#include "WWLib/bittype.h" - -//============================================================================= -/** W3D implementation of the game display which is responsible for creating - * all interaction with the screen and updating the display - */ -class W3DParticleSystemManager : public ParticleSystemManager -{ - -public: - W3DParticleSystemManager(); - virtual ~W3DParticleSystemManager() override; - - virtual void doParticles(RenderInfoClass &rinfo) override; - virtual void queueParticleRender() override; - ///< returns the number of particles shown on screen per frame - virtual Int getOnScreenParticleCount() override { return m_onScreenParticleCount; } - -private: - enum { MAX_POINTS_PER_GROUP = 512 }; - - PointGroupClass *m_pointGroup; ///< the point group that contains all of the particles - StreakLineClass *m_streakLine; ///< the streak class that contains all of the streaks - ShareBufferClass *m_posBuffer; ///< array of particle positions - ShareBufferClass *m_RGBABuffer; ///< array of particle color and alpha - ShareBufferClass *m_sizeBuffer; ///< array of particle sizes - ShareBufferClass *m_angleBuffer; ///< array of particle orientations - Bool m_readyToRender; ///< if true, it is OK to render -}; diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 38595af3fcf..faf87e06c91 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -359,6 +359,8 @@ W3DDisplay::W3DDisplay() for (i = 0; i < DisplayStringCount; i++) m_displayStrings[i] = nullptr; + m_benchmarkDisplayString = nullptr; + m_cinematicDisplayString = nullptr; m_batchTexture = nullptr; m_batchMode = DRAW_IMAGE_ALPHA; @@ -394,6 +396,8 @@ W3DDisplay::~W3DDisplay() TheDisplayStringManager->freeDisplayString(m_benchmarkDisplayString); } + TheDisplayStringManager->freeDisplayString(m_cinematicDisplayString); + // delete 2D renderer if( m_2DRender ) { @@ -1801,7 +1805,7 @@ void W3DDisplay::draw() // //PredictiveLODOptimizerClass::Optimize_LODs( 5000 ); - Bool freezeTime = TheFramePacer->isTimeFrozen() || TheFramePacer->isGameHalted(); + Bool freezeTime = TheFramePacer->isTimeFrozen(); // hack to let client spin fast in network games but still do effects at the same pace. -MDC static UnsignedInt lastFrame = ~0; @@ -1810,7 +1814,7 @@ void W3DDisplay::draw() /// @todo: I'm assuming the first view is our main 3D view. W3DView *primaryW3DView=(W3DView *)getFirstView(); - if (!freezeTime && TheScriptEngine->isTimeFast()) + if ((!freezeTime || TheFramePacer->isGameHalted()) && TheScriptEngine->isTimeFast()) { primaryW3DView->updateCameraMovements(); // Update camera motion effects. syncTime += TheW3DFrameLengthInMsec; @@ -1952,28 +1956,29 @@ void W3DDisplay::draw() // display cinematicText over the black if( m_cinematicText != AsciiString::TheEmptyString && m_cinematicTextFrames != 0) { - DisplayString *displayString = TheDisplayStringManager->newDisplayString(); + if( m_cinematicDisplayString == nullptr ) + m_cinematicDisplayString = TheDisplayStringManager->newDisplayString(); // set word wrap if necessary Int wordWrapWidth = TheDisplay->getWidth() - 20; - displayString->setWordWrap( wordWrapWidth ); - displayString->setWordWrapCentered( TRUE ); + m_cinematicDisplayString->setWordWrap( wordWrapWidth ); + m_cinematicDisplayString->setWordWrapCentered( TRUE ); UnicodeString text; text.translate( m_cinematicText ); - displayString->setText( text ); + m_cinematicDisplayString->setText( text ); Color color = GameMakeColor( 255, 255, 255, 255 ); // white Color backColor = GameMakeColor( 0, 0, 0, 0 ); // black - displayString->setFont( m_cinematicFont ); + m_cinematicDisplayString->setFont( m_cinematicFont ); Int height = TheDisplay->getHeight() * .9; Int width; - if( displayString->getWidth() > TheDisplay->getWidth() ) + if( m_cinematicDisplayString->getWidth() > TheDisplay->getWidth() ) width = 20; else - width = ( TheDisplay->getWidth() - displayString->getWidth() ) / 2; - displayString->draw( width, height, color, backColor ); + width = ( TheDisplay->getWidth() - m_cinematicDisplayString->getWidth() ) / 2; + m_cinematicDisplayString->draw( width, height, color, backColor ); m_cinematicTextFrames--; } @@ -2025,7 +2030,7 @@ void W3DDisplay::draw() if (TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript() || TheGameLogic->isGamePaused()) { - freezeTime = false; // We're frozen for debug or for pause, and need to continue out of the loop. + break; // We're frozen for debug or for pause, and need to break out of the loop. } } while (freezeTime && !TheTacticalView->isCameraMovementFinished()); diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp index eafcf5fe6d6..48ede071d5e 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp @@ -86,6 +86,7 @@ W3DDisplayString::W3DDisplayString() m_size.x = 0; m_size.y = 0; m_fontChanged = FALSE; + m_clipChanged = FALSE; m_clipRegion.lo.x = 0; m_clipRegion.lo.y = 0; m_clipRegion.hi.x = 0; @@ -170,19 +171,18 @@ void W3DDisplayString::draw( Int x, Int y, Color color, Color dropColor, Int xDr { if(m_useHotKey) { - m_textRenderer.Set_Hot_Key_Parse(TRUE); m_textRenderer.Build_Sentence( getText().str(), &m_hotKeyPos.x, &m_hotKeyPos.y ); m_hotkey.translate(TheHotKeyManager->searchHotKey(getText())); if(!m_hotkey.isEmpty()) m_textRendererHotKey.Build_Sentence(m_hotkey.str(), nullptr, nullptr); else - { - m_useHotKey = FALSE; m_textRendererHotKey.Reset(); - } } else + { m_textRenderer.Build_Sentence( getText().str(), nullptr, nullptr ); + m_textRendererHotKey.Reset(); + } m_fontChanged = FALSE; m_textChanged = FALSE; needNewPolys = TRUE; @@ -193,7 +193,9 @@ void W3DDisplayString::draw( Int x, Int y, Color color, Color dropColor, Int xDr // if our position has changed, or our colors have changed, or our // text data has changed, we need to redo the texture quads // + // TheSuperHackers @bugfix arcticdolphin 08/10/2026 Redo the quads when the clip region changes. if( needNewPolys || + m_clipChanged || x != m_textPos.x || y != m_textPos.y || color != m_currTextColor || @@ -205,6 +207,7 @@ void W3DDisplayString::draw( Int x, Int y, Color color, Color dropColor, Int xDr m_textPos.y = y; m_currTextColor = color; m_currDropColor = dropColor; + m_clipChanged = FALSE; // reset the quads m_textRenderer.Reset_Polys(); @@ -217,7 +220,7 @@ void W3DDisplayString::draw( Int x, Int y, Color color, Color dropColor, Int xDr m_textRenderer.Set_Location( Vector2( m_textPos.x, m_textPos.y ) ); m_textRenderer.Draw_Sentence( m_currTextColor ); - if (m_useHotKey) + if (m_textRendererHotKey.Has_Sentence_Data()) { m_textRendererHotKey.Reset_Polys(); m_textRendererHotKey.Set_Location( Vector2( m_textPos.x + m_hotKeyPos.x , m_textPos.y +m_hotKeyPos.y) ); @@ -228,7 +231,7 @@ void W3DDisplayString::draw( Int x, Int y, Color color, Color dropColor, Int xDr TheDisplay->flush(); - if (m_useHotKey) + if (m_textRendererHotKey.Has_Sentence_Data()) { m_textRendererHotKey.Render(); } @@ -336,6 +339,7 @@ void W3DDisplayString::setClipRegion( IRegion2D *region ) // assign new region m_clipRegion = *region; + m_clipChanged = TRUE; // set new region in renderer m_textRenderer.Set_Clipping_Rect( RectClass( m_clipRegion.lo.x, @@ -388,6 +392,10 @@ void W3DDisplayString::setWordWrap( Int wordWrap ) void W3DDisplayString::setUseHotkey( Bool useHotkey, Color hotKeyColor ) { + // TheSuperHackers @performance arcticdolphin 08/10/2026 Skip the rebuild when the request is unchanged. + if( m_useHotKey == useHotkey && m_hotKeyColor == hotKeyColor ) + return; + m_useHotKey = useHotkey; m_hotKeyColor = hotKeyColor; m_textRenderer.Set_Hot_Key_Parse(useHotkey); diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp index 6d32d14b4ee..f0c2c018fa8 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp @@ -818,16 +818,11 @@ ParticleEmitterClass::Save (ChunkSaveClass &chunk_save) const void -ParticleEmitterClass::Set_Name (const char *pname) +ParticleEmitterClass::Set_Name (const char* pname) { - // Free the old name if necessary - if (NameString != nullptr) { - ::free (NameString); - NameString = nullptr; - } - - // Copy the provided name - NameString = ::_strdup (pname); + char* name = ::_strdup(pname); + ::free(NameString); + NameString = name; } diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_ldr.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_ldr.cpp index b39a05becdc..3f7773785f8 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_ldr.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/part_ldr.cpp @@ -264,8 +264,9 @@ ParticleEmitterDefClass::Set_Creation_Volume (Vector3Randomizer *randomizer) void ParticleEmitterDefClass::Set_User_String (const char *pstring) { - SAFE_FREE (m_pUserString); - m_pUserString = ::_strdup (pstring); + char* copy = ::_strdup(pstring); + SAFE_FREE(m_pUserString); + m_pUserString = copy; } @@ -276,8 +277,9 @@ ParticleEmitterDefClass::Set_User_String (const char *pstring) void ParticleEmitterDefClass::Set_Name (const char *pname) { - SAFE_FREE (m_pName); - m_pName = ::_strdup (pname); + char* copy = ::_strdup(pname); + SAFE_FREE(m_pName); + m_pName = copy; } diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp index c12469332d5..2e2e8974ad2 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp @@ -1368,7 +1368,7 @@ void WW3D::Make_Screen_Shot( const char * filename_base , const float gamma, con SurfaceClass::SurfaceDescription surfaceDesc; surface->Get_Description(surfaceDesc); - SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format))); + SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format)); DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), nullptr, 0, surfaceCopy->Peek_D3D_Surface(), nullptr); surface->Release_Ref(); @@ -1718,7 +1718,7 @@ void WW3D::Update_Movie_Capture() SurfaceClass::SurfaceDescription surfaceDesc; surface->Get_Description(surfaceDesc); - SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format))); + SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format)); DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), nullptr, 0, surfaceCopy->Peek_D3D_Surface(), nullptr); surface->Release_Ref(); diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MapReaderWriterInfo.h b/GeneralsMD/Code/GameEngine/Include/Common/MapReaderWriterInfo.h index c114ad29109..02eb3a762be 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/MapReaderWriterInfo.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/MapReaderWriterInfo.h @@ -55,6 +55,11 @@ #define K_LIGHTING_VERSION_3 3 // Added 2 additional global lights for terrain. #define K_WORLDDICT_VERSION_1 1 #define K_MAPPREVIEW_VERSION_1 1 + +// TheSuperHackers @bugfix arcticdolphin 12/10/2026 Map size limits. +#define MAX_MAP_DIMENSION 8192 // The shroud texture spans the map and shoreline tiles pack each coordinate into 16 bits +#define MAX_MAP_AREA (4096*4096) // Terrain, pathfinding and partition arrays allocate per cell in a 32 bit process + /** Virtual helper class, so that we can write map data using FILE* or CFile. */ class OutputStream { public: diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index fba73ad5ec3..1a82d84adfc 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -44,6 +44,7 @@ class ReplayGameInfo : public GameInfo for (Int i = 0; i< MAX_SLOTS; ++i) setSlotPointer(i, &m_ReplaySlot[i]); } + virtual ~ReplayGameInfo() override {} }; enum RecorderModeType CPP_11(: Int) { diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h b/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h index 8d0bfb6686d..0ae8cf8d198 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/GameWindowManager.h @@ -278,8 +278,7 @@ class GameWindowManager : public SubsystemInterface virtual GameWindow *winGetCapture(); ///< current mouse capture settings virtual Int winSetModal(GameWindow* window); ///< put at top of modal stack - virtual Int winUnsetModal(GameWindow* window); /**< take window off modal stack, if window is - not at top of stack and error will occur */ + virtual Int winUnsetModal( GameWindow *window ); ///< take window off the modal stack from anywhere in the stack //--------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////// @@ -335,7 +334,6 @@ class GameWindowManager : public SubsystemInterface protected: void processDestroyList(); ///< process windows waiting to be killed - void removeWindowFromModalStack( GameWindow *window ); Int drawWindow(GameWindow* window); ///< draw this window diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h index d5798a48ec7..7e39fd7e8f2 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h @@ -602,6 +602,10 @@ class InGameUI : public SubsystemInterface, public Snapshot void setDrawRMBScrollAnchor(Bool b) { m_drawRMBScrollAnchor = b; } void setMoveRMBScrollAnchor(Bool b) { m_moveRMBScrollAnchor = b; } + void showReplayControls(); + void hideReplayControls(); + void toggleReplayControls(); + private: virtual Int getIdleWorkerCount(); virtual Object* findIdleWorker(Object* obj); @@ -760,7 +764,9 @@ class InGameUI : public SubsystemInterface, public Snapshot void expireHint(HintType type, UnsignedInt hintIndex); void createControlBar(); ///< create the control bar user interface + void destroyControlBar(); void createReplayControl(); ///< create the replay control window + void destroyReplayControl(); void setMouseCursor(Mouse::MouseCursor c); @@ -1078,6 +1084,9 @@ class InGameUI : public SubsystemInterface, public Snapshot Int totalHeight = 0; bool isUpdating = false; + GameWindow *m_controlBarWindow; + GameWindow *m_replayWindow; + // World Animation Data WorldAnimationList m_worldAnimationList; ///< the list of world animations diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Scripts.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Scripts.h index 561807e7360..fb4bcded355 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Scripts.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Scripts.h @@ -135,7 +135,7 @@ class ScriptGroup : public MemoryPoolObject, public Snapshot void setWarnings(Bool warnings) { m_hasWarnings = warnings; } void setNextGroup(ScriptGroup* pGr) { m_nextGroup = pGr; } - AsciiString getName() const { return m_groupName;} + const AsciiString& getName() const { return m_groupName;} Bool isActive() const { return m_isGroupActive;} Bool isSubroutine() const { return m_isGroupSubroutine;} Bool hasWarnings() const { return m_hasWarnings;} @@ -669,7 +669,7 @@ class Script : public MemoryPoolObject, public Snapshot Real getCurTime() {return m_curTime;} Int getDelayEvalSeconds() {return m_delayEvaluationSeconds;} - AsciiString getName() const { return m_scriptName;} + const AsciiString& getName() const { return m_scriptName;} AsciiString getComment() const {return m_comment;} AsciiString getActionComment() const {return m_actionComment;} AsciiString getConditionComment() const {return m_conditionComment;} @@ -699,7 +699,7 @@ class Script : public MemoryPoolObject, public Snapshot OrCondition* findPreviousOrCondition(OrCondition* curOr); // Support routines for ScriptEngine - - AsciiString getConditionTeamName() {return m_conditionTeamName;} + const AsciiString& getConditionTeamName() const {return m_conditionTeamName;} void setConditionTeamName(AsciiString teamName) {m_conditionTeamName = teamName;} }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/SidesList.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/SidesList.h index 55cbcd800e5..3713fc1cc35 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/SidesList.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/SidesList.h @@ -330,7 +330,7 @@ class BuildListInfo : public MemoryPoolObject, public Snapshot const Coord2D *getRallyOffset() const {return &m_rallyPointOffset;} Real getAngle() const {return m_angle;} Bool isInitiallyBuilt() {return m_isInitiallyBuilt;} - AsciiString getScript() {return m_script;} + const AsciiString& getScript() const {return m_script;} Int getHealth() {return m_health;} Bool getWhiner() {return m_whiner;} Bool getUnsellable() {return m_unsellable;} diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GameSpyGameInfo.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GameSpyGameInfo.h index a1acedac598..4b1e8ee7100 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GameSpyGameInfo.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GameSpyGameInfo.h @@ -67,6 +67,7 @@ class GameSpyGameInfo : public GameInfo public: GameSpyGameInfo(); + virtual ~GameSpyGameInfo() override {} inline void setServer(SBServer server) { m_server = server; } inline SBServer getServer() { return m_server; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp index a80e368fc86..33ad638e060 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManager.cpp @@ -96,24 +96,13 @@ void GameWindowManager::processDestroyList() next = doDestroy->m_next; - // Check to see if this window is "special" - if (m_mouseCaptor == doDestroy) - winRelease(doDestroy); - - if (m_keyboardFocus == doDestroy) - winSetFocus(NULL); - - removeWindowFromModalStack(doDestroy); - - if( m_currMouseRgn == doDestroy ) - m_currMouseRgn = nullptr; - - if (m_grabWindow == doDestroy) - m_grabWindow = NULL; - // send the destroy message to the window we're about to kill winSendSystemMsg(doDestroy, GWM_DESTROY, 0, 0); + DEBUG_ASSERTCRASH( m_mouseCaptor != doDestroy && m_keyboardFocus != doDestroy + && m_currMouseRgn != doDestroy && m_grabWindow != doDestroy, + ("processDestroyList: manager still points at a window being destroyed") ); + DEBUG_ASSERTCRASH(doDestroy->winGetUserData() == NULL, ("Win user data is expected to be deleted now")); // free the memory @@ -1422,7 +1411,7 @@ Int GameWindowManager::winDestroy(GameWindow* window) if (m_keyboardFocus == window) winSetFocus(NULL); - removeWindowFromModalStack( window ); + winUnsetModal( window ); if (m_currMouseRgn == window) m_currMouseRgn = NULL; @@ -1506,6 +1495,24 @@ Int GameWindowManager::winSetModal(GameWindow* window) DEBUG_LOG(("WinSetModal: Non Root window attempted to go modal.")); return WIN_ERR_INVALID_PARAMETER; // return error if not } + + // TheSuperHackers @bugfix arcticdolphin 08/09/2026 If already modal, move to the top instead of duplicating. + ModalWindow *previous = nullptr; + for( ModalWindow *existing = m_modalHead; existing != nullptr; previous = existing, existing = existing->next ) + { + if( existing->window != window ) + continue; + + if( previous != nullptr ) + { + previous->next = existing->next; + existing->next = m_modalHead; + m_modalHead = existing; + } + + return WIN_ERR_OK; + } + // Allocate new Modal Window Entry modal = newInstance(ModalWindow); if (modal == NULL) @@ -1524,54 +1531,36 @@ Int GameWindowManager::winSetModal(GameWindow* window) } //------------------------------------------------------------------------------------------------- -/** pops window off of the modal stack. If this window is not the top - * of the modal stack an error will occur. */ - //------------------------------------------------------------------------------------------------- -Int GameWindowManager::winUnsetModal(GameWindow* window) +/** takes the window off the modal stack from anywhere in the stack */ +//------------------------------------------------------------------------------------------------- +// TheSuperHackers @bugfix arcticdolphin 07/09/2026 Remove the window from anywhere in the modal stack, not just the top, so a destroyed window cannot leave a dangling entry behind. +Int GameWindowManager::winUnsetModal( GameWindow *window ) { - ModalWindow* next; - - if (window == NULL) + if( window == nullptr ) return WIN_ERR_INVALID_WINDOW; - // verify entry is at top of list - if ((m_modalHead == NULL) || (m_modalHead->window != window)) - { - - // return error if not - DEBUG_LOG(("WinUnsetModal: Invalid window attempting to unset modal (%d)", - window->winGetWindowId())); - return WIN_ERR_GENERAL_FAILURE; - - } - - // remove from top of list - next = m_modalHead->next; - deleteInstance(m_modalHead); - m_modalHead = next; + ModalWindow *previous = nullptr; + ModalWindow *modal = m_modalHead; - return WIN_ERR_OK; - -} - -// TheSuperHackers @bugfix arcticdolphin 27/08/2026 Remove destroyed windows from the entire modal stack. -void GameWindowManager::removeWindowFromModalStack( GameWindow *window ) -{ - ModalWindow **link = &m_modalHead; - - while( *link ) + while( modal != nullptr ) { - ModalWindow *modal = *link; - - if( modal->window != window ) + if( modal->window == window ) { - link = &modal->next; - continue; + if( previous != nullptr ) + previous->next = modal->next; + else + m_modalHead = modal->next; + + deleteInstance(modal); + return WIN_ERR_OK; } - *link = modal->next; - deleteInstance(modal); + previous = modal; + modal = modal->next; } + + return WIN_ERR_GENERAL_FAILURE; + } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp index d588e90cc31..ce7723ac6d8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GameWindowManagerScript.cpp @@ -2772,7 +2772,9 @@ GameWindow *GameWindowManager::winCreateFromScript( AsciiString filenameString, { DEBUG_LOG(( "WinCreateFromScript: Error parsing layout block" )); - return FALSE; + inFile->close(); + inFile = nullptr; + return nullptr; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index b833f4c59d1..f9d4c1f8a51 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -144,8 +144,6 @@ static UnicodeString formatIncomeValue(UnsignedInt cashPerMin) /// The InGameUI singleton instance. InGameUI* TheInGameUI = nullptr; -GameWindow* m_replayWindow = nullptr; - // ------------------------------------------------------------------------------------------------ struct KindOfSelectionData { @@ -261,38 +259,6 @@ static Bool similarUnitSelection(Drawable* test, void* userData) return FALSE; } -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void showReplayControls() -{ - if (m_replayWindow) - { - Bool show = TheGameLogic->isInReplayGame(); - m_replayWindow->winHide(!show); - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void hideReplayControls() -{ - if (m_replayWindow) - { - m_replayWindow->winHide(TRUE); - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void toggleReplayControls() -{ - if (m_replayWindow) - { - Bool show = TheGameLogic->isInReplayGame() && m_replayWindow->winIsHidden(); - m_replayWindow->winHide(!show); - } -} - // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ SuperweaponInfo::SuperweaponInfo( @@ -1161,6 +1127,7 @@ InGameUI::InGameUI() #endif } + m_controlBarWindow = nullptr; m_replayWindow = nullptr; m_messagesOn = TRUE; @@ -1337,6 +1304,9 @@ InGameUI::~InGameUI() // clean up obs overlay cleanupObserverOverlay(); + + destroyReplayControl(); + destroyControlBar(); } //------------------------------------------------------------------------------------------------- @@ -4326,7 +4296,7 @@ void InGameUI::expireHint(HintType type, UnsignedInt hintIndex) void InGameUI::createControlBar() { - TheWindowManager->winCreateFromScript("ControlBar.wnd"); + m_controlBarWindow = TheWindowManager->winCreateFromScript("ControlBar.wnd"); HideControlBar(); /* // hide all windows created from this layout @@ -4337,6 +4307,16 @@ void InGameUI::createControlBar() } +void InGameUI::destroyControlBar() +{ + if (m_controlBarWindow != nullptr) + { + TheWindowManager->winDestroy(m_controlBarWindow); + m_controlBarWindow = nullptr; + m_idleWorkerWin = nullptr; // This window belongs to the Control Bar and is implicitly deleted as well. + } +} + //------------------------------------------------------------------------------------------------- /** Create the replay control GUI */ //------------------------------------------------------------------------------------------------- @@ -4354,6 +4334,41 @@ void InGameUI::createReplayControl() } +void InGameUI::destroyReplayControl() +{ + if (m_replayWindow != nullptr) + { + TheWindowManager->winDestroy(m_replayWindow); + m_replayWindow = nullptr; + } +} + +void InGameUI::showReplayControls() +{ + if (m_replayWindow) + { + Bool show = TheGameLogic->isInReplayGame(); + m_replayWindow->winHide(!show); + } +} + +void InGameUI::hideReplayControls() +{ + if (m_replayWindow) + { + m_replayWindow->winHide(TRUE); + } +} + +void InGameUI::toggleReplayControls() +{ + if (m_replayWindow) + { + Bool show = TheGameLogic->isInReplayGame() && m_replayWindow->winIsHidden(); + m_replayWindow->winHide(!show); + } +} + // ------------------------------------------------------------------------------------------------ // InGameUI::playMovie // ------------------------------------------------------------------------------------------------ @@ -6202,11 +6217,7 @@ void InGameUI::resetIdleWorker() void InGameUI::recreateControlBar() { - GameWindow* win = TheWindowManager->winGetWindowFromId(nullptr, TheNameKeyGenerator->nameToKey("ControlBar.wnd")); - deleteInstance(win); - - m_idleWorkerWin = nullptr; - + destroyControlBar(); createControlBar(); delete TheControlBar; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp index 5b2fef0caa5..e6dacec43b0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp @@ -9809,6 +9809,7 @@ static const std::string F_SIZE = "Size"; static const std::string F_STARTSIZERATE = "StartSizeRate"; static const std::string F_SIZERATE = "SizeRate"; static const std::string F_SIZERATEDAMP = "SizeRateDamping"; +static const std::string F_VOLPARTICLEDEPTH = "VolParticleDepth"; static const std::string F_ALPHA1 = "Alpha1"; static const std::string F_ALPHA2 = "Alpha2"; @@ -9971,6 +9972,9 @@ void _writeSingleParticleSystem(File* out, ParticleSystemTemplate* templ) sprintf(buff2, FORMAT_STRING, templ->m_sizeRateDamping.getMaximumValue()); thisEntry.append(SEP_HEAD).append(F_SIZERATEDAMP).append(EQ_WITH_SPACES).append(buff1).append(SEP_SPACE).append(buff2).append(SEP_EOL); + sprintf(buff1, "%d", templ->m_volumeParticleDepth); + thisEntry.append(SEP_HEAD).append(F_VOLPARTICLEDEPTH).append(EQ_WITH_SPACES).append(buff1).append(SEP_EOL); + sprintf(buff1, FORMAT_STRING, templ->m_alphaKey[0].var.getMinimumValue()); sprintf(buff2, FORMAT_STRING, templ->m_alphaKey[0].var.getMaximumValue()); sprintf(buff3, "%d", templ->m_alphaKey[0].frame); diff --git a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt index 56408e821bd..8b824957cab 100644 --- a/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngineDevice/CMakeLists.txt @@ -54,7 +54,7 @@ set(GAMEENGINEDEVICE_SRC Include/W3DDevice/GameClient/W3DInGameUI.h Include/W3DDevice/GameClient/W3DMirror.h # Include/W3DDevice/GameClient/W3DMouse.h - Include/W3DDevice/GameClient/W3DParticleSys.h +# Include/W3DDevice/GameClient/W3DParticleSys.h Include/W3DDevice/GameClient/W3DPoly.h Include/W3DDevice/GameClient/W3DProjectedShadow.h # Include/W3DDevice/GameClient/W3DPropBuffer.h diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index 08ee22dcdc4..543a92587a8 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -212,6 +212,7 @@ class W3DDisplay : public Display DisplayString* m_displayStrings[DisplayStringCount]; DisplayString* m_benchmarkDisplayString; + DisplayString* m_cinematicDisplayString; W3DDebugDisplay* m_nativeDebugDisplay; ///< W3D specific debug display interface diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplayString.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplayString.h index 0d49e002dab..8e0c6a39ef5 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplayString.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplayString.h @@ -97,6 +97,7 @@ class W3DDisplayString : public DisplayString Render2DSentenceClass m_textRendererHotKey; ///< for drawing text Bool m_textChanged; ///< when contents of string change this is TRUE Bool m_fontChanged; ///< when font has changed this is TRUE + Bool m_clipChanged; ///< when clip region has changed this is TRUE UnicodeString m_hotkey; ///< holds the current hotkey marker. Bool m_useHotKey; ICoord2D m_hotKeyPos; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 5eda9635d85..b7910fef974 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -444,6 +444,8 @@ W3DDisplay::W3DDisplay() for (i = 0; i < DisplayStringCount; i++) m_displayStrings[i] = nullptr; + m_benchmarkDisplayString = nullptr; + m_cinematicDisplayString = nullptr; m_batchTexture = nullptr; m_batchMode = DRAW_IMAGE_ALPHA; @@ -479,6 +481,8 @@ W3DDisplay::~W3DDisplay() TheDisplayStringManager->freeDisplayString(m_benchmarkDisplayString); } + TheDisplayStringManager->freeDisplayString(m_cinematicDisplayString); + // delete 2D renderer if( m_2DRender ) { @@ -1936,12 +1940,12 @@ void W3DDisplay::draw() // //PredictiveLODOptimizerClass::Optimize_LODs( 5000 ); - Bool freezeTime = TheFramePacer->isTimeFrozen() || TheFramePacer->isGameHalted(); + Bool freezeTime = TheFramePacer->isTimeFrozen(); /// @todo: I'm assuming the first view is our main 3D view. W3DView *primaryW3DView=(W3DView *)getFirstView(); - if (!freezeTime && TheScriptEngine->isTimeFast()) + if ((!freezeTime || TheFramePacer->isGameHalted()) && TheScriptEngine->isTimeFast()) { primaryW3DView->updateCameraMovements(); // Update camera motion effects. return; @@ -2072,28 +2076,29 @@ void W3DDisplay::draw() // display cinematicText over the black if( m_cinematicText != AsciiString::TheEmptyString && m_cinematicTextFrames != 0) { - DisplayString *displayString = TheDisplayStringManager->newDisplayString(); + if( m_cinematicDisplayString == nullptr ) + m_cinematicDisplayString = TheDisplayStringManager->newDisplayString(); // set word wrap if necessary Int wordWrapWidth = TheDisplay->getWidth() - 20; - displayString->setWordWrap( wordWrapWidth ); - displayString->setWordWrapCentered( TRUE ); + m_cinematicDisplayString->setWordWrap( wordWrapWidth ); + m_cinematicDisplayString->setWordWrapCentered( TRUE ); UnicodeString text; text.translate( m_cinematicText ); - displayString->setText( text ); + m_cinematicDisplayString->setText( text ); Color color = GameMakeColor( 255, 255, 255, 255 ); // white Color backColor = GameMakeColor( 0, 0, 0, 0 ); // black - displayString->setFont( m_cinematicFont ); + m_cinematicDisplayString->setFont( m_cinematicFont ); Int height = TheDisplay->getHeight() * .9; Int width; - if( displayString->getWidth() > TheDisplay->getWidth() ) + if( m_cinematicDisplayString->getWidth() > TheDisplay->getWidth() ) width = 20; else - width = ( TheDisplay->getWidth() - displayString->getWidth() ) / 2; - displayString->draw( width, height, color, backColor ); + width = ( TheDisplay->getWidth() - m_cinematicDisplayString->getWidth() ) / 2; + m_cinematicDisplayString->draw( width, height, color, backColor ); m_cinematicTextFrames--; } @@ -2145,7 +2150,7 @@ void W3DDisplay::draw() if (TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript() || TheGameLogic->isGamePaused()) { - freezeTime = false; // We're frozen for debug or for pause, and need to continue out of the loop. + break; // We're frozen for debug or for pause, and need to break out of the loop. } } while (freezeTime && !TheTacticalView->isCameraMovementFinished()); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp index c8a4b78ea59..10f36a95cea 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplayString.cpp @@ -86,6 +86,7 @@ W3DDisplayString::W3DDisplayString() m_size.x = 0; m_size.y = 0; m_fontChanged = FALSE; + m_clipChanged = FALSE; m_clipRegion.lo.x = 0; m_clipRegion.lo.y = 0; m_clipRegion.hi.x = 0; @@ -170,19 +171,18 @@ void W3DDisplayString::draw( Int x, Int y, Color color, Color dropColor, Int xDr { if(m_useHotKey) { - m_textRenderer.Set_Hot_Key_Parse(TRUE); m_textRenderer.Build_Sentence( getText().str(), &m_hotKeyPos.x, &m_hotKeyPos.y ); m_hotkey.translate(TheHotKeyManager->searchHotKey(getText())); if(!m_hotkey.isEmpty()) m_textRendererHotKey.Build_Sentence(m_hotkey.str(), nullptr, nullptr); else - { - m_useHotKey = FALSE; m_textRendererHotKey.Reset(); - } } else + { m_textRenderer.Build_Sentence( getText().str(), nullptr, nullptr ); + m_textRendererHotKey.Reset(); + } m_fontChanged = FALSE; m_textChanged = FALSE; needNewPolys = TRUE; @@ -193,7 +193,9 @@ void W3DDisplayString::draw( Int x, Int y, Color color, Color dropColor, Int xDr // if our position has changed, or our colors have changed, or our // text data has changed, we need to redo the texture quads // + // TheSuperHackers @bugfix arcticdolphin 08/10/2026 Redo the quads when the clip region changes. if( needNewPolys || + m_clipChanged || x != m_textPos.x || y != m_textPos.y || color != m_currTextColor || @@ -205,6 +207,7 @@ void W3DDisplayString::draw( Int x, Int y, Color color, Color dropColor, Int xDr m_textPos.y = y; m_currTextColor = color; m_currDropColor = dropColor; + m_clipChanged = FALSE; // reset the quads m_textRenderer.Reset_Polys(); @@ -217,7 +220,7 @@ void W3DDisplayString::draw( Int x, Int y, Color color, Color dropColor, Int xDr m_textRenderer.Set_Location( Vector2( m_textPos.x, m_textPos.y ) ); m_textRenderer.Draw_Sentence( m_currTextColor ); - if (m_useHotKey) + if (m_textRendererHotKey.Has_Sentence_Data()) { m_textRendererHotKey.Reset_Polys(); m_textRendererHotKey.Set_Location( Vector2( m_textPos.x + m_hotKeyPos.x , m_textPos.y +m_hotKeyPos.y) ); @@ -228,7 +231,7 @@ void W3DDisplayString::draw( Int x, Int y, Color color, Color dropColor, Int xDr TheDisplay->flush(); - if (m_useHotKey) + if (m_textRendererHotKey.Has_Sentence_Data()) { m_textRendererHotKey.Render(); } @@ -336,6 +339,7 @@ void W3DDisplayString::setClipRegion( IRegion2D *region ) // assign new region m_clipRegion = *region; + m_clipChanged = TRUE; // set new region in renderer m_textRenderer.Set_Clipping_Rect( RectClass( m_clipRegion.lo.x, @@ -388,6 +392,10 @@ void W3DDisplayString::setWordWrap( Int wordWrap ) void W3DDisplayString::setUseHotkey( Bool useHotkey, Color hotKeyColor ) { + // TheSuperHackers @performance arcticdolphin 08/10/2026 Skip the rebuild when the request is unchanged. + if( m_useHotKey == useHotkey && m_hotKeyColor == hotKeyColor ) + return; + m_useHotKey = useHotkey; m_hotKeyColor = hotKeyColor; m_textRenderer.Set_Hot_Key_Parse(useHotkey); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp index ca8d9045bcc..4ffe27a972b 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_emt.cpp @@ -831,16 +831,11 @@ ParticleEmitterClass::Save (ChunkSaveClass &chunk_save) const void -ParticleEmitterClass::Set_Name (const char *pname) +ParticleEmitterClass::Set_Name (const char* pname) { - // Free the old name if necessary - if (NameString != nullptr) { - ::free (NameString); - NameString = nullptr; - } - - // Copy the provided name - NameString = ::_strdup (pname); + char* name = ::_strdup(pname); + ::free(NameString); + NameString = name; } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_ldr.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_ldr.cpp index 4b847fd6e2e..356ea079b05 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_ldr.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/part_ldr.cpp @@ -267,8 +267,9 @@ ParticleEmitterDefClass::Set_Creation_Volume (Vector3Randomizer *randomizer) void ParticleEmitterDefClass::Set_User_String (const char *pstring) { - SAFE_FREE (m_pUserString); - m_pUserString = ::_strdup (pstring); + char* copy = ::_strdup(pstring); + SAFE_FREE(m_pUserString); + m_pUserString = copy; } @@ -279,8 +280,9 @@ ParticleEmitterDefClass::Set_User_String (const char *pstring) void ParticleEmitterDefClass::Set_Name (const char *pname) { - SAFE_FREE (m_pName); - m_pName = ::_strdup (pname); + char* copy = ::_strdup(pname); + SAFE_FREE(m_pName); + m_pName = copy; } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp index e9ba5f5f652..25a30a04496 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp @@ -1373,7 +1373,7 @@ void WW3D::Make_Screen_Shot( const char * filename_base , const float gamma, con SurfaceClass::SurfaceDescription surfaceDesc; surface->Get_Description(surfaceDesc); - SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format))); + SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format)); DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), nullptr, 0, surfaceCopy->Peek_D3D_Surface(), nullptr); surface->Release_Ref(); @@ -1723,7 +1723,7 @@ void WW3D::Update_Movie_Capture() SurfaceClass::SurfaceDescription surfaceDesc; surface->Get_Description(surfaceDesc); - SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (DX8Wrapper::_Create_DX8_Surface(surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format))); + SurfaceClass* surfaceCopy = NEW_REF(SurfaceClass, (surfaceDesc.Width, surfaceDesc.Height, surfaceDesc.Format)); DX8Wrapper::_Copy_DX8_Rects(surface->Peek_D3D_Surface(), nullptr, 0, surfaceCopy->Peek_D3D_Surface(), nullptr); surface->Release_Ref(); diff --git a/scripts/cpp/unify_move_files.py b/scripts/cpp/unify_move_files.py index 7047ccc6d6d..9fa88f72c60 100644 --- a/scripts/cpp/unify_move_files.py +++ b/scripts/cpp/unify_move_files.py @@ -606,6 +606,7 @@ def main(): #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/INI/INIWeapon.cpp", Game.CORE, "GameEngine/Source/Common/INI/INIWeapon.cpp") #unify_file(Game.ZEROHOUR, "GameEngine/Source/Common/INI/INIWebpageURL.cpp", Game.CORE, "GameEngine/Source/Common/INI/INIWebpageURL.cpp") + #unify_file(Game.ZEROHOUR, "GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h", Game.CORE, "GameEngineDevice/Include/W3DDevice/GameClient/W3DParticleSys.h") #unify_file(Game.ZEROHOUR, "GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp", Game.CORE, "GameEngineDevice/Source/W3DDevice/GameClient/W3DParticleSys.cpp") return