use forge_foundation::mana::ManaAtom;
use super::{mana_atom_from_produced, EffectContext};
use crate::spellability::SpellAbility;
pub fn build_spell_ability(sa: &mut crate::spellability::SpellAbility) {
if !sa.uses_targeting() {
sa.is_mana_ability = true;
}
}
pub fn handle_special_mana(
ctx: &mut EffectContext,
sa: &crate::spellability::SpellAbility,
source_id: crate::ids::CardId,
player: crate::ids::PlayerId,
special: &str,
) -> Vec<String> {
resolve_special_mana(ctx, sa, source_id, player, special)
}
#[manabrew_engine_macros::spell_effect(ManaEffect)]
fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
let player = sa.activating_player;
let source_id = match sa.source {
Some(id) => id,
None => return,
};
let produced_ir = match sa.produced_ir() {
Some(ir) => ir,
None => return,
};
let produced = produced_ir.as_script_text();
if sa.ir.optional {
let _card_name = ctx.game.card(source_id).card_name.clone();
if !ctx.agents[player.index()].confirm_action(
player,
Some("ProduceMana"),
"Produce mana?",
&[],
Some(source_id),
sa.api,
) {
return;
}
}
let chooser = sa
.chooser()
.and_then(|d| super::resolve_defined_player(d, player, ctx.game))
.unwrap_or(player);
let restriction = sa.ir.restrict_valid.clone().map(|r| {
if r.contains("ChosenType") {
let chosen = ctx
.game
.card(source_id)
.chosen_type
.clone()
.unwrap_or_default();
r.replace("ChosenType", &chosen)
} else {
r
}
});
let adds_no_counter = sa.ir.adds_no_counter;
let adds_keywords = sa.ir.adds_keywords.clone();
let adds_keywords_valid = sa.ir.adds_keywords_valid.clone();
let adds_counters = sa.ir.adds_counters.clone();
let adds_counters_valid = sa.ir.adds_counters_valid.clone();
let triggers_when_spent = sa.ir.triggers_when_spent.clone();
let Some(produced_ir) = sa.produced_ir().cloned() else {
return;
};
if let Some(special) = produced_ir.special_kind() {
let mana_tokens = resolve_special_mana(ctx, sa, source_id, player, special);
if mana_tokens.is_empty() {
return;
}
let source_is_snow = ctx.game.card(source_id).type_line.is_snow();
for tok in &mana_tokens {
if let Some(atom) = mana_atom_from_produced(tok) {
let mut m = crate::mana::Mana::simple(atom);
m.source_card = Some(source_id);
m.is_snow = source_is_snow;
m.restriction = restriction.clone();
m.adds_no_counter = adds_no_counter;
m.adds_keywords = adds_keywords.clone();
m.adds_keywords_valid = adds_keywords_valid.clone();
m.adds_counters = adds_counters.clone();
m.adds_counters_valid = adds_counters_valid.clone();
m.triggers_when_spent = triggers_when_spent.clone();
ctx.mana_pools[player.index()].add_mana(m);
}
}
return;
}
let mana_string: Option<String> = if produced_ir.is_any_like() {
let available = [
"W".to_string(),
"U".to_string(),
"B".to_string(),
"R".to_string(),
"G".to_string(),
];
Some(available.first().cloned().unwrap_or("W".to_string()))
} else if produced_ir.is_choice_like() {
let options: Vec<&str> = if let Some(rest) = produced.strip_prefix("Combo ") {
rest.split_whitespace().collect()
} else {
produced.split(',').map(|s| s.trim()).collect()
};
let available: Vec<String> = options
.iter()
.filter_map(|name| {
let lower = name.to_lowercase();
match lower.as_str() {
"white" | "w" => Some("W".to_string()),
"blue" | "u" => Some("U".to_string()),
"black" | "b" => Some("B".to_string()),
"red" | "r" => Some("R".to_string()),
"green" | "g" => Some("G".to_string()),
"colorless" | "c" => Some("C".to_string()),
_ => mana_atom_from_produced(name).map(|_| name.to_string()),
}
})
.collect();
available
.first()
.cloned()
.or_else(|| Some(produced.to_string()))
} else if produced == "Chosen" {
let chosen = ctx
.game
.card(source_id)
.chosen_colors
.first()
.cloned()
.unwrap_or_else(|| "White".to_string());
let lower = chosen.to_lowercase();
match lower.as_str() {
"white" => Some("W".to_string()),
"blue" => Some("U".to_string()),
"black" => Some("B".to_string()),
"red" => Some("R".to_string()),
"green" => Some("G".to_string()),
_ => Some("C".to_string()),
}
} else {
Some(produced.to_string())
};
let mut final_mana = match mana_string {
Some(ms) => ms,
None => return,
};
let amount = super::resolve_numeric_svar(ctx.game, sa, "Amount", 1);
if amount <= 0 {
return;
}
let is_combo = produced_ir.is_choice_like();
if amount > 1 && is_combo {
let available: Vec<String> = if produced_ir.is_any_like() {
vec!["W", "U", "B", "R", "G"]
.into_iter()
.map(String::from)
.collect()
} else {
let options: Vec<&str> = if let Some(rest) = produced.strip_prefix("Combo ") {
rest.split_whitespace().collect()
} else {
produced.split(',').map(|s| s.trim()).collect()
};
options
.iter()
.filter_map(|name| {
let lower = name.to_lowercase();
match lower.as_str() {
"white" | "w" => Some("W".to_string()),
"blue" | "u" => Some("U".to_string()),
"black" | "b" => Some("B".to_string()),
"red" | "r" => Some("R".to_string()),
"green" | "g" => Some("G".to_string()),
"colorless" | "c" => Some("C".to_string()),
_ => None,
}
})
.collect()
};
let mut per_unit: Vec<String> = Vec::with_capacity(amount as usize);
for _ in 0..amount {
let pick = ctx.agents[chooser.index()]
.choose_color(chooser, &available)
.or_else(|| available.first().cloned())
.unwrap_or_else(|| "C".to_string());
per_unit.push(pick);
}
let _card_name = ctx.game.card(source_id).card_name.clone();
let _summary = ctx.agents[chooser.index()].specify_mana_combo(
chooser,
&per_unit,
amount as usize,
Some(source_id),
None,
);
final_mana = per_unit.join(" ");
} else if amount > 1 {
let base = final_mana.clone();
for _ in 1..amount {
final_mana.push(' ');
final_mana.push_str(&base);
}
}
{
use crate::replacement::replacement_handler::{apply_replacements, ReplacementEvent};
let mut event = ReplacementEvent::ProduceMana {
source: source_id,
activator: player,
mana: final_mana.clone(),
};
let result = apply_replacements(ctx.game, &mut event);
if result == crate::replacement::ReplacementResult::Updated {
if let ReplacementEvent::ProduceMana { mana: new_mana, .. } = event {
final_mana = new_mana;
}
}
}
let source_is_snow = ctx.game.card(source_id).type_line.is_snow();
for tok in final_mana.split_whitespace() {
if let Some(atom) = mana_atom_from_produced(tok) {
let mut m = crate::mana::Mana::simple(atom);
m.source_card = Some(source_id);
m.is_snow = source_is_snow;
m.restriction = restriction.clone();
m.adds_no_counter = adds_no_counter;
m.adds_keywords = adds_keywords.clone();
m.adds_keywords_valid = adds_keywords_valid.clone();
m.adds_counters = adds_counters.clone();
m.adds_counters_valid = adds_counters_valid.clone();
m.triggers_when_spent = triggers_when_spent.clone();
ctx.mana_pools[player.index()].add_mana(m);
}
}
}
pub fn resolve_special_mana(
ctx: &mut EffectContext,
sa: &SpellAbility,
source_id: crate::ids::CardId,
player: crate::ids::PlayerId,
special: &str,
) -> Vec<String> {
use forge_foundation::ZoneType;
if special.starts_with("EachColorAmong_Valid") {
let filter = special
.strip_prefix("EachColorAmong_Valid ")
.unwrap_or("Permanent.YouCtrl");
return each_color_among_valid(ctx.game, source_id, player, filter);
}
if special == "EachColorAmong_ExiledWith" {
let mut colors = Vec::new();
for card in ctx.game.cards.iter() {
if card.zone != ZoneType::Exile {
continue;
}
if !card.remembered_cards.contains(&source_id) {
continue;
}
for color in card.color.iter() {
let letter = color_to_letter(color.long_name());
if !letter.is_empty() && !colors.contains(&letter) {
colors.push(letter);
}
}
}
return colors;
}
if special.starts_with("EachColoredManaSymbol") {
if special.contains("Milled") {
let gy = ctx.game.cards_in_zone(ZoneType::Graveyard, player);
if let Some(&last_card_id) = gy.last() {
let milled = ctx.game.card(last_card_id);
let mut tokens = Vec::new();
for &shard in milled.mana_cost.shards() {
let atoms = shard.shard();
if (atoms & ManaAtom::WHITE) != 0 {
tokens.push("W".to_string());
}
if (atoms & ManaAtom::BLUE) != 0 {
tokens.push("U".to_string());
}
if (atoms & ManaAtom::BLACK) != 0 {
tokens.push("B".to_string());
}
if (atoms & ManaAtom::RED) != 0 {
tokens.push("R".to_string());
}
if (atoms & ManaAtom::GREEN) != 0 {
tokens.push("G".to_string());
}
}
return tokens;
}
}
return vec![];
}
if special == "DoubleManaInPool" {
let pool = &ctx.mana_pools[player.index()];
let mut tokens = Vec::new();
for &(atom, letter) in &[
(ManaAtom::WHITE, "W"),
(ManaAtom::BLUE, "U"),
(ManaAtom::BLACK, "B"),
(ManaAtom::RED, "R"),
(ManaAtom::GREEN, "G"),
(ManaAtom::COLORLESS, "C"),
] {
let count = pool.count_color(atom);
for _ in 0..count {
tokens.push(letter.to_string());
}
}
return tokens;
}
if special == "EnchantedManaCost" {
let source = ctx.game.card(source_id);
if let Some(attached_to) = source.attached_to {
let enchanted = ctx.game.card(attached_to);
let mut tokens = Vec::new();
for &shard in enchanted.mana_cost.shards() {
let atoms = shard.shard();
if (atoms & ManaAtom::WHITE) != 0 {
tokens.push("W".to_string());
}
if (atoms & ManaAtom::BLUE) != 0 {
tokens.push("U".to_string());
}
if (atoms & ManaAtom::BLACK) != 0 {
tokens.push("B".to_string());
}
if (atoms & ManaAtom::RED) != 0 {
tokens.push("R".to_string());
}
if (atoms & ManaAtom::GREEN) != 0 {
tokens.push("G".to_string());
}
}
let generic = enchanted.mana_cost.generic_cost();
for _ in 0..generic {
tokens.push("C".to_string());
}
return tokens;
}
return vec![];
}
if special.starts_with("LastNotedType") {
let source = ctx.game.card(source_id);
if let Some(noted) = source.chosen_colors.first() {
let letter = color_to_letter(¬ed.to_lowercase());
if !letter.is_empty() {
let amount = super::resolve_numeric_svar(ctx.game, sa, "Amount", 1);
let mut tokens = Vec::new();
for _ in 0..amount {
tokens.push(letter.clone());
}
return tokens;
}
}
return vec![];
}
vec![]
}
pub fn each_color_among_valid(
game: &crate::game::GameState,
source_id: crate::ids::CardId,
controller: crate::ids::PlayerId,
filter: &str,
) -> Vec<String> {
use forge_foundation::ZoneType;
let mut colors = Vec::new();
for card in game.cards.iter() {
if card.zone != ZoneType::Battlefield {
continue;
}
if !matches_simple_valid(card, filter, source_id, controller) {
continue;
}
for color in card.color.iter() {
let letter = color_to_letter(color.long_name());
if !letter.is_empty() && !colors.contains(&letter) {
colors.push(letter);
}
}
}
colors
}
pub fn available_special_mana_atoms(
game: &crate::game::GameState,
source_id: crate::ids::CardId,
controller: crate::ids::PlayerId,
special: &str,
) -> Vec<u16> {
let mut atoms = Vec::new();
if special.starts_with("EachColorAmong_Valid") {
let filter = special
.strip_prefix("EachColorAmong_Valid ")
.unwrap_or("Permanent.YouCtrl");
for letter in each_color_among_valid(game, source_id, controller, filter) {
if let Some(atom) = crate::mana::mana_atom_from_produced(&letter) {
atoms.push(atom);
}
}
}
atoms
}
fn matches_simple_valid(
card: &crate::card::Card,
filter: &str,
source_id: crate::ids::CardId,
controller: crate::ids::PlayerId,
) -> bool {
let parts: Vec<&str> = filter.split('.').collect();
let base = parts[0];
let type_ok = match base {
"Permanent" | "Card" => true,
"Creature" => card.is_creature(),
"Land" => card.type_line.is_land(),
"Artifact" => card.type_line.is_artifact(),
"Enchantment" => card.type_line.is_enchantment(),
_ => card.type_line.has_subtype(base),
};
if !type_ok {
return false;
}
for &qualifier in parts.iter().skip(1) {
for q in qualifier.split('+') {
match q {
"YouCtrl" => {
if card.controller != controller {
return false;
}
}
"OppCtrl" => {
if card.controller == controller {
return false;
}
}
"MonoColor" => {
if card.color.count_colors() != 1 {
return false;
}
}
"Self" => {
if card.id != source_id {
return false;
}
}
"Legendary" => {
if !card.type_line.is_legendary() {
return false;
}
}
_ => {}
}
}
}
true
}
fn color_to_letter(color_name: &str) -> String {
match color_name {
"white" => "W".to_string(),
"blue" => "U".to_string(),
"black" => "B".to_string(),
"red" => "R".to_string(),
"green" => "G".to_string(),
_ => String::new(),
}
}