Convert Shadow to CantBlockBy/CanBlockIfShadow static abilities - #11877
Open
BigCrunch22 wants to merge 3 commits into
Open
BigCrunch22 wants to merge 3 commits into
BigCrunch22 wants to merge 3 commits into
Conversation
Move Shadow's block-legality logic out of hardcoded keyword-string checks in CombatUtil.canBlock() and into real StaticAbility-driven CantBlockBy restrictions generated by CardFactoryUtil, mirroring how Flying/Landwalk/Fear are already implemented (Card-Forge#3307). Add StaticAbilityMode.CanBlockIfShadow, a direct sibling of the existing CanBlockIfReach/Dragon Hunter mechanism, to support the four cards that let a creature block shadow creatures without having shadow itself (Aetherflame Wall, Aether Web, Heartwood Dryad, Wall of Diffusion). Rewrite those four as plain static abilities with no keyword involved. Fix a copy/paste bug found along the way: Wall of Diffusion and Heartwood Dryad were both scripted with Aetherflame Wall's ability text ("as though they didn't have shadow") instead of their own Oracle wording ("as though it had shadow") - the two are not mechanically interchangeable. Update CreatureEvaluator's AI scoring heuristic, which was reading the now-removed keyword string. Closes Card-Forge#2550.
tool4ever
reviewed
Sep 12, 2026
Contributor
|
I'm still not 100% sure what should happen if 🤔 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This implements issue #2550, one item off the #3307 "remove hidden keywords" checklist. Shadow's blocking restriction currently lives as three hand-written
hasKeyword(...)checks insideCombatUtil.canBlock(), instead of as composableStaticAbilityrestrictions the way Flying, Landwalk, Fear, etc. are already implemented. This PR moves it onto that same infrastructure and discards the keyword-string hack entirely.What was there before
Two problems with this:
"CARDNAME can block creatures with shadow as though they didn't have shadow."was reused, unmodified, as the "keyword" for four different cards — including two (Wall of Diffusion, Heartwood Dryad) whose real Oracle text says "as though it had shadow," not "as though they didn't have shadow." Those two phrasings are not mechanically interchangeable (see below), so those two cards were silently behaving like Aetherflame Wall instead of matching their own printed text.What changed
1. Shadow itself → two generated
CantBlockByabilitiesCardFactoryUtilnow generates Shadow's two-directional restriction the same way Flying already generates its own (oneCantBlockByability for the attacker side, one for the blocker side), instead ofCombatUtilspecial-casing it. The three hardcoded checks above are deleted outright.2. New
StaticAbilityMode.CanBlockIfShadowFour printed cards let a creature block a shadow creature despite the blocker not having shadow itself. That's exactly what
CanBlockIfReachalready does for Dragon Hunter ("can block Dragons as though it had reach"), so this adds a direct sibling mode,CanBlockIfShadow, plus two small helper methods inStaticAbilityCantAttackBlock, hooked into the existingValidBlockeroverride check right next to the Reach one.3. The four cards rewritten as plain static abilities — no keyword involved
Heartwood Dryad,Wall of Diffusion— oneCanBlockIfShadowability each, now using their correct Oracle wording.Aetherflame Wall,Aether Web— the sameCanBlockIfShadow, plus one extra ability:CantBlock | ValidCard$ Card.Self+withShadow. This reproduces the documented ruling that if Aetherflame Wall ever gains shadow, it can't block anything — not even other shadow creatures — without needing any special-cased combat logic to get there.4.
CreatureEvaluatorAI heuristicWas checking for the now-deleted keyword string; updated to check for the
CanBlockIfShadowstatic ability instead.Why the extra
CantBlockability"Can block creatures with shadow as though they didn't have shadow" (Aetherflame Wall's wording) and "as though it had shadow" (Heartwood Dryad's wording) read as interchangeable but aren't: only the first creates an actual rules contradiction if the creature ever gains real shadow. Rather than model that interaction generically, it's declared directly as a second, always-on ability — if the card currently has shadow, it can't block, period. That one extra line reproduces the known ruling exactly.
Files changed
forge-game/.../card/CardFactoryUtil.javaforge-game/.../staticability/StaticAbilityMode.javaforge-game/.../staticability/StaticAbilityCantAttackBlock.javaforge-game/.../combat/CombatUtil.javaforge-ai/.../CreatureEvaluator.javares/cardsfolder/a/aetherflame_wall.txtres/cardsfolder/a/aether_web.txtres/cardsfolder/h/heartwood_dryad.txtres/cardsfolder/w/wall_of_diffusion.txtCloses #2550.
Code changes made with heavy assistance from Claude