Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Core/GameEngine/Include/Common/OptionPreferences.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class OptionPreferences : public UserPreferences
Real getScrollFactor();
Bool getDrawScrollAnchor();
Bool getMoveScrollAnchor();
Bool getSelectionCircleEnabled() const;
Bool getCursorCaptureEnabledInWindowedGame() const;
Bool getCursorCaptureEnabledInWindowedMenu() const;
Bool getCursorCaptureEnabledInFullscreenGame() const;
Expand Down
14 changes: 14 additions & 0 deletions Core/GameEngine/Source/Common/OptionPreferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,20 @@ Bool OptionPreferences::getRightMouseScrollWithAlternateMouseEnabled() const
return FALSE;
}

// TheSuperHackers @feature Options.ini: SelectionCircle = Yes draws a green ring on the ground
// under every selected object, so the current selection reads at a glance.
Bool OptionPreferences::getSelectionCircleEnabled() const
{
OptionPreferences::const_iterator it = find("SelectionCircle");
if (it == end())
return FALSE;

if (stricmp(it->second.str(), "yes") == 0) {
return TRUE;
}
return FALSE;
}

Bool OptionPreferences::getRetaliationModeEnabled()
{
OptionPreferences::const_iterator it = find("Retaliation");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,9 @@ class W3DModelDraw : public DrawModule, public ObjectDrawInterface

virtual void setFullyObscuredByShroud(Bool fullyObscured) override;
virtual void setTerrainDecal(TerrainDecalType type) override;
// TheSuperHackers @feature Selection ring, kept in its own slot so it does not evict the
// horde or chem suit decal while a unit is selected.
virtual void setSelectionDecal(Bool enable, Real radius) override;

virtual Bool isVisible() const override;
virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle) override;
Expand Down Expand Up @@ -501,6 +504,12 @@ class W3DModelDraw : public DrawModule, public ObjectDrawInterface
RenderObjClass* m_renderObject; ///< W3D Render object for this drawable
Shadow* m_shadow; ///< Updates/Renders shadows of this object
Shadow* m_terrainDecal;
// TheSuperHackers @feature Selection ring decal, independent of m_terrainDecal. The wanted
// state is remembered separately so the ring survives render object rebuilds - a damage
// state or upgrade swaps the model while the object stays selected.
Shadow* m_selectionDecal;
Bool m_selectionDecalWanted;
Real m_selectionDecalRadius;
TerrainTracksRenderObjClass* m_trackRenderObject; ///< This is rendered under object
ParticleSystemIDVec m_particleSystemIDs; ///< The ID numbers of the particle systems currently running.
std::vector<ModelConditionInfo::HideShowSubObjInfo> m_subObjectVec;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1732,6 +1732,10 @@ W3DModelDraw::W3DModelDraw(Thing *thing, const ModuleData* moduleData) : DrawMod
m_shadow = nullptr;
m_shadowEnabled = TRUE;
m_terrainDecal = nullptr;
// TheSuperHackers @feature no selection ring until one is asked for
m_selectionDecal = nullptr;
m_selectionDecalWanted = FALSE;
m_selectionDecalRadius = 0.0f;
m_trackRenderObject = nullptr;
m_whichAnimInCurState = -1;
m_nextState = nullptr;
Expand Down Expand Up @@ -1836,6 +1840,10 @@ void W3DModelDraw::setHidden(Bool hidden)
if (m_terrainDecal)
m_terrainDecal->enableShadowRender(!hidden);

// TheSuperHackers @fix the ring must not keep rendering under a hidden drawable
if (m_selectionDecal)
m_selectionDecal->enableShadowRender(!hidden);

if (m_trackRenderObject && hidden)
{ const Coord3D* pos = getDrawable()->getPosition();
m_trackRenderObject->addCapEdgeToTrack(pos->x,pos->y);
Expand Down Expand Up @@ -1949,6 +1957,9 @@ void W3DModelDraw::setFullyObscuredByShroud(Bool fullyObscured)
m_shadow->enableShadowInvisible(m_fullyObscuredByShroud);
if (m_terrainDecal)
m_terrainDecal->enableShadowInvisible(m_fullyObscuredByShroud);
// TheSuperHackers @fix the ring must not expose a fully shrouded unit
if (m_selectionDecal)
m_selectionDecal->enableShadowInvisible(m_fullyObscuredByShroud);

doStartOrStopParticleSys();
}
Expand Down Expand Up @@ -2719,6 +2730,53 @@ Bool W3DModelDraw::updateBonesForClientParticleSystems()



//-------------------------------------------------------------------------------------------------
// TheSuperHackers @feature Selection ring decal.
//-------------------------------------------------------------------------------------------------
/** Put a green ring on the ground under this object, or take it away.
*
* Uses the projected shadow system rather than screen space lines, so the ring is genuinely
* projected onto the terrain and the model draws over it. It lives in its own slot rather than
* sharing m_terrainDecal, so selecting a horde unit does not evict its horde ring.
*
* Expects a PlainRingSelection.tga in the mod's assets. The engine appends the extension, and
* the art is tinted green at runtime, so a plain white or greyscale ring works. */
//-------------------------------------------------------------------------------------------------
void W3DModelDraw::setSelectionDecal(Bool enable, Real radius)
{
// remembered so the ring can be recreated after a model swap tears the render object down
m_selectionDecalWanted = enable;
m_selectionDecalRadius = radius;

if (m_selectionDecal)
{
m_selectionDecal->release();
m_selectionDecal = nullptr;
}

if (!enable || m_renderObject == nullptr || TheProjectedShadowManager == nullptr)
return;

Shadow::ShadowTypeInfo decalInfo;
decalInfo.allowUpdates = FALSE; //the ring never needs regenerating
decalInfo.allowWorldAlign = TRUE; //wrap it around terrain and world objects
decalInfo.m_type = SHADOW_ALPHA_DECAL;
strlcpy(decalInfo.m_ShadowName, "PlainRingSelection", ARRAY_SIZE(decalInfo.m_ShadowName));
decalInfo.m_sizeX = radius * 2.0f;
decalInfo.m_sizeY = radius * 2.0f;
decalInfo.m_offsetX = 0.0f;
decalInfo.m_offsetY = 0.0f;

m_selectionDecal = TheProjectedShadowManager->addDecal(m_renderObject, &decalInfo);
if (m_selectionDecal)
{
m_selectionDecal->enableShadowInvisible(m_fullyObscuredByShroud);
m_selectionDecal->enableShadowRender(TRUE);
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
//the art is a plain ring, so tint it to the selection green
m_selectionDecal->setColor(GameMakeColor(0, 255, 0, 255));
}
}

//-------------------------------------------------------------------------------------------------
void W3DModelDraw::setTerrainDecal(TerrainDecalType type)
{
Expand Down Expand Up @@ -2790,6 +2848,14 @@ void W3DModelDraw::nukeCurrentRender(Matrix3D* xform)
m_terrainDecal->release();
m_terrainDecal = nullptr;

// TheSuperHackers @fix The selection ring is bound to the render object about to be torn down
// here, exactly like the shadow and the terrain decal above, so it has to go with them. Left
// behind it stayed registered with the projected shadow manager while the render object it
// points at was freed, and the next renderShadows walked that dangling entry into the driver.
if (m_selectionDecal)
m_selectionDecal->release();
m_selectionDecal = nullptr;
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.

// remove existing render object from the scene
if (m_renderObject)
{
Expand Down Expand Up @@ -3078,6 +3144,11 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState)
}
}

// TheSuperHackers @fix The selection ring was bound to the render object that was just
// torn down; the object is still selected, so put the ring back on the new one.
if (m_selectionDecalWanted)
setSelectionDecal(TRUE, m_selectionDecalRadius);
Comment on lines +3147 to +3150

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Hidden ring becomes visible

When a selected, effectively hidden drawable replaces its model, this block recreates the selection decal with rendering enabled, while the subsequent hidden-state restoration disables only the render object and normal shadow. The green ring therefore renders beneath the hidden drawable and reveals its location.

Knowledge Base Used: Game client runtime

Prompt To Fix With AI
This is a comment left during a code review.
Path: Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp
Line: 3147-3150

Comment:
**Hidden ring becomes visible**

When a selected, effectively hidden drawable replaces its model, this block recreates the selection decal with rendering enabled, while the subsequent hidden-state restoration disables only the render object and normal shadow. The green ring therefore renders beneath the hidden drawable and reveals its location.

**Knowledge Base Used:** [Game client runtime](https://app.greptile.com/thesuperhackers/-/custom-context/knowledge-base/thesuperhackers/generalsgamecode/-/docs/game-client-runtime.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


if( m_renderObject )
{
// set collision type for render object. Used by WW3D2 collision code.
Expand Down
3 changes: 3 additions & 0 deletions Generals/Code/GameEngine/Include/Common/DrawModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class DrawModule : public DrawableModule
virtual void setTerrainDecal(TerrainDecalType type) {};
virtual void setTerrainDecalSize(Real x, Real y) {};
virtual void setTerrainDecalOpacity(Real o) {};
// TheSuperHackers @feature Selection ring decal, in its own slot so it does not evict the
// horde or chem suit decal while a unit is selected.
virtual void setSelectionDecal(Bool enable, Real radius) {};

virtual void setFullyObscuredByShroud(Bool fullyObscured) = 0;

Expand Down
3 changes: 3 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class DrawModule : public DrawableModule
virtual void setTerrainDecal(TerrainDecalType type) {};
virtual void setTerrainDecalSize(Real x, Real y) {};
virtual void setTerrainDecalOpacity(Real o) {};
// TheSuperHackers @feature Selection ring decal, in its own slot so it does not evict the
// horde or chem suit decal while a unit is selected.
virtual void setSelectionDecal(Bool enable, Real radius) {};

virtual void setFullyObscuredByShroud(Bool fullyObscured) = 0;

Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ class GlobalData : public SubsystemInterface
Bool m_useAlternateMouse;
Bool m_useRightMouseScrollWithAlternateMouse; // TheSuperHackers @feature User option for RMB scroll in Alternate Mouse mode.
Bool m_clientRetaliationModeEnabled;
// TheSuperHackers @feature Draw a green hexagon ring under selected objects.
Bool m_selectionCircleEnabled;
Bool m_doubleClickAttackMove;
Bool m_rightMouseAlwaysScrolls;
Int m_jpegQuality; // TheSuperHackers @feature Quality for JPEG screenshots.
Expand Down
3 changes: 3 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,9 @@ class Drawable : public Thing,

/// Return true if drawable has been marked as "selected"
Bool isSelected() const { return m_selected; }
// TheSuperHackers @feature Green selection ring decal (Options.ini: SelectionCircle).
// Client only -- never xfer'd, never read by game logic.
void updateSelectionDecal( void );
void onSelected(); ///< Work unrelated to selection that must happen at time of selection
void onUnselected(); ///< Work unrelated to selection that must happen at time of unselection

Expand Down
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,7 @@ GlobalData::GlobalData()
m_useRightMouseScrollWithAlternateMouse = TRUE;
#endif
m_clientRetaliationModeEnabled = TRUE; //On by default.
m_selectionCircleEnabled = FALSE;
m_doubleClickAttackMove = FALSE;

}
Expand Down Expand Up @@ -1206,6 +1207,7 @@ void GlobalData::parseGameDataDefinition( INI* ini )
TheWritableGlobalData->m_useRightMouseScrollWithAlternateMouse = optionPref.getRightMouseScrollWithAlternateMouseEnabled();
TheWritableGlobalData->m_clientRetaliationModeEnabled = optionPref.getRetaliationModeEnabled();
TheWritableGlobalData->m_doubleClickAttackMove = optionPref.getDoubleClickAttackMoveEnabled();
TheWritableGlobalData->m_selectionCircleEnabled = optionPref.getSelectionCircleEnabled();
TheWritableGlobalData->m_jpegQuality = optionPref.getJpegQuality();
TheWritableGlobalData->m_keyboardScrollFactor = optionPref.getScrollFactor();
TheWritableGlobalData->m_drawScrollAnchor = optionPref.getDrawScrollAnchor();
Expand Down
48 changes: 47 additions & 1 deletion GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -979,8 +979,50 @@ void Drawable::colorTint( const RGBColor* color )
//-------------------------------------------------------------------------------------------------
/** Gathering point for all things besides actual selection that must happen on selection */
//-------------------------------------------------------------------------------------------------
// TheSuperHackers @feature Selection ring decal.
//-------------------------------------------------------------------------------------------------
/** Show or hide the green selection ring, following the SelectionCircle option.
*
* Only the first draw module gets one, matching how terrain decals avoid stacking. */
//-------------------------------------------------------------------------------------------------
void Drawable::updateSelectionDecal( void )
{
Bool wanted = TheGlobalData && TheGlobalData->m_selectionCircleEnabled && isSelected();

const Object *obj = getObject();
if( obj == nullptr || obj->isEffectivelyDead() || obj->isKindOf( KINDOF_IGNORED_IN_GUI ) )
wanted = FALSE;

Real radius = 0.0f;
if( wanted )
{
// The bounding circle encloses the whole model, so it reads as too big drawn at full
// size. How much too big depends on the shape: a building fills its circle, a soldier
// barely occupies the middle of one, so scale per kind.
Real scale;
if( obj->isKindOf( KINDOF_STRUCTURE ) )
scale = 1.0f;
else if( obj->isKindOf( KINDOF_INFANTRY ) )
scale = 0.7f;
else
scale = 0.85f; // vehicles, aircraft and everything else

radius = getDrawableGeometryInfo().getBoundingCircleRadius() * scale;
if( radius < 1.0f )
radius = 1.0f;
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
}

for( DrawModule **dm = getDrawModules(); *dm; ++dm )
{
(*dm)->setSelectionDecal( wanted, radius );
break; // first draw module only, so rings do not stack
}
}

void Drawable::onSelected()
{
// TheSuperHackers @feature put the selection ring up straight away rather than waiting a frame
updateSelectionDecal();

flashAsSelected();//much simpler

Expand All @@ -1001,7 +1043,8 @@ void Drawable::onSelected()
//-------------------------------------------------------------------------------------------------
void Drawable::onUnselected()
{
// nothing
// TheSuperHackers @feature take the selection ring down
updateSelectionDecal();
}

//-------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -4161,6 +4204,9 @@ void Drawable::reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* old
//-------------------------------------------------------------------------------------------------
void Drawable::reactToGeometryChange()
{
// TheSuperHackers @fix resize the selection ring along with the geometry it is derived from
updateSelectionDecal();

for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
(*dm)->reactToGeometryChange();
Expand Down