Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
21 changes: 21 additions & 0 deletions crates/engine/src/parser/oracle_effect/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31224,6 +31224,23 @@ fn public_attack_prohibition_parser_preserves_legacy_anaphora_and_connector() {
}
));
assert!(connector.duration.is_none());

let planeswalker_only = parse_effect_chain(
"that player can't attack planeswalkers you control during their next turn.",
AbilityKind::Spell,
);
assert!(matches!(
planeswalker_only.effect.as_ref(),
Effect::AddRestriction {
restriction: GameRestriction::ProhibitActivity {
activity: ProhibitedActivity::Attack {
defended: crate::types::triggers::AttackTargetFilter::Planeswalker,
protected_player: None,
},
..
},
}
));
}

#[test]
Expand All @@ -31233,6 +31250,10 @@ fn scoped_cant_attack_prohibition_supports_both_verbs_and_all_defended_scopes()
for verb in ["can't", "cannot"] {
for (scope, expected_defended) in [
("you", AttackTargetFilter::Player),
(
"planeswalkers you control",
AttackTargetFilter::Planeswalker,
),
(
"you or planeswalkers you control",
AttackTargetFilter::PlayerOrPlaneswalker,
Expand Down
16 changes: 16 additions & 0 deletions crates/engine/src/parser/oracle_ir/snapshot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2487,6 +2487,22 @@ fn liliana_the_repentant() {
insta::assert_json_snapshot!("liliana_the_repentant_lowered", &lowered);
}

/// CR 508.1d + CR 602.1: Onakke's two printed lines exercise both the
/// planeswalker-only combat-tax static and its graveyard activation. Snapshot
/// both document IR and lowering so neither line can silently degrade while
/// the other stays supported.
#[test]
fn onakke_oathkeeper() {
let (ir, lowered) = parse_two_layer(
"Creatures can't attack planeswalkers you control unless their controller pays {1} for each creature they control that's attacking a planeswalker you control.\n{4}{W}{W}, Exile this card from your graveyard: Return target planeswalker card from your graveyard to the battlefield.",
"Onakke Oathkeeper",
&["Creature"],
&["Ogre", "Spirit"],
);
insta::assert_json_snapshot!("onakke_oathkeeper_ir", &ir);
insta::assert_json_snapshot!("onakke_oathkeeper_lowered", &lowered);
}

/// CR 702.142a Boast: pins the order of the two IMPLICIT restrictions.
///
/// No Boast card in the pool states its activation instruction outside reminder
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
---
source: crates/engine/src/parser/oracle_ir/snapshot_tests.rs
expression: "&ir"
---
{
"items": [
{
"id": 0,
"source": {
"id": {
"item": 0,
"ordinal": 0
},
"span": {
"first_line": 0,
"last_line": 0,
"start_byte": 0,
"end_byte": 157,
"precision": "Exact",
"ordinal_within_span": 0
},
"fragment": "Creatures can't attack planeswalkers you control unless their controller pays {1} for each creature they control that's attacking a planeswalker you control."
},
"node": {
"Static": {
"definition": {
"mode": "CantAttack",
"affected": {
"type": "Typed",
"type_filters": [
"Creature"
],
"controller": "Opponent",
"properties": []
},
"modifications": [],
"condition": {
"type": "UnlessPay",
"cost": {
"type": "Cost",
"shards": [],
"generic": 1
},
"scaling": {
"type": "PerAffectedCreature"
},
"defended": "Planeswalker"
},
"affected_zone": null,
"effect_zone": null,
"active_zones": [],
"characteristic_defining": false,
"description": "Creatures can't attack planeswalkers you control unless their controller pays {1} for each creature they control that's attacking a planeswalker you control."
},
"source_text": "Creatures can't attack planeswalkers you control unless their controller pays {1} for each creature they control that's attacking a planeswalker you control.",
"body_ir": null
}
}
},
{
"id": 1,
"source": {
"id": {
"item": 1,
"ordinal": 0
},
"span": {
"first_line": 1,
"last_line": 1,
"start_byte": 158,
"end_byte": 277,
"precision": "Exact",
"ordinal_within_span": 0
},
"fragment": "{4}{W}{W}, Exile this card from your graveyard: Return target planeswalker card from your graveyard to the battlefield."
},
"node": {
"Spell": {
"source_text": "Return target planeswalker card from your graveyard to the battlefield",
"body": {
"clauses": [
{
"id": 0,
"source": {
"id": {
"item": 0,
"ordinal": 1
},
"span": {
"first_line": 0,
"last_line": 0,
"start_byte": 0,
"end_byte": 70,
"precision": "ChainRelative",
"ordinal_within_span": 0
},
"fragment": "Return target planeswalker card from your graveyard to the battlefield"
},
"disposition": {
"Emit": {
"followup": null,
"intrinsic": null
}
},
"parsed": {
"effect": {
"type": "ChangeZone",
"origin": "Graveyard",
"destination": "Battlefield",
"target": {
"type": "Typed",
"type_filters": [
"Planeswalker"
],
"controller": "You",
"properties": [
{
"type": "InZone",
"zone": "Graveyard"
}
]
},
"owner_library": false,
"enter_transformed": false,
"enter_tapped": false,
"enters_attacking": false
},
"duration": null,
"sub_ability": null,
"distribute": null,
"multi_target": null,
"condition": null,
"optional": false,
"unless_pay": null
},
"boundary": null,
"condition": null,
"is_optional": false,
"opponent_may_scope": null,
"repeat_for": null,
"player_scope": null,
"starting_with": null,
"delayed_condition": null,
"prefix_delayed_condition": null,
"multi_target": null,
"where_x_expression": null,
"unless_pay": null
}
],
"kind": "Activated",
"continuation_kind": null,
"player_scope_rewrite": "Apply",
"chain_rounding": null,
"actor": null,
"in_trigger": false,
"repeat_until": null
},
"shell": {
"sub_link": null,
"cost": {
"type": "Composite",
"costs": [
{
"type": "Mana",
"cost": {
"type": "Cost",
"shards": [
"White",
"White"
],
"generic": 4
}
},
{
"type": "Exile",
"count": 1,
"zone": "Graveyard",
"filter": {
"type": "SelfRef"
}
}
]
},
"activation_zone": "Graveyard",
"description": "{4}{W}{W}, Exile this card from your graveyard: Return target planeswalker card from your graveyard to the battlefield.",
"stages": [
"NormalizeActivatedManaInstead",
"ExtractCostReduction",
"ExtractManaSpendTrigger"
]
},
"die_results": [],
"root_transforms": []
}
}
}
],
"source_text": "Creatures can't attack planeswalkers you control unless their controller pays {1} for each creature they control that's attacking a planeswalker you control.\n{4}{W}{W}, Exile this card from your graveyard: Return target planeswalker card from your graveyard to the battlefield.",
"card_name": "Onakke Oathkeeper"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
source: crates/engine/src/parser/oracle_ir/snapshot_tests.rs
expression: "&lowered"
---
{
"abilities": [
{
"kind": "Activated",
"effect": {
"type": "ChangeZone",
"origin": "Graveyard",
"destination": "Battlefield",
"target": {
"type": "Typed",
"type_filters": [
"Planeswalker"
],
"controller": "You",
"properties": [
{
"type": "InZone",
"zone": "Graveyard"
}
]
},
"owner_library": false,
"enter_transformed": false,
"enter_tapped": false,
"enters_attacking": false
},
"cost": {
"type": "Composite",
"costs": [
{
"type": "Mana",
"cost": {
"type": "Cost",
"shards": [
"White",
"White"
],
"generic": 4
}
},
{
"type": "Exile",
"count": 1,
"zone": "Graveyard",
"filter": {
"type": "SelfRef"
}
}
]
},
"sub_ability": null,
"duration": null,
"description": "{4}{W}{W}, Exile this card from your graveyard: Return target planeswalker card from your graveyard to the battlefield.",
"target_prompt": null,
"activation_zone": "Graveyard",
"condition": null,
"optional_targeting": false,
"optional": false,
"forward_result": false
}
],
"triggers": [],
"statics": [
{
"mode": "CantAttack",
"affected": {
"type": "Typed",
"type_filters": [
"Creature"
],
"controller": "Opponent",
"properties": []
},
"modifications": [],
"condition": {
"type": "UnlessPay",
"cost": {
"type": "Cost",
"shards": [],
"generic": 1
},
"scaling": {
"type": "PerAffectedCreature"
},
"defended": "Planeswalker"
},
"affected_zone": null,
"effect_zone": null,
"active_zones": [],
"characteristic_defining": false,
"description": "Creatures can't attack planeswalkers you control unless their controller pays {1} for each creature they control that's attacking a planeswalker you control."
}
],
"replacements": [],
"extractedKeywords": []
}
12 changes: 10 additions & 2 deletions crates/engine/src/parser/oracle_static/evasion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2398,7 +2398,8 @@ pub(crate) fn try_parse_ignore_landwalk_for_blocking(

/// CR 508.1d + CR 508.1h + CR 509.1c + CR 118.12a: Parse the combat-tax static family:
///
/// - "Creatures can't attack [you | you or planeswalkers you control] unless their
/// - "Creatures can't attack [you | planeswalkers you control | you or planeswalkers
/// you control] unless their
/// controller pays {N} [for each of those creatures][, where X is the number of
/// <filter>][.]"
/// - "Creatures can't block unless their controller pays {N} [for each of those
Expand Down Expand Up @@ -2544,7 +2545,7 @@ pub(crate) fn parse_crew_contribution_static(text: &str) -> Option<StaticDefinit
/// | "each creature with one or more counters on it " | "~ "
/// color := ("non")? ("white"|"blue"|"black"|"red"|"green")
/// restriction := "can't attack" | "can't block" | "can't attack or block"
/// scope := " you" | " you or planeswalkers you control"
/// scope := " you" | " planeswalkers you control" | " you or planeswalkers you control"
/// payer := "their controller pays " | "its controller pays " | "you pay "
/// suffix := " for each ..." dynamic_x?
/// dynamic_x := ", where x is the number of " <filter-phrase>
Expand Down Expand Up @@ -2639,6 +2640,10 @@ pub(crate) fn parse_combat_tax_body(input: &str) -> OracleResult<'_, CombatTaxPa
AttackTargetFilter::PlayerOrPlaneswalker,
tag_no_case::<_, _, OracleError<'_>>(" you or planeswalkers you control"),
),
value(
AttackTargetFilter::Planeswalker,
tag_no_case::<_, _, OracleError<'_>>(" planeswalkers you control"),
),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
value(
AttackTargetFilter::Player,
tag_no_case::<_, _, OracleError<'_>>(" you"),
Expand Down Expand Up @@ -2670,6 +2675,9 @@ pub(crate) fn parse_combat_tax_body(input: &str) -> OracleResult<'_, CombatTaxPa
tag_no_case::<_, _, OracleError<'_>>(
" for each creature they control that's attacking you or a planeswalker you control",
),
tag_no_case::<_, _, OracleError<'_>>(
" for each creature they control that's attacking a planeswalker you control",
),
tag_no_case::<_, _, OracleError<'_>>(
" for each creature they control that's attacking you",
),
Expand Down
Loading
Loading