Skip to main content

manabrew_engine/ability/effects/
ring_tempts_you_effect.rs

1//! RingTempts effect — The Ring tempts you (Lord of the Rings mechanic).
2//!
3//! Ported from Java's `RingTemptsYouEffect.java`.
4//! The Ring tempts you: Your Ring-bearer gains the next level of abilities.
5//! If you don't have a Ring-bearer, choose a creature you control.
6
7use forge_foundation::ZoneType;
8
9use super::EffectContext;
10use crate::ability::ability_factory::build_spell_ability;
11use crate::event::RunParams;
12use crate::game::GameState;
13use crate::ids::{CardId, PlayerId};
14use crate::player::player_factory_util::add_static_ability;
15use crate::spellability::SpellAbility;
16use crate::trigger::parse_trigger;
17use crate::trigger::TriggerHandler;
18use crate::trigger::TriggerType;
19
20/// Controller-change / leaves-play callback for the Ring-bearer.
21/// Mirrors the `GameCommand.run()` in Java `RingTemptsYouEffect` that clears
22/// the ring-bearer designation when the creature changes controller or
23/// leaves the battlefield.
24pub fn run(game: &mut crate::game::GameState, player: crate::ids::PlayerId) {
25    // If the ring-bearer is no longer controlled by this player or left play,
26    // clear the ring-bearer designation.
27    if let Some(bearer_id) = game.player(player).ring_bearer {
28        let bearer = game.card(bearer_id);
29        if bearer.zone != ZoneType::Battlefield || bearer.controller != player {
30            game.player_mut(player).ring_bearer = None;
31        }
32    }
33}
34
35/// Struct form of this effect so it can participate in the
36/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
37/// `RingTemptsYouEffect` class extending `SpellAbilityEffect`.
38#[manabrew_engine_macros::spell_effect(RingTemptsYouEffect)]
39fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
40    let controller = sa.activating_player;
41    let players = if let Some(def) = sa.defined_player() {
42        super::resolve_defined_players(def, controller, ctx.game)
43    } else {
44        vec![controller]
45    };
46
47    for pid in players {
48        ring_tempts(ctx, sa, pid);
49    }
50}
51
52fn ring_tempts(ctx: &mut EffectContext, _sa: &SpellAbility, player: PlayerId) {
53    crate::player::create_the_ring(ctx.game, player);
54
55    let current_level = ctx.game.player(player).ring_level;
56    if current_level < 4 {
57        ctx.game.player_ring_tempt(player);
58    }
59    sync_ring_effect(ctx.game, ctx.trigger_handler, player);
60
61    let creatures: Vec<CardId> = ctx
62        .game
63        .cards
64        .iter()
65        .filter(|c| {
66            c.zone == ZoneType::Battlefield
67                && c.controller == player
68                && c.type_line
69                    .core_types
70                    .iter()
71                    .any(|ct| matches!(ct, forge_foundation::CoreType::Creature))
72        })
73        .map(|c| c.id)
74        .collect();
75
76    let mut ring_bearer = None;
77    if !creatures.is_empty() {
78        ctx.agents[player.index()].snapshot_state(ctx.game, ctx.mana_pools);
79        if let Some(chosen) = ctx.agents[player.index()].choose_single_card_for_zone_change(
80            ctx.game,
81            player,
82            &creatures,
83            "Choose your Ring-bearer",
84            false,
85        ) {
86            ctx.game.player_set_ring_bearer(player, Some(chosen));
87            ring_bearer = Some(chosen);
88        }
89    }
90
91    ctx.trigger_handler.run_trigger(
92        TriggerType::RingTemptsYou,
93        RunParams {
94            player: Some(player),
95            card: ring_bearer,
96            ..Default::default()
97        },
98        false,
99    );
100}
101
102pub fn sync_ring_effect(
103    game: &mut GameState,
104    trigger_handler: &mut TriggerHandler,
105    player: PlayerId,
106) {
107    if game.player(player).ring_level <= 0 {
108        return;
109    }
110    crate::player::create_the_ring(game, player);
111    let level = game.player(player).ring_level.min(4);
112    for ring_level in 1..=level {
113        set_ring_level(game, trigger_handler, player, ring_level);
114    }
115}
116
117fn set_ring_level(
118    game: &mut GameState,
119    trigger_handler: &mut TriggerHandler,
120    player: PlayerId,
121    level: i32,
122) {
123    let Some(effect_id) = game.player(player).ring_effect_card else {
124        return;
125    };
126
127    let changed = match level {
128        1 => {
129            let effect = game.card_mut(effect_id);
130            if effect.static_abilities.len() >= 2 {
131                false
132            } else {
133                add_static_ability(
134                    effect,
135                    "Mode$ Continuous | EffectZone$ Command | Affected$ Card.YouCtrl+IsRingbearer | AddType$ Legendary | Description$ Your Ring-bearer is legendary.",
136                ) | add_static_ability(
137                    effect,
138                    "Mode$ CantBlockBy | EffectZone$ Command | ValidAttacker$ Card.YouCtrl+IsRingbearer | ValidBlockerRelative$ Creature.powerGTX | Description$ Your Ring-bearer can't be blocked by creatures with greater power.",
139                )
140            }
141        }
142        2 => add_ring_attack_trigger(game, effect_id, player),
143        _ => false,
144    };
145
146    if changed {
147        trigger_handler.unregister_active_triggers(effect_id);
148        trigger_handler.register_active_trigger(game, effect_id);
149    }
150}
151
152fn add_ring_attack_trigger(game: &mut GameState, effect_id: CardId, player: PlayerId) -> bool {
153    if game.card(effect_id).triggers.iter().any(|trigger| {
154        trigger
155            .description
156            .contains("Whenever your Ring-bearer attacks")
157    }) {
158        return false;
159    }
160
161    {
162        let effect = game.card_mut(effect_id);
163        effect.set_s_var(
164            "RingAttackDiscard",
165            "DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose",
166        );
167    }
168
169    let Some(mut trigger) = parse_trigger(
170        "Mode$ Attacks | ValidCard$ Card.YouCtrl+IsRingbearer | TriggerDescription$ Whenever your Ring-bearer attacks, draw a card, then discard a card. | TriggerZones$ Command",
171        &mut 0,
172    ) else {
173        return false;
174    };
175
176    let ability = build_spell_ability(
177        game,
178        effect_id,
179        "DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ RingAttackDiscard",
180        player,
181    );
182    trigger.bind_host_card_id(effect_id);
183    trigger.set_overriding_ability(ability);
184    game.card_mut(effect_id).add_trigger(trigger)
185}