Skip to content

Refactor: Hidden keywords to StaticAbilityMode (Issue #3307)#9703

Open
calaespi wants to merge 20 commits intoCard-Forge:masterfrom
calaespi:refactor/hidden-keywords
Open

Refactor: Hidden keywords to StaticAbilityMode (Issue #3307)#9703
calaespi wants to merge 20 commits intoCard-Forge:masterfrom
calaespi:refactor/hidden-keywords

Conversation

@calaespi
Copy link
Contributor

@calaespi calaespi commented Feb 9, 2026

Refactors hardcoded hidden keywords (MustBeBlocked, CantGainControl, etc.) to use type-safe StaticAbilityMode enum. Includes comprehensive tests in HiddenKeywordsMetaTest.java.

@Hanmac
Copy link
Contributor

Hanmac commented Feb 10, 2026

You probably should check out the other StaticAbility classes, like StaticAbilityCantTransform:
https://github.com/Card-Forge/forge/blob/master/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantTransform.java

using something like ValidCard and others can be used by other cards

Comment on lines 6 to 8
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ All creatures able to block CARDNAME do so. | Description$ All creatures able to block enchanted creature do so.
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddStaticAbility$ SMustBlock | Description$ All creatures able to block enchanted creature do so.
SVar:SMustBlock:Mode$ MustBeBlockedByAll
Oracle:Enchant creature\nAll creatures able to block enchanted creature do so.
Copy link
Contributor

Choose a reason for hiding this comment

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

If MustBeBlockedByAll is done as a static, it should not be added to the enchanted creature anymore.

Notice how the Aura doesn't say: enchanted creature gains

Copy link
Contributor

Choose a reason for hiding this comment

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

imo at least all MustBeBlockedBy variants should be handled together
but maybe remove it from this one so we can merge in smaller steps
and you have the static already as part of #4745 instead

@Hanmac
Copy link
Contributor

Hanmac commented Feb 10, 2026

I did the CountersRemain as extra MR: #9709

calaespi and others added 7 commits February 10, 2026 23:36
…ngesZone excludes Stack/Exile + Exiled). Initialize Localizer/Lang to prevent NPE during GameType init.
…tions to aid Outpost Siege simulations. Keep Issue4745Test disabled until full phase-driven harness is ready.
Harness de integración: Upkeep y TestAction para Outpost Siege (Issue Card-Forge#4745)
…ngesZone excludes Stack/Exile + Exiled). Initialize Localizer/Lang to prevent NPE during GameType init.
Asegurar ForgetOnMoved: unit test y init de localización
@calaespi
Copy link
Contributor Author

Merged master (incorporating StaticAbilityCountersRemain) and refactored CantGainControl, LethalDamageByPower, and BounceAtUntap to use dedicated StaticAbility classes as requested. Also removed the redundant RetainCounters mode.

// this is the amount of damage a creature needs to receive before it dies
public final int getLethal() {
if (hasKeyword("Lethal damage dealt to CARDNAME is determined by its power rather than its toughness.")) {
boolean lethalByPower = hasKeyword("Lethal damage dealt to CARDNAME is determined by its power rather than its toughness.") || StaticAbilityLethalDamageByPower.isLethalDamageByPower(this);
Copy link
Contributor

Choose a reason for hiding this comment

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

@Hanmac
we need a better solution imo
this method is called so many times, it'd be terrible to check it like this for a single card

don't want another Topsy Turvy situation like #8513

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer the new approach?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to clarify: the current implementation already uses the cached flag approach (checking !lethalDamageByPower.isEmpty()) to avoid the expensive iteration. I assume this is the "new approach" you are referring to?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes thanks
just need @Hanmac to vote on it

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 on that part

If the card gets updated, the keyword can be removed

Also remove the static mode that was added because of it

@calaespi
Copy link
Contributor Author

Refactored LethalDamageByPower to use a cached boolean flag in Card.java, avoiding the expensive iteration over all static abilities in getLethal(). Also removed the now unused StaticAbilityLethalDamageByPower class. Verified with tests (Zilortha test passes, existing failure in Skullbriar is unrelated).

return;
}

boolean cantGainControl = c.hasKeyword("Other players can't gain control of CARDNAME.") || StaticAbilityCantGainControl.cantGainControl(c);
Copy link
Contributor

Choose a reason for hiding this comment

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

this seems fine
but there is another place with the KW
remove checking for it from both


// Simulate Detain effect
// Detain adds "CARDNAME can't attack or block." and "CARDNAME's activated abilities can't be activated."
bear.addIntrinsicKeyword("CARDNAME can't attack or block.");
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't test for outdated KW

@calaespi
Copy link
Contributor Author

I have merged the latest changes from upstream/master and resolved the conflicts in AbilityActivated.java.
I preserved the isDetained() check from upstream while keeping the removal of the redundant keyword loop.
The PR should be conflict-free now.

Copy link
Contributor

Choose a reason for hiding this comment

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

reverting too much now...

@@ -0,0 +1,89 @@
package forge.gamesimulationtests;
Copy link
Contributor

Choose a reason for hiding this comment

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

not sure what you're doing but you keep mixing your PR
this is from #9717

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants