Skip to content

bugfix(object): Add support for object that has POWERED KindOf type without power - #3574

Open
Caball009 wants to merge 4 commits into
TheSuperHackers:mainfrom
Caball009:Caball009/fix_kindof_powered
Open

Caball009 wants to merge 4 commits into
TheSuperHackers:mainfrom
Caball009:Caball009/fix_kindof_powered

Conversation

@Caball009

@Caball009 Caball009 commented Oct 22, 2026 •

Copy link
Copy Markdown
Object AmericaTankAvengerLaserTurret
  KindOf = +POWERED
End

This PR makes 3 changes:

  1. Fixes a crash due to a nullptr dereference in Drawable::setColorTintEnvelope (see issue). The USA Avenger laser turret now turns dark when underpowered (if set to POWERED), but no low power icon because the unit itself is still responsive.
  2. Powered units that get built without sufficient power turn dark immediately.
  3. No-retail: Powered units go through the standard energy consumption logic, even when they consume no energy themselves. If a player has insufficient power, all powered units and unit modules are affected and their drawables are updated.

See commits for clean diffs.

TODO:

  • Replicate to Generals.
  • Separate changes into 3 commits: (1) crash fix, (2) dark tint when built, (3) no retail changes.

@Caball009 Caball009 added Enhancement Is new feature or request Minor Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Crash This is a crash, very bad labels Oct 22, 2026
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The changes update tint-envelope access and transfer for Overlord riders. They also add non-retail-compatible handling for powered objects: brownout signaling for qualifying zero-energy objects, disabled tint during initialization, and attack rejection while underpowered.

Changes

Tint-envelope transfer

Layer / File(s) Summary
Tint-envelope API and rider draw calls
GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h, Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp, Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp, Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTruckDraw.cpp
getColorTintEnvelope returns a const pointer. setColorTintEnvelope accepts a const pointer and copies only when the destination and source pointers are non-null. The three Overlord rider draw paths pass the envelope pointer directly.

Powered-object behavior

Layer / File(s) Summary
Zero-energy powered-object influence handling
GeneralsMD/Code/GameEngine/Source/Common/RTS/Energy.cpp
Energy influence entry and exit retain their production and consumption adjustments. Outside retail-compatible builds, each method signals a brownout for a zero-energy powered object when power is insufficient and an owner exists.
Underpowered tint and attack eligibility
GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
Initialization applies disabled tint to powered objects disabled by DISABLED_UNDERPOWERED. Outside retail-compatible builds, Object::isAbleToAttack rejects underpowered objects.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested labels: Bug, Fix, Stability, Rendering, NoRetail

Suggested reviewers: bobtista

Merge Risk: Moderate · previous review be685

Creating a powered unit with zero energy while its owner is out of power can crash the game. A captured or transferred powered unit can also stay disabled under an owner that has power. Both problems should be fixed before this draft is merged.

Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Linked Issues check Warning Issue [#7] requires no crash, no attack or turret turning, a black texture, a lightning symbol, and free transporter movement when the portable Avenger turret loses power. The null-safe `Drawable::set… Implement the complete underpowered behavior for issue [#7]. Add the lightning-symbol indicator and block turret turning while DISABLED_UNDERPOWERED is active. Apply the attack restriction in every intended build configuration. Add regres…
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly summarizes the main change: support for objects with the POWERED KindOf type when they have no power.
Out of Scope Changes check Passed The Drawable changes prevent null tint-envelope dereferences in rider draw paths. The Object, Energy, and tint changes implement underpowered powered-object behavior. These changes directly supp…
Description check Passed The pull request description directly matches the changeset. It describes the crash fix, underpowered tint behavior, and no-retail energy logic implemented in the modified files.
Full details: Out of Scope Changes check

Explanation

The Drawable changes prevent null tint-envelope dereferences in rider draw paths. The Object, Energy, and tint changes implement underpowered powered-object behavior. These changes directly support issue [#7].

Full details: Linked Issues check

Explanation

Issue [#7] requires no crash, no attack or turret turning, a black texture, a lightning symbol, and free transporter movement when the portable Avenger turret loses power. The null-safe Drawable::setColorTintEnvelope call addresses the reported crash. Object::initObject applies TINT_STATUS_DISABLED to newly initialized underpowered objects, and Object::isAbleToAttack blocks attacks only when RETAIL_COMPATIBLE_CRC is disabled. The current description confirms that the lightning symbol is still absent. The reviewed changes do not establish that the turret cannot turn or that the transporter remains movable. No regression test evidence is present.

Resolution

Implement the complete underpowered behavior for issue [#7]. Add the lightning-symbol indicator and block turret turning while DISABLED_UNDERPOWERED is active. Apply the attack restriction in every intended build configuration. Add regression coverage for the crash and the powered Avenger behavior, including transporter movement.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 19178bd7-d94a-4f90-903d-ecfebd6d3e0f
📥 Commits

Reviewing files that changed from the base of the PR and between b7f0e31 and ea67714.

📒 Files selected for processing: 7
  • Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordAircraftDraw.cpp
  • Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTankDraw.cpp
  • Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DOverlordTruckDraw.cpp
  • Generals/Code/GameEngine/Include/GameClient/Drawable.h
  • Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp
  • GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread Generals/Code/GameEngine/Include/GameClient/Drawable.h Outdated
Comment thread Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (2)

🟡 Minor · Underpowered turrets can still rotate through recentering and idle… · Object.cpp:2995-2999

Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp:2995-2999
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Underpowered turrets can still rotate through recentering and idle scanning.

The DISABLED_UNDERPOWERED check in Object::isAbleToAttack() blocks only the object-target attack path. The turret recentering and idle-scan states call friend_turnTowardsAngle() with no power check. An underpowered turret can therefore keep turning, which Issue #7 does not allow.

Apply the power check in both Generals/ and GeneralsMD/, because each variant has its own TurretAI.cpp. A check in the shared turn helper would cover the recentering, idle-scan, and object-aim states in each variant. The body of that helper was not inspected, so no diff is given.

🟡 Minor · Allocate the rider's tint envelope before copying the carrier tint. · Drawable.h:317-318

Generals/Code/GameEngine/Include/GameClient/Drawable.h:317-318
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Allocate the rider's tint envelope before copying the carrier tint.

When a rider is added after the carrier becomes underpowered, the containment path does not propagate the carrier's existing disabled state. The new rider Drawable starts with a null tint envelope. Each Overlord draw path then calls setColorTintEnvelope(...), but the setter skips the copy. The rider can therefore miss the dark underpowered tint.

Update the shared setter in both game-variant headers.

Suggested fix
--- a/Generals/Code/GameEngine/Include/GameClient/Drawable.h
+++ b/Generals/Code/GameEngine/Include/GameClient/Drawable.h
@@
-	void setColorTintEnvelope( const TintEnvelope* source ) { if (m_colorTintEnvelope && source) *m_colorTintEnvelope = *source; }
+	void setColorTintEnvelope( const TintEnvelope* source )
+	{
+		if (source)
+		{
+			if (m_colorTintEnvelope == nullptr)
+				m_colorTintEnvelope = newInstance(TintEnvelope);
+			*m_colorTintEnvelope = *source;
+		}
+	}
--- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h
@@
-	void setColorTintEnvelope( const TintEnvelope* source ) { if (m_colorTintEnvelope && source) *m_colorTintEnvelope = *source; }
+	void setColorTintEnvelope( const TintEnvelope* source )
+	{
+		if (source)
+		{
+			if (m_colorTintEnvelope == nullptr)
+				m_colorTintEnvelope = newInstance(TintEnvelope);
+			*m_colorTintEnvelope = *source;
+		}
+	}
🧹 Nitpick comments: 2
Generals/Code/GameEngine/Source/Common/RTS/Energy.cpp: 1

128-129: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Limit zero-energy power updates to the affected object.

A zero-energy KINDOF_POWERED object reaches addConsumption(0) on both entry and exit. This call still scans all player objects through Player::onPowerBrownOutChange, although the energy state does not change. Apply the current brownout state directly to the entering object. Remove the player-wide refresh from the zero-energy exit path.

Suggested fix
 #if !RETAIL_COMPATIBLE_CRC
-	else if (obj->isKindOf(KINDOF_POWERED))
-		addConsumption(-energy);
+	else if (obj->isKindOf(KINDOF_POWERED)) {
+		if (hasSufficientPower())
+			obj->clearDisabled(DISABLED_UNDERPOWERED);
+		else
+			obj->setDisabled(DISABLED_UNDERPOWERED);
+	}
 #endif
@@
-#if !RETAIL_COMPATIBLE_CRC
-	else if (obj->isKindOf(KINDOF_POWERED))
-		addConsumption(energy);
-#endif
GeneralsMD/Code/GameEngine/Source/Common/RTS/Energy.cpp: 1

147-148: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid the player-wide refresh for zero-energy powered objects.

When energy == 0 and the object is KINDOF_POWERED, addConsumption(0) still calls Player::onPowerBrownOutChange, which iterates the player's objects. The same unnecessary scan occurs when the object leaves.

Synchronize only the entering object with hasSufficientPower(). Remove the zero-energy exit callback. Apply the same change in Generals and GeneralsMD.

Suggested fix
 #if !RETAIL_COMPATIBLE_CRC
-	else if (obj->isKindOf(KINDOF_POWERED))
-		addConsumption(-energy);
+	else if (obj->isKindOf(KINDOF_POWERED)) {
+		if (hasSufficientPower())
+			obj->clearDisabled(DISABLED_UNDERPOWERED);
+		else
+			obj->setDisabled(DISABLED_UNDERPOWERED);
+	}
 #endif
-#if !RETAIL_COMPATIBLE_CRC
-	else if (obj->isKindOf(KINDOF_POWERED))
-		addConsumption(energy);
-#endif

ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fb6eac9b-42e5-433f-891e-4a3f0baa40be
📥 Commits

Reviewing files that changed from the base of the PR and between ea67714 and 611cd56.

📒 Files selected for processing: 2
  • Generals/Code/GameEngine/Source/Common/RTS/Energy.cpp
  • GeneralsMD/Code/GameEngine/Source/Common/RTS/Energy.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread GeneralsMD/Code/GameEngine/Source/Common/RTS/Energy.cpp Outdated
@Caball009
Caball009 force-pushed the Caball009/fix_kindof_powered branch 2 times, most recently from 311a978 to be68507 Compare October 24, 2026 00:45

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6db375ef-f6e8-4cf0-a696-c4e0e3f4452f
📥 Commits

Reviewing files that changed from the base of the PR and between 311a978 and be68507.

📒 Files selected for processing: 2
  • GeneralsMD/Code/GameEngine/Source/Common/RTS/Energy.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread GeneralsMD/Code/GameEngine/Source/Common/RTS/Energy.cpp Outdated
Comment thread GeneralsMD/Code/GameEngine/Source/Common/RTS/Energy.cpp Outdated
@Caball009
Caball009 force-pushed the Caball009/fix_kindof_powered branch 5 times, most recently from 8b39d33 to cffe846 Compare October 24, 2026 18:57
@Caball009 Caball009 added the NoRetail This fix or change is not applicable with Retail game compatibility label Oct 24, 2026
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp Outdated
@Caball009
Caball009 force-pushed the Caball009/fix_kindof_powered branch 2 times, most recently from fb56f09 to 08d6abf Compare October 29, 2026 02:06
@Caball009
Caball009 marked this pull request as ready for review October 29, 2026 02:08
@greptile-apps

greptile-apps Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] No new blocking issue was established in the changes since the previous review.

Summary

The PR adds null-safe tint copying and updates how powered objects react to insufficient power.

  • Overlord riders receive tint envelopes without requiring one to exist.
  • Zero-cost powered objects now darken when their player lacks power.

Previous-thread outcomes:

  • Caball009 accepted the Generals build mismatch as deferred work because updating Generals remains an open TODO.
  • The inline-conditional thread was manually resolved without explanation; its reply addressed review wording, not the code.
  • The destruction-sound finding was withdrawn because it did not establish a new effect.

Reviews (4) · Last reviewed commit: "fix(object): Initialize first m_behavior..." · Reviewed by Greptile

Comment thread GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h
@Caball009 Caball009 added the Mod Relates to Mods or modding label Oct 29, 2026
// TheSuperHackers @info clearDisabled is only called here for correctness, ensuring the data
// is cleared if the object is about to be destroyed, since Object's destructor doesn't clear this.
else if (obj->isKindOf(KINDOF_POWERED))
obj->clearDisabled(DISABLED_UNDERPOWERED);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Moving a unit to another team while underpowered goes through this, so it gets re-enabled and then disabled again right after when it joins the new team. Maybe only do this on destroy?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Ok, changed.

Comment on lines +3301 to +3302
// TheSuperHackers A unit module that requires to be powered cannot attack without power.
if (isDisabledByType(DISABLED_UNDERPOWERED))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment missing tag/date.

Affects every underpowered object, not just units. AI auto-acquire skips objects that can’t attack, so enemies would stop attacking underpowered units and structures.

Attack orders and guard logic change too I think.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I've removed this as it doesn't seem necessary, anyway.

@Caball009
Caball009 force-pushed the Caball009/fix_kindof_powered branch from 08d6abf to 773acdb Compare October 29, 2026 20:03
Comment thread GeneralsMD/Code/GameEngine/Source/Common/RTS/Energy.cpp
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
Comment thread GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp
@Caball009
Caball009 force-pushed the Caball009/fix_kindof_powered branch from 773acdb to 2adabd0 Compare October 29, 2026 20:06
Comment on lines +310 to +312
// explicitly initialize first field as nullptr as Object::pauseAllSpecialPowers relies on it,
// which may be called downstream of Player::onPowerBrownOutChange and Object::setTeam
m_behaviors[0] = nullptr;

@Caball009 Caball009 Oct 29, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Strictly unnecessary but would otherwise implicitly rely on the memory manager which zeroes out newly allocated memory. A lot of code relies on this behavior, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Crash This is a crash, very bad Enhancement Is new feature or request Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker Mod Relates to Mods or modding NoRetail This fix or change is not applicable with Retail game compatibility ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adding POWERED KindOf to portable structures crashed the game

3 participants