Skip to main content

manabrew_engine/ability/effects/
token_effect.rs

1use super::token_effect_base::{TokenCreateResult, TokenEffectBase, TOKEN_EFFECT_BASE};
2use super::EffectContext;
3use crate::card::card_zone_table::CardZoneTable;
4use crate::ids::PlayerId;
5use crate::spellability::SpellAbility;
6
7/// Struct form of this effect so it can participate in the
8/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
9/// `TokenEffect` class extending `TokenEffectBase`.
10#[manabrew_engine_macros::spell_effect(TokenEffect)]
11fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
12    // Create token creature(s) on the battlefield.
13    // Mirrors Java TokenEffect / TokenEffectBase.
14    let amount: usize = sa
15        .ir
16        .token_amount
17        .as_deref()
18        .map(|raw| super::resolve_numeric_value(ctx.game, sa, raw, 1))
19        .unwrap_or(1)
20        .max(0) as usize;
21
22    let token_owners = resolve_token_owners(ctx, sa);
23    if token_owners.is_empty() {
24        return;
25    }
26
27    let mut trigger_list = CardZoneTable::default();
28    let scripts = TOKEN_EFFECT_BASE.token_scripts(sa);
29    let result = if scripts.is_empty() {
30        let mut all_created = TokenCreateResult::default();
31        for owner in token_owners {
32            let template = TOKEN_EFFECT_BASE.build_inline_token(sa, owner);
33            let token_table = TOKEN_EFFECT_BASE.make_token_table_internal(owner, template, amount);
34            let created =
35                TOKEN_EFFECT_BASE.make_token_table(ctx, token_table, false, &mut trigger_list, sa);
36            all_created.created.extend(created.created);
37            all_created.combat_changed |= created.combat_changed;
38        }
39        all_created
40    } else {
41        TOKEN_EFFECT_BASE.make_token_table_from_scripts(
42            ctx,
43            &token_owners,
44            &scripts,
45            amount,
46            false,
47            &mut trigger_list,
48            sa,
49        )
50    };
51
52    if !result.created.is_empty() {
53        trigger_list.trigger_changes_zone_all(ctx.trigger_handler, ctx.game, Some(sa));
54    }
55}
56
57fn resolve_token_owners(ctx: &EffectContext, sa: &SpellAbility) -> Vec<PlayerId> {
58    // Java parity (`SpellAbilityEffect.getDefinedPlayersOrTargeted("TokenOwner")`):
59    // when an explicit `TokenOwner$` resolves to no players (e.g. `TargetedController`
60    // with no chosen target), no tokens are created. Only fall back to the
61    // activator when no `TokenOwner` is specified at all.
62    if let Some(defined) = sa.token_owner() {
63        return crate::ability::ability_utils::resolve_defined_players_with_sa(
64            defined,
65            sa,
66            sa.activating_player,
67            ctx.game,
68        );
69    }
70    vec![sa.activating_player]
71}