From fa9b9e2c1fb7679bcf4c1c79288a1c94cb4b2cdb Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 22 Nov 2025 12:04:13 +0100 Subject: [PATCH 1/4] refactor(radar): Simplify function Radar::addObject (#1893) --- Core/GameEngine/Include/Common/Radar.h | 7 +- .../GameEngine/Source/Common/System/Radar.cpp | 231 +++++++++--------- .../W3DDevice/Common/System/W3DRadar.cpp | 2 + 3 files changed, 127 insertions(+), 113 deletions(-) diff --git a/Core/GameEngine/Include/Common/Radar.h b/Core/GameEngine/Include/Common/Radar.h index 83f779247c..55703c86fc 100644 --- a/Core/GameEngine/Include/Common/Radar.h +++ b/Core/GameEngine/Include/Common/Radar.h @@ -196,8 +196,8 @@ class Radar : public Snapshot, Bool tryEvent( RadarEventType event, const Coord3D *pos ); ///< try to make a "stealth" event // adding and removing objects from the radar - virtual RadarObjectType addObject( Object *obj ); ///< add object to radar - virtual RadarObjectType removeObject( Object *obj ); ///< remove object from radar + virtual RadarObjectType addObject( Object *obj ); ///< add object to radar + virtual RadarObjectType removeObject( Object *obj ); ///< remove object from radar // radar options void hide( Int playerIndex, Bool hide ) { m_radarHidden[playerIndex] = hide; } ///< hide/show the radar @@ -252,6 +252,9 @@ class Radar : public Snapshot, // search the object list for an object that maps to the given logical radar coordinates Object *searchListForRadarLocationMatch( RadarObject *listHead, ICoord2D *radarMatch ); + void linkRadarObject( RadarObject *newObj, RadarObject **list ); + void assignObjectColorToRadarObject( RadarObject *radarObj, Object *obj ); + Bool m_radarHidden[MAX_PLAYER_COUNT]; ///< true when radar is not visible Bool m_radarForceOn[MAX_PLAYER_COUNT]; ///< true when radar is forced to be on RadarObject *m_objectList; ///< list of objects in the radar diff --git a/Core/GameEngine/Source/Common/System/Radar.cpp b/Core/GameEngine/Source/Common/System/Radar.cpp index 8883f247e0..0657232ccc 100644 --- a/Core/GameEngine/Source/Common/System/Radar.cpp +++ b/Core/GameEngine/Source/Common/System/Radar.cpp @@ -426,50 +426,7 @@ RadarObjectType Radar::addObject( Object *obj ) newObj->friend_setObject( obj ); // set color for this object on the radar - const Player *player = obj->getControllingPlayer(); - Player *clientPlayer = rts::getObservedOrLocalPlayer(); - Bool useIndicatorColor = true; - - if( obj->isKindOf( KINDOF_DISGUISER ) ) - { - //Because we have support for disguised units pretending to be units from another - //team, we need to intercept it here and make sure it's rendered appropriately - //based on which client is rendering it. - StealthUpdate *update = obj->getStealth(); - if( update ) - { - if( update->isDisguised() ) - { - Player *disguisedPlayer = ThePlayerList->getNthPlayer( update->getDisguisedPlayerIndex() ); - if( player->getRelationship( clientPlayer->getDefaultTeam() ) != ALLIES && clientPlayer->isPlayerActive() ) - { - //Neutrals and enemies will see this disguised unit as the team it's disguised as. - player = disguisedPlayer; - if( player ) - useIndicatorColor = false; - } - //Otherwise, the color will show up as the team it really belongs to (already set above). - } - } - } - - if( obj->getContain() ) - { - // To handle Stealth garrison, ask containers what color they are drawing with to the local player. - // Local is okay because radar display is not synced. - player = obj->getContain()->getApparentControllingPlayer( clientPlayer ); - if( player ) - useIndicatorColor = false; - } - - if( useIndicatorColor || (player == NULL) ) - { - newObj->setColor( obj->getIndicatorColor() ); - } - else - { - newObj->setColor( player->getPlayerColor() ); - } + assignObjectColorToRadarObject( newObj, obj ); // set a chunk of radar data in the object obj->friend_setRadarData( newObj ); @@ -491,73 +448,7 @@ RadarObjectType Radar::addObject( Object *obj ) } // link object to master list at the head of it's priority section - if( *list == NULL ) - *list = newObj; // trivial case, an empty list - else - { - RadarPriorityType prevPriority, currPriority; - RadarObject *currObject, *prevObject, *nextObject; - - prevObject = NULL; - prevPriority = RADAR_PRIORITY_INVALID; - for( currObject = *list; currObject; currObject = nextObject ) - { - - // get the next object - nextObject = currObject->friend_getNext(); - - // get the priority of this entry in the list (currPriority) - currPriority = currObject->friend_getObject()->getRadarPriority(); - - // - // if there is no previous object, or the previous priority is less than the - // our new priority, and the current object in the list has a priority - // higher than our equal to our own we need to be inserted here - // - if( (prevObject == NULL || prevPriority < newPriority ) && - (currPriority >= newPriority) ) - { - - // insert into the list just ahead of currObject - if( prevObject ) - { - - // the new entry next points to what the previous one used to point to - newObj->friend_setNext( prevObject->friend_getNext() ); - - // the previous one next now points to the new entry - prevObject->friend_setNext( newObj ); - - } - else - { - - // the new object next points to the current object - newObj->friend_setNext( currObject ); - - // new list head is now newObj - *list = newObj; - - } - - break; // exit for, stop the insert - - } - else if( nextObject == NULL ) - { - - // at the end of the list, put object here - currObject->friend_setNext( newObj ); - - } - - // our current object is now the previous object - prevObject = currObject; - prevPriority = currPriority; - - } - - } + linkRadarObject(newObj, list); return objectType; } @@ -1606,3 +1497,121 @@ Bool Radar::isPriorityVisible( RadarPriorityType priority ) } } + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void Radar::linkRadarObject( RadarObject *newObj, RadarObject **list ) +{ + if( *list == NULL ) + { + // trivial case, an empty list + *list = newObj; + return; + } + + RadarPriorityType newPriority = newObj->friend_getObject()->getRadarPriority(); + RadarPriorityType prevPriority; + RadarPriorityType currPriority; + RadarObject *currObject; + RadarObject *prevObject; + RadarObject *nextObject; + + DEBUG_ASSERTCRASH(newObj->friend_getNext() == NULL, ("newObj->friend_getNext is not NULL")); + + prevObject = NULL; + prevPriority = RADAR_PRIORITY_INVALID; + for( currObject = *list; currObject; currObject = nextObject ) + { + // get the next object + nextObject = currObject->friend_getNext(); + + // get the priority of this entry in the list (currPriority) + currPriority = currObject->friend_getObject()->getRadarPriority(); + + // + // if there is no previous object, or the previous priority is less than the + // our new priority, and the current object in the list has a priority + // higher than our equal to our own we need to be inserted here + // + if( (prevObject == NULL || prevPriority < newPriority ) && (currPriority >= newPriority) ) + { + // insert into the list just ahead of currObject + if( prevObject ) + { + // the new entry next points to what the previous one used to point to + newObj->friend_setNext( prevObject->friend_getNext() ); + + // the previous one next now points to the new entry + prevObject->friend_setNext( newObj ); + } + else + { + // the new object next points to the current object + newObj->friend_setNext( currObject ); + + // new list head is now newObj + *list = newObj; + } + break; + } + else if( nextObject == NULL ) + { + // at the end of the list, put object here + currObject->friend_setNext( newObj ); + } + + // our current object is now the previous object + prevObject = currObject; + prevPriority = currPriority; + } +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void Radar::assignObjectColorToRadarObject( RadarObject *radarObj, Object *obj ) +{ + const Player *player = obj->getControllingPlayer(); + Player *clientPlayer = rts::getObservedOrLocalPlayer(); + Bool useIndicatorColor = true; + + if( obj->isKindOf( KINDOF_DISGUISER ) ) + { + //Because we have support for disguised units pretending to be units from another + //team, we need to intercept it here and make sure it's rendered appropriately + //based on which client is rendering it. + StealthUpdate *update = obj->getStealth(); + if( update ) + { + if( update->isDisguised() ) + { + Player *disguisedPlayer = ThePlayerList->getNthPlayer( update->getDisguisedPlayerIndex() ); + if( player->getRelationship( clientPlayer->getDefaultTeam() ) != ALLIES && clientPlayer->isPlayerActive() ) + { + //Neutrals and enemies will see this disguised unit as the team it's disguised as. + player = disguisedPlayer; + if( player ) + useIndicatorColor = false; + } + //Otherwise, the color will show up as the team it really belongs to (already set above). + } + } + } + + if( obj->getContain() ) + { + // To handle Stealth garrison, ask containers what color they are drawing with to the local player. + // Local is okay because radar display is not synced. + player = obj->getContain()->getApparentControllingPlayer( clientPlayer ); + if( player ) + useIndicatorColor = false; + } + + if( useIndicatorColor || (player == NULL) ) + { + radarObj->setColor( obj->getIndicatorColor() ); + } + else + { + radarObj->setColor( player->getPlayerColor() ); + } +} diff --git a/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp b/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp index be576da841..2d93dca3c6 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp @@ -1018,6 +1018,8 @@ RadarObjectType W3DRadar::addObject( Object* obj ) } //------------------------------------------------------------------------------------------------- +// TheSuperHackers @bugfix xezon 05/07/2025 Now removes the cached hero immediately because +// otherwise the object pointer could be dangling and used for a bit too long. //------------------------------------------------------------------------------------------------- RadarObjectType W3DRadar::removeObject( Object* obj ) { From 16d58ed4c73ebfbba6c4cc915b11c7daae7bd414 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 22 Nov 2025 14:11:34 +0100 Subject: [PATCH 2/4] refactor(radar): Simplify function W3DRadar::renderObjectList (#1893) --- .../Include/W3DDevice/Common/W3DRadar.h | 1 + .../W3DDevice/Common/System/W3DRadar.cpp | 78 ++++++++++++------- .../Source/GameLogic/Object/Object.cpp | 4 +- .../Source/GameLogic/Object/Object.cpp | 4 +- 4 files changed, 54 insertions(+), 33 deletions(-) diff --git a/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h b/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h index 193d755f68..03ab169086 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h +++ b/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h @@ -84,6 +84,7 @@ class W3DRadar : public Radar void buildTerrainTexture( TerrainLogic *terrain ); ///< create the terrain texture of the radar void drawIcons( Int pixelX, Int pixelY, Int width, Int height ); ///< draw all of the radar icons void updateObjectTexture(TextureClass *texture); + static Bool canRenderObject( const RadarObject *rObj, const Player *localPlayer ); void renderObjectList( const RadarObject *listHead, TextureClass *texture, Bool calcHero = FALSE ); ///< render an object list to the texture void interpolateColorForHeight( RGBColor *color, Real height, diff --git a/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp b/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp index 2d93dca3c6..c6c413b345 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp @@ -625,6 +625,54 @@ void W3DRadar::updateObjectTexture(TextureClass *texture) renderObjectList( getLocalObjectList(), texture, TRUE ); } +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool W3DRadar::canRenderObject( const RadarObject *rObj, const Player *localPlayer ) +{ + if (rObj->isTemporarilyHidden()) + { + return false; + } + + const Int playerIndex = localPlayer->getPlayerIndex(); + const Object *obj = rObj->friend_getObject(); + + // + // check for shrouded status + // if object is fogged or shrouded, don't render it + // + if (obj->getShroudedStatus(playerIndex) > OBJECTSHROUD_PARTIAL_CLEAR) + { + return false; + } + + // + // objects with a local only unit priority will only appear on the radar if they + // are controlled by the local player, or if the local player is an observer (cause + // they are godlike and can see everything) + // + if (obj->getRadarPriority() == RADAR_PRIORITY_LOCAL_UNIT_ONLY && + obj->getControllingPlayer() != localPlayer && + localPlayer->isPlayerActive() ) + { + return false; + } + + // + // ML-- What the heck is this? local-only and neutral-observer-viewed units are stealthy?? Since when? + // Now it twinkles for any stealthed object, whether locally controlled or neutral-observer-viewed + // + if (TheControlBar->getCurrentlyViewedPlayerRelationship(obj->getTeam()) == ENEMIES && + obj->testStatus( OBJECT_STATUS_STEALTHED ) && + !obj->testStatus( OBJECT_STATUS_DETECTED ) && + !obj->testStatus( OBJECT_STATUS_DISGUISED ) ) + { + return false; + } + + return true; +} + //------------------------------------------------------------------------------------------------- /** Render an object list into the texture passed in */ //------------------------------------------------------------------------------------------------- @@ -642,7 +690,6 @@ void W3DRadar::renderObjectList( const RadarObject *listHead, TextureClass *text ICoord2D radarPoint; Player *player = rts::getObservedOrLocalPlayer(); - const Int playerIndex = player->getPlayerIndex(); if( calcHero ) { @@ -652,49 +699,26 @@ void W3DRadar::renderObjectList( const RadarObject *listHead, TextureClass *text for( const RadarObject *rObj = listHead; rObj; rObj = rObj->friend_getNext() ) { - - if (rObj->isTemporarilyHidden()) + if (!canRenderObject(rObj, player)) continue; - // get object - const Object *obj = rObj->friend_getObject(); - - // check for shrouded status - if (obj->getShroudedStatus(playerIndex) > OBJECTSHROUD_PARTIAL_CLEAR) - continue; //object is fogged or shrouded, don't render it. - - // - // objects with a local only unit priority will only appear on the radar if they - // are controlled by the local player, or if the local player is an observer (cause - // they are godlike and can see everything) - // - if( obj->getRadarPriority() == RADAR_PRIORITY_LOCAL_UNIT_ONLY && - obj->getControllingPlayer() != player && - player->isPlayerActive() ) - continue; - // get object position + const Object *obj = rObj->friend_getObject(); const Coord3D *pos = obj->getPosition(); // compute object position as a radar blip radarPoint.x = pos->x / (m_mapExtent.width() / RADAR_CELL_WIDTH); radarPoint.y = pos->y / (m_mapExtent.height() / RADAR_CELL_HEIGHT); - // get the color we're going to draw in + // get the color we're going to draw in Color c = rObj->getColor(); - - // adjust the alpha for stealth units so they "fade/blink" on the radar for the controller // if( obj->getRadarPriority() == RADAR_PRIORITY_LOCAL_UNIT_ONLY ) // ML-- What the heck is this? local-only and neutral-observer-viewed units are stealthy?? Since when? // Now it twinkles for any stealthed object, whether locally controlled or neutral-observer-viewed if( obj->testStatus( OBJECT_STATUS_STEALTHED ) ) { - if ( TheControlBar->getCurrentlyViewedPlayerRelationship(obj->getTeam()) == ENEMIES ) - if( !obj->testStatus( OBJECT_STATUS_DETECTED ) && !obj->testStatus( OBJECT_STATUS_DISGUISED ) ) - continue; - UnsignedByte r, g, b, a; GameGetColorComponents( c, &r, &g, &b, &a ); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 758c63d504..fbda3ce5b8 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -5442,10 +5442,8 @@ void Object::goInvulnerable( UnsignedInt time ) // ------------------------------------------------------------------------------------------------ RadarPriorityType Object::getRadarPriority( void ) const { - RadarPriorityType priority = RADAR_PRIORITY_INVALID; - // first, get the priority at the thing template level - priority = getTemplate()->getDefaultRadarPriority(); + RadarPriorityType priority = getTemplate()->getDefaultRadarPriority(); // // there are some objects that we want to show up on the radar when they have diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 3083c4738e..45a31a7f43 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -6282,10 +6282,8 @@ void Object::goInvulnerable( UnsignedInt time ) // ------------------------------------------------------------------------------------------------ RadarPriorityType Object::getRadarPriority( void ) const { - RadarPriorityType priority = RADAR_PRIORITY_INVALID; - // first, get the priority at the thing template level - priority = getTemplate()->getDefaultRadarPriority(); + RadarPriorityType priority = getTemplate()->getDefaultRadarPriority(); // // there are some objects that we want to show up on the radar when they have From 5db73aa72b07e773d83234c06e165d2bca4a8686 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 22 Nov 2025 14:16:45 +0100 Subject: [PATCH 3/4] refactor(radar): Simplify function Radar::deleteListResources (#1893) --- Core/GameEngine/Include/Common/Radar.h | 1 + .../GameEngine/Source/Common/System/Radar.cpp | 61 ++++++------------- 2 files changed, 20 insertions(+), 42 deletions(-) diff --git a/Core/GameEngine/Include/Common/Radar.h b/Core/GameEngine/Include/Common/Radar.h index 55703c86fc..563a80098f 100644 --- a/Core/GameEngine/Include/Common/Radar.h +++ b/Core/GameEngine/Include/Common/Radar.h @@ -239,6 +239,7 @@ class Radar : public Snapshot, void internalCreateEvent( const Coord3D *world, RadarEventType type, Real secondsToLive, const RGBAColorInt *color1, const RGBAColorInt *color2 ); + void deleteList( RadarObject **list ); void deleteListResources( void ); ///< delete list radar resources used Bool deleteFromList( Object *obj, RadarObject **list ); ///< try to remove object from specific list diff --git a/Core/GameEngine/Source/Common/System/Radar.cpp b/Core/GameEngine/Source/Common/System/Radar.cpp index 0657232ccc..4f38fdf244 100644 --- a/Core/GameEngine/Source/Common/System/Radar.cpp +++ b/Core/GameEngine/Source/Common/System/Radar.cpp @@ -62,56 +62,33 @@ Radar *TheRadar = NULL; ///< the radar global singleton #define RADAR_QUEUE_TERRAIN_REFRESH_DELAY (LOGICFRAMES_PER_SECOND * 3.0f) //------------------------------------------------------------------------------------------------- -/** Delete list resources used by the radar and return them to the memory pools */ //------------------------------------------------------------------------------------------------- -void Radar::deleteListResources( void ) +void Radar::deleteList( RadarObject **list ) { - RadarObject *nextObject; - - // delete entries from the local object list - while( m_localObjectList ) + while( *list ) { - - // get next object - nextObject = m_localObjectList->friend_getNext(); - - // remove radar data from object - m_localObjectList->friend_getObject()->friend_setRadarData( NULL ); - - // delete the head of the list - deleteInstance(m_localObjectList); - - // set head of the list to the next object - m_localObjectList = nextObject; - + RadarObject *nextObject = (*list)->friend_getNext(); + (*list)->friend_getObject()->friend_setRadarData( NULL ); + deleteInstance(*list); + *list = nextObject; } +} - // delete entries from the regular object list - while( m_objectList ) - { - - // get next object - nextObject = m_objectList->friend_getNext(); - - // remove radar data from object - m_objectList->friend_getObject()->friend_setRadarData( NULL ); - - // delete the head of the list - deleteInstance(m_objectList); - - // set head of the list to the next object - m_objectList = nextObject; - - } +//------------------------------------------------------------------------------------------------- +/** Delete list resources used by the radar and return them to the memory pools */ +//------------------------------------------------------------------------------------------------- +void Radar::deleteListResources( void ) +{ + deleteList(&m_objectList); + deleteList(&m_localObjectList); - Object *obj; - for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) +#ifdef DEBUG_CRASHING + for( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) { - - DEBUG_ASSERTCRASH( obj->friend_getRadarData() == NULL, ("oops") ); - + DEBUG_ASSERTCRASH( obj->friend_getRadarData() == NULL, + ("Radar::deleteListResources: Unexpectedly an object still has radar data assigned") ); } - +#endif } // PUBLIC METHODS ///////////////////////////////////////////////////////////////////////////////// From 46187c9a65c332bce2cd00fd3c5c0090fcaf9b71 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sat, 22 Nov 2025 14:30:11 +0100 Subject: [PATCH 4/4] bugfix(radar): Add hero radar objects into its own list to get rid of hero cache updates and its related issues (#1893) --- Core/GameEngine/Include/Common/Radar.h | 20 ++-- .../GameEngine/Source/Common/System/Radar.cpp | 97 +++++++++++++++---- .../Include/W3DDevice/Common/W3DRadar.h | 7 +- .../W3DDevice/Common/System/W3DRadar.cpp | 65 ++----------- .../GameEngine/Source/Common/RTS/Player.cpp | 13 ++- .../GameEngine/Source/Common/RTS/Player.cpp | 13 ++- 6 files changed, 117 insertions(+), 98 deletions(-) diff --git a/Core/GameEngine/Include/Common/Radar.h b/Core/GameEngine/Include/Common/Radar.h index 563a80098f..c3ccbc29e8 100644 --- a/Core/GameEngine/Include/Common/Radar.h +++ b/Core/GameEngine/Include/Common/Radar.h @@ -77,13 +77,6 @@ enum RadarEventType CPP_11(: Int) }; -enum RadarObjectType CPP_11(: Int) -{ - RadarObjectType_None = 0, - RadarObjectType_Regular, - RadarObjectType_Local, -}; - // PROTOTYPES ///////////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- @@ -196,8 +189,8 @@ class Radar : public Snapshot, Bool tryEvent( RadarEventType event, const Coord3D *pos ); ///< try to make a "stealth" event // adding and removing objects from the radar - virtual RadarObjectType addObject( Object *obj ); ///< add object to radar - virtual RadarObjectType removeObject( Object *obj ); ///< remove object from radar + virtual Bool addObject( Object *obj ); ///< add object to radar + virtual Bool removeObject( Object *obj ); ///< remove object from radar // radar options void hide( Int playerIndex, Bool hide ) { m_radarHidden[playerIndex] = hide; } ///< hide/show the radar @@ -245,8 +238,6 @@ class Radar : public Snapshot, inline Real getTerrainAverageZ() const { return m_terrainAverageZ; } inline Real getWaterAverageZ() const { return m_waterAverageZ; } - inline const RadarObject* getObjectList() const { return m_objectList; } - inline const RadarObject* getLocalObjectList() const { return m_localObjectList; } void clearAllEvents( void ); ///< remove all radar events in progress @@ -258,11 +249,18 @@ class Radar : public Snapshot, Bool m_radarHidden[MAX_PLAYER_COUNT]; ///< true when radar is not visible Bool m_radarForceOn[MAX_PLAYER_COUNT]; ///< true when radar is forced to be on + RadarObject *m_objectList; ///< list of objects in the radar RadarObject *m_localObjectList; /** list of objects for the local player, sorted * in exactly the same priority as the regular * object list for all other objects */ + // TheSuperHackers @bugfix xezon 22/11/2025 Now stores local heroes in a separate list, + // because they are treated with special icons but should otherwise work like all other + // radar objects. In retail version, the cached hero object data was able to dangle + // for a few frames and cause undefined behavior. + RadarObject *m_localHeroObjectList; ///< list of hero objects for the local player + Real m_terrainAverageZ; ///< average Z for terrain samples Real m_waterAverageZ; ///< average Z for water samples diff --git a/Core/GameEngine/Source/Common/System/Radar.cpp b/Core/GameEngine/Source/Common/System/Radar.cpp index 4f38fdf244..86ac2046b8 100644 --- a/Core/GameEngine/Source/Common/System/Radar.cpp +++ b/Core/GameEngine/Source/Common/System/Radar.cpp @@ -81,6 +81,7 @@ void Radar::deleteListResources( void ) { deleteList(&m_objectList); deleteList(&m_localObjectList); + deleteList(&m_localHeroObjectList); #ifdef DEBUG_CRASHING for( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) @@ -191,6 +192,7 @@ Radar::Radar( void ) m_radarWindow = NULL; m_objectList = NULL; m_localObjectList = NULL; + m_localHeroObjectList = NULL; std::fill(m_radarHidden, m_radarHidden + ARRAY_SIZE(m_radarHidden), false); std::fill(m_radarForceOn, m_radarForceOn + ARRAY_SIZE(m_radarForceOn), false); m_terrainAverageZ = 0.0f; @@ -379,13 +381,13 @@ void Radar::newMap( TerrainLogic *terrain ) /** Add an object to the radar list. The object will be sorted in the list to be grouped * using it's radar priority */ //------------------------------------------------------------------------------------------------- -RadarObjectType Radar::addObject( Object *obj ) +Bool Radar::addObject( Object *obj ) { // get the radar priority for this object RadarPriorityType newPriority = obj->getRadarPriority(); if( isPriorityVisible( newPriority ) == FALSE ) - return RadarObjectType_None; + return FALSE; // if this object is on the radar, remove it in favor of the new add RadarObject **list; @@ -408,26 +410,26 @@ RadarObjectType Radar::addObject( Object *obj ) // set a chunk of radar data in the object obj->friend_setRadarData( newObj ); - RadarObjectType objectType; // // we will put this on either the local object list for objects that belong to the // local player, or on the regular object list for all other objects // if( obj->isLocallyControlled() ) { - list = &m_localObjectList; - objectType = RadarObjectType_Local; + if ( obj->isHero() ) + list = &m_localHeroObjectList; + else + list = &m_localObjectList; } else { list = &m_objectList; - objectType = RadarObjectType_Regular; } // link object to master list at the head of it's priority section linkRadarObject(newObj, list); - return objectType; + return TRUE; } //------------------------------------------------------------------------------------------------- @@ -474,24 +476,24 @@ Bool Radar::deleteFromList( Object *obj, RadarObject **list ) //------------------------------------------------------------------------------------------------- /** Remove an object from the radar, the object may reside in any list */ //------------------------------------------------------------------------------------------------- -RadarObjectType Radar::removeObject( Object *obj ) +Bool Radar::removeObject( Object *obj ) { // sanity if( obj->friend_getRadarData() == NULL ) - return RadarObjectType_None; + return FALSE; + if( deleteFromList( obj, &m_localHeroObjectList ) == TRUE ) + return TRUE; if( deleteFromList( obj, &m_localObjectList ) == TRUE ) - return RadarObjectType_Local; + return TRUE; else if( deleteFromList( obj, &m_objectList ) == TRUE ) - return RadarObjectType_Regular; + return TRUE; else { - - // sanity DEBUG_ASSERTCRASH( 0, ("Radar: Tried to remove object '%s' which was not found", obj->getTemplate()->getName().str()) ); - return RadarObjectType_None; + return FALSE; } } @@ -716,10 +718,14 @@ Object *Radar::objectUnderRadarPixel( const ICoord2D *pixel ) // to the radar location // - // search the local object list - obj = searchListForRadarLocationMatch( m_localObjectList, &radar ); + // search the local hero object list + obj = searchListForRadarLocationMatch( m_localHeroObjectList, &radar ); + + // search the local object list if not found + if( obj == NULL ) + obj = searchListForRadarLocationMatch( m_localObjectList, &radar ); - // search all other objects if not found + // search all other objects if still not found if( obj == NULL ) obj = searchListForRadarLocationMatch( m_objectList, &radar ); @@ -1359,6 +1365,7 @@ static void xferRadarObjectList( Xfer *xfer, RadarObject **head ) * Version Info: * 1: Initial version * 2: TheSuperHackers @tweak Serialize m_radarHidden, m_radarForceOn for each player + * 3: TheSuperHackers @tweak Serialize m_localHeroObjectList */ // ------------------------------------------------------------------------------------------------ void Radar::xfer( Xfer *xfer ) @@ -1368,7 +1375,7 @@ void Radar::xfer( Xfer *xfer ) #if RETAIL_COMPATIBLE_XFER_SAVE XferVersion currentVersion = 1; #else - XferVersion currentVersion = 2; + XferVersion currentVersion = 3; #endif XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); @@ -1398,12 +1405,66 @@ void Radar::xfer( Xfer *xfer ) xfer->xferUser(&m_radarForceOn, sizeof(m_radarForceOn)); } + if (version <= 2) + { + if (xfer->getXferMode() == XFER_SAVE) + { + // TheSuperHackers @info For legacy xfer compatibility. + // Transfer all local hero objects to local object list. + RadarObject **fromList = &m_localHeroObjectList; + RadarObject **toList = &m_localObjectList; + while (*fromList != NULL) + { + RadarObject* nextObject = (*fromList)->friend_getNext(); + (*fromList)->friend_setNext(NULL); + linkRadarObject(*fromList, toList); + *fromList = nextObject; + } + } + } + else + { + xferRadarObjectList( xfer, &m_localHeroObjectList ); + } + // save our local object list xferRadarObjectList( xfer, &m_localObjectList ); // save the regular object list xferRadarObjectList( xfer, &m_objectList ); + if (version <= 2) + { + // TheSuperHackers @info For legacy xfer compatibility. + // Transfer hero local object(s) back to local hero object list. + // This needs to be done on both load and save. + RadarObject **fromList = &m_localObjectList; + RadarObject **toList = &m_localHeroObjectList; + RadarObject *currObject; + RadarObject *prevObject; + RadarObject *nextObject; + prevObject = NULL; + for (currObject = *fromList; currObject != NULL; currObject = nextObject) + { + nextObject = currObject->friend_getNext(); + if (currObject->friend_getObject()->isHero()) + { + if (prevObject != NULL) + { + prevObject->friend_setNext(nextObject); + } + else + { + *fromList = nextObject; + } + currObject->friend_setNext(NULL); + linkRadarObject(currObject, toList); + continue; + } + prevObject = currObject; + } + } + // save the radar event count and data UnsignedShort eventCountVerify = MAX_RADAR_EVENTS; UnsignedShort eventCount = eventCountVerify; diff --git a/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h b/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h index 03ab169086..528ddb52a9 100644 --- a/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h +++ b/Core/GameEngineDevice/Include/W3DDevice/Common/W3DRadar.h @@ -58,9 +58,6 @@ class W3DRadar : public Radar virtual void update( void ); ///< subsystem update virtual void reset( void ); ///< subsystem reset - virtual RadarObjectType addObject( Object *obj ); ///< add object to radar - virtual RadarObjectType removeObject( Object *obj ); ///< remove object from radar - virtual void newMap( TerrainLogic *terrain ); ///< reset radar for new map virtual void draw( Int pixelX, Int pixelY, Int width, Int height ); ///< draw the radar @@ -85,7 +82,7 @@ class W3DRadar : public Radar void drawIcons( Int pixelX, Int pixelY, Int width, Int height ); ///< draw all of the radar icons void updateObjectTexture(TextureClass *texture); static Bool canRenderObject( const RadarObject *rObj, const Player *localPlayer ); - void renderObjectList( const RadarObject *listHead, TextureClass *texture, Bool calcHero = FALSE ); ///< render an object list to the texture + void renderObjectList( const RadarObject *listHead, TextureClass *texture ); void interpolateColorForHeight( RGBColor *color, Real height, Real hiZ, @@ -122,6 +119,4 @@ class W3DRadar : public Radar Real m_viewAngle; ///< camera angle used for the view box we have Real m_viewZoom; ///< camera zoom used for the view box we have ICoord2D m_viewBox[ 4 ]; ///< radar cell points for the 4 corners of view box - - std::vector m_cachedHeroObjectList; //< cache of hero objects for drawing icons in radar overlay }; diff --git a/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp b/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp index c6c413b345..21aea05038 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/Common/System/W3DRadar.cpp @@ -602,12 +602,13 @@ void W3DRadar::drawEvents( Int pixelX, Int pixelY, Int width, Int height ) //------------------------------------------------------------------------------------------------- void W3DRadar::drawIcons( Int pixelX, Int pixelY, Int width, Int height ) { - // draw the hero icons - std::vector::const_iterator iter = m_cachedHeroObjectList.begin(); - while (iter != m_cachedHeroObjectList.end()) + Player *player = rts::getObservedOrLocalPlayer(); + for (RadarObject *heroObj = m_localHeroObjectList; heroObj; heroObj = heroObj->friend_getNext()) { - drawHeroIcon( pixelX, pixelY, width, height, (*iter)->getPosition() ); - ++iter; + if (canRenderObject(heroObj, player)) + { + drawHeroIcon(pixelX, pixelY, width, height, heroObj->friend_getObject()->getPosition()); + } } } @@ -621,8 +622,9 @@ void W3DRadar::updateObjectTexture(TextureClass *texture) REF_PTR_RELEASE(surface); // rebuild the object overlay - renderObjectList( getObjectList(), texture ); - renderObjectList( getLocalObjectList(), texture, TRUE ); + renderObjectList( m_objectList, texture ); + renderObjectList( m_localObjectList, texture ); + renderObjectList( m_localHeroObjectList, texture ); } //------------------------------------------------------------------------------------------------- @@ -676,7 +678,7 @@ Bool W3DRadar::canRenderObject( const RadarObject *rObj, const Player *localPlay //------------------------------------------------------------------------------------------------- /** Render an object list into the texture passed in */ //------------------------------------------------------------------------------------------------- -void W3DRadar::renderObjectList( const RadarObject *listHead, TextureClass *texture, Bool calcHero ) +void W3DRadar::renderObjectList( const RadarObject *listHead, TextureClass *texture ) { // sanity @@ -691,12 +693,6 @@ void W3DRadar::renderObjectList( const RadarObject *listHead, TextureClass *text Player *player = rts::getObservedOrLocalPlayer(); - if( calcHero ) - { - // clear all entries from the cached hero object list - m_cachedHeroObjectList.clear(); - } - for( const RadarObject *rObj = listHead; rObj; rObj = rObj->friend_getNext() ) { if (!canRenderObject(rObj, player)) @@ -734,12 +730,6 @@ void W3DRadar::renderObjectList( const RadarObject *listHead, TextureClass *text } - // cache hero objects for drawing in icon layer - if( calcHero && obj->isHero() ) - { - m_cachedHeroObjectList.push_back(obj); - } - // draw the blip, but make sure the points are legal if( legalRadarPoint( radarPoint.x, radarPoint.y ) ) surface->DrawPixel( radarPoint.x, radarPoint.y, c ); @@ -988,8 +978,6 @@ void W3DRadar::reset( void ) // extending functionality, call base class Radar::reset(); - m_cachedHeroObjectList.clear(); - // clear our texture data, but do not delete the resources SurfaceClass *surface; @@ -1024,39 +1012,6 @@ void W3DRadar::update( void ) } -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -RadarObjectType W3DRadar::addObject( Object* obj ) -{ - RadarObjectType addedType = Radar::addObject(obj); - - if (addedType == RadarObjectType_Local) - { - if (obj->isHero() && !RadarObject::isTemporarilyHidden(obj)) - { - m_cachedHeroObjectList.push_back(obj); - } - } - - return addedType; -} - -//------------------------------------------------------------------------------------------------- -// TheSuperHackers @bugfix xezon 05/07/2025 Now removes the cached hero immediately because -// otherwise the object pointer could be dangling and used for a bit too long. -//------------------------------------------------------------------------------------------------- -RadarObjectType W3DRadar::removeObject( Object* obj ) -{ - RadarObjectType removedType = Radar::removeObject(obj); - - if (removedType == RadarObjectType_Local) - { - stl::find_and_erase_unordered(m_cachedHeroObjectList, obj); - } - - return removedType; -} - //------------------------------------------------------------------------------------------------- /** Reset the radar for the new map data being given to it */ //------------------------------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp index 4803ccc2fd..217474b898 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -1045,12 +1045,12 @@ void Player::becomingLocalPlayer(Bool yes) { // Added support for updating the perceptions of garrisoned buildings containing enemy stealth units. // When changing teams, it is necessary to update this information. + Bool requireRadarRefresh = false; ContainModuleInterface *contain = object->getContain(); if( contain ) { contain->recalcApparentControllingPlayer(); - TheRadar->removeObject( object ); - TheRadar->addObject( object ); + requireRadarRefresh = true; } if( object->isKindOf( KINDOF_DISGUISER ) ) @@ -1080,11 +1080,16 @@ void Player::becomingLocalPlayer(Bool yes) else draw->setIndicatorColor( object->getIndicatorColor() ); } - TheRadar->removeObject( object ); - TheRadar->addObject( object ); + requireRadarRefresh = true; } } } + + if (requireRadarRefresh) + { + TheRadar->removeObject( object ); + TheRadar->addObject( object ); + } } deleteInstance(iter); } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index 79f8392c89..48296af878 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -1086,12 +1086,12 @@ void Player::becomingLocalPlayer(Bool yes) { // Added support for updating the perceptions of garrisoned buildings containing enemy stealth units. // When changing teams, it is necessary to update this information. + Bool requireRadarRefresh = false; ContainModuleInterface *contain = object->getContain(); if( contain ) { contain->recalcApparentControllingPlayer(); - TheRadar->removeObject( object ); - TheRadar->addObject( object ); + requireRadarRefresh = true; } if( object->isKindOf( KINDOF_DISGUISER ) ) @@ -1123,11 +1123,16 @@ void Player::becomingLocalPlayer(Bool yes) else draw->setIndicatorColor( object->getIndicatorColor() ); } - TheRadar->removeObject( object ); - TheRadar->addObject( object ); + requireRadarRefresh = true; } } } + + if (requireRadarRefresh) + { + TheRadar->removeObject( object ); + TheRadar->addObject( object ); + } } deleteInstance(iter); }