bugfix(gamelogic): Modules now cease updating when disabled by non-whitelisted disabled types#2458
Conversation
|
| Filename | Overview |
|---|---|
| Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | Wraps the module update condition in a RETAIL_COMPATIBLE_CRC guard, introducing a new exclusive disabled-types check via testForAll in non-retail builds; logic is correct and handles the zero-disabled case via vacuous truth. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | Identical fix applied to the Zero Hour variant; both #ifdef ALLOW_NONSLEEPY_UPDATES and sleepy-update paths are correctly patched. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[GameLogic::update - iterate modules] --> B{RETAIL_COMPATIBLE_CRC?}
B -- Yes --> C["Old check: !dis.any() || dis.anyIntersectionWith(whitelist)"]
B -- No --> D["New check: whitelist.testForAll(dis)
= all active disabled bits ⊆ whitelist"]
C --> E{Condition true?}
D --> E
E -- Yes --> F[Run module update]
E -- No --> G[Skip update / use default sleep]
subgraph testForAll semantics
H["dis = 0 (not disabled)"] --> I["(W & 0)==0 → true ✓"]
J["dis = HELD, W = {HELD}"] --> K["(HELD & HELD)==HELD → true ✓"]
L["dis = EMP, W = {HELD}"] --> M["(HELD & EMP)==EMP → false ✓"]
N["dis = HELD|EMP, W = {HELD}"] --> O["HELD == HELD|EMP → false ✓ (bug fixed)"]
end
Reviews (3): Last reviewed commit: "refactor: Remove redundant checks" | Re-trigger Greptile
43e4df1 to
5330787
Compare
Caball009
left a comment
There was a problem hiding this comment.
Before merging this I'd like to test if this code overlaps / conflicts with possible fixes for the gattling cannon barrels rotating despite insufficient energy.
|
Do we need to label this is Controversial? |
| // Previously, if the disabled mask had any bits in common with the disabled-types-to-process mask, | ||
| // the update would be processed. Now, if any *other* bits are set in the disabled mask, the update | ||
| // is no longer processed. | ||
| if (!dis.any() || u->getDisabledTypesToProcess().testForAll(dis)) |
There was a problem hiding this comment.
Is this certainly correct?
I would have expected:
const Bool isDisabled = dis.any() && dis.testForAny(u->getDisabledTypesToProcess());
if (!isDisabled)
...There was a problem hiding this comment.
The getDisabledTypesToProcess function returns the disabled statuses that do not block the module's update.
For example, BaseRegenerateUpdate::getDisabledTypesToProcess returns DISABLED_UNDERPOWERED. This means the module is allowed to continue performing its repair update even if the player has low power.
Similarly, PoisonedBehaviour::getDisabledTypesToProcess returns DISABLEDMASK_ALL because nothing should disable the poisoned update. This is highlighted by the accompanying comment "we should still poison disabled things".
The function serves as a whitelist of disabled types that do not affect the update of the respective module. Perhaps a better name would be getAllowedDisabledTypes or getIgnoredDisabledTypes.
There was a problem hiding this comment.
Ok got it. But why is this condition still correct then?
So what we need to test is: (dis & ~allowedDisabledTypes) == 0
Is u->getDisabledTypesToProcess().testForAll(dis) equivalent to that? It is so complicated to read. Can we write that easier?
There was a problem hiding this comment.
So after cleaning up BitFlags function and getting a better understanding of what each of these functions do, I think
if (!dis.any() || u->getDisabledTypesToProcess().testForAll(dis))
{
// do the update ...
}should be
const DisabledMaskType disallowedDisabledTypes = u->getDisabledTypesToProcess().flip();
if (dis.testForNone(disallowedDisabledTypes))
{
// do the update ...
}Can you double check?
There was a problem hiding this comment.
Isn't that the same thing but in reverse? What u->getDisabledTypesToProcess().testForAll(dis)) does is essentially check that all bits set in dis (the current status) are also set in the allowed mask. If dis has any bits that are not in the allowed mask, then testForAll returns false and the update is skipped.
There was a problem hiding this comment.
Right. But then you can omit !dis.any() as well. It is implicitly true when dis is empty. The first version is somehow much more complicated to understand for me in this context, because it uses the test-against flags as our primary, which requires mental gymnastics.
|
Condition can probably be a bit simplified. |
What do you suggest? |
|
Can you try remove |
It does work. The check is indeed redundant, but it does make the intent explicit and possibly a bit clearer. Should I remove it? |
|
When not needed, remove. |
5330787 to
a2c0626
Compare
Closes TheSuperHackers/GeneralsGamePatch#147
This change fixes an issue where modules continue updating when disabled by a type that is not whitelisted.
For example, the stealth detector module has
DISABLED_HELDwhitelisted. This allows stealth detection to still run if an object is held, which in most cases is the result of containment (thoughCanDetectWhileContainedtypically blocks this). Any other disabling type is expected to shut the stealth detector down, such asDISABLED_EMPorDISABLED_SUBDUED. The problem is that the module only looks for whether theDISABLED_HELDbit is set and, if so, any other disabled type such asDISABLED_EMPis not considered.This can also be seen elsewhere, such as with powered USA base defences.
BaseRegenerateUpdatewhitelistsDISABLED_UNDERPOWERED. This means a Patriot Missile Battery will still repair itself when out of power. Conversely, if a Patriot Missile Battery is disabled by a Microwave Tank (DISABLED_SUBDUED), it will not repair itself. But if an underpowered Patriot Missile Battery is out of power and disabled by a Microwave Tank, then it will repair itself.Before
The EMP Patriot will not repair while subdued, but will while the power is also out
REPAIR.mp4
After
A disabled (including unmanned) Overlord's Gattling Cannon no longer detects stealth units
DISABLE_DETECTION.mp4