1use forge_foundation::{PhaseType, ZoneType};
2use serde::{Deserialize, Serialize};
3use std::collections::{BTreeMap, HashMap};
4
5use crate::ability::AbilityKey;
6use crate::agent::GameEntity;
7use crate::card::card_damage_map::CardDamageMap;
8use crate::card::card_zone_table::CardZoneTable;
9use crate::ids::{CardId, PlayerId};
10use strum_macros::Display;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ZoneChangeRecord {
17 pub origin: ZoneType,
18 pub destination: ZoneType,
19 pub card: CardId,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct CounterTableEntry {
24 pub source: Option<PlayerId>,
25 pub object_card: Option<CardId>,
26 pub object_player: Option<PlayerId>,
27 pub counters: BTreeMap<String, i32>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, Display)]
31#[allow(clippy::large_enum_variant)]
32pub enum AbilityValue {
33 Card(CardId),
34 Player(PlayerId),
35 Cards(Vec<CardId>),
36 Players(Vec<PlayerId>),
37 GameEntities(Vec<GameEntity>),
38 VoteMap(Vec<(String, Vec<PlayerId>)>),
39 SpellAbility(crate::spellability::SpellAbility),
40 CardZoneTable(CardZoneTable),
41 DamageMap(CardDamageMap),
42 CounterMap(BTreeMap<String, i32>),
43 String(String),
44 Int(i32),
45 Bool(bool),
46 Zone(ZoneType),
47 Phase(PhaseType),
48}
49
50impl Default for AbilityValue {
51 fn default() -> Self {
52 AbilityValue::String(String::new())
53 }
54}
55
56impl AbilityValue {
57 pub fn as_str(&self) -> &str {
58 match self {
59 AbilityValue::String(value) => value.as_str(),
60 _ => "",
61 }
62 }
63}
64
65impl std::ops::Deref for AbilityValue {
66 type Target = str;
67
68 fn deref(&self) -> &Self::Target {
69 self.as_str()
70 }
71}
72
73impl From<CardId> for AbilityValue {
74 fn from(value: CardId) -> Self {
75 AbilityValue::Card(value)
76 }
77}
78
79impl From<PlayerId> for AbilityValue {
80 fn from(value: PlayerId) -> Self {
81 AbilityValue::Player(value)
82 }
83}
84
85impl From<Vec<CardId>> for AbilityValue {
86 fn from(value: Vec<CardId>) -> Self {
87 AbilityValue::Cards(value)
88 }
89}
90
91impl From<Vec<PlayerId>> for AbilityValue {
92 fn from(value: Vec<PlayerId>) -> Self {
93 AbilityValue::Players(value)
94 }
95}
96
97impl From<Vec<(String, Vec<PlayerId>)>> for AbilityValue {
98 fn from(value: Vec<(String, Vec<PlayerId>)>) -> Self {
99 AbilityValue::VoteMap(value)
100 }
101}
102
103impl From<crate::spellability::SpellAbility> for AbilityValue {
104 fn from(value: crate::spellability::SpellAbility) -> Self {
105 AbilityValue::SpellAbility(value)
106 }
107}
108
109impl From<CardZoneTable> for AbilityValue {
110 fn from(value: CardZoneTable) -> Self {
111 AbilityValue::CardZoneTable(value)
112 }
113}
114
115impl From<CardDamageMap> for AbilityValue {
116 fn from(value: CardDamageMap) -> Self {
117 AbilityValue::DamageMap(value)
118 }
119}
120
121impl From<BTreeMap<String, i32>> for AbilityValue {
122 fn from(value: BTreeMap<String, i32>) -> Self {
123 AbilityValue::CounterMap(value)
124 }
125}
126
127impl From<String> for AbilityValue {
128 fn from(value: String) -> Self {
129 AbilityValue::String(value)
130 }
131}
132
133impl From<&str> for AbilityValue {
134 fn from(value: &str) -> Self {
135 AbilityValue::String(value.to_string())
136 }
137}
138
139impl From<&String> for AbilityValue {
140 fn from(value: &String) -> Self {
141 AbilityValue::String(value.clone())
142 }
143}
144
145impl From<i32> for AbilityValue {
146 fn from(value: i32) -> Self {
147 AbilityValue::Int(value)
148 }
149}
150
151impl From<bool> for AbilityValue {
152 fn from(value: bool) -> Self {
153 AbilityValue::Bool(value)
154 }
155}
156
157impl From<ZoneType> for AbilityValue {
158 fn from(value: ZoneType) -> Self {
159 AbilityValue::Zone(value)
160 }
161}
162
163impl From<PhaseType> for AbilityValue {
164 fn from(value: PhaseType) -> Self {
165 AbilityValue::Phase(value)
166 }
167}
168
169#[derive(Debug, Clone, Default, Serialize, Deserialize)]
173pub struct RunParams {
174 pub card: Option<CardId>,
175 pub card_lki: Option<CardId>,
176 pub cards: Option<Vec<CardId>>,
178 pub zone_changes: Option<Vec<ZoneChangeRecord>>,
180 pub change_zone_table: Option<CardZoneTable>,
182 pub origin: Option<ZoneType>,
183 pub destination: Option<ZoneType>,
184 pub destinations: Option<String>,
186 pub activator: Option<PlayerId>,
188 pub cause_player: Option<PlayerId>,
189 pub player: Option<PlayerId>,
190 pub phase: Option<PhaseType>,
191 pub damage_source: Option<CardId>,
192 pub damage_target_player: Option<PlayerId>,
193 pub damage_target_card: Option<CardId>,
194 pub target_player: Option<PlayerId>,
196 pub target_card: Option<CardId>,
197 pub damage_amount: Option<i32>,
198 pub is_combat_damage: Option<bool>,
199 pub first_time: Option<bool>,
201 pub fizzle: Option<bool>,
203 pub valiant: Option<bool>,
205 pub attacker: Option<CardId>,
206 pub attacked_player: Option<PlayerId>,
208 pub attacked_card: Option<CardId>,
209 pub other_attacker_ids: Option<Vec<CardId>>,
211 pub defenders_player_ids: Option<Vec<PlayerId>>,
213 pub defenders_card_ids: Option<Vec<CardId>>,
214 pub attacking_player: Option<PlayerId>,
216 pub defending_player: Option<PlayerId>,
217 pub spell_card: Option<CardId>,
218 pub spell_controller: Option<PlayerId>,
219 pub card2: Option<CardId>,
221 pub explored: Option<CardId>,
223 pub spell_ability: Option<crate::spellability::SpellAbility>,
225 pub source_sa: Option<crate::spellability::SpellAbility>,
227 pub ability_mana: Option<crate::spellability::SpellAbility>,
229 pub cause: Option<crate::spellability::SpellAbility>,
231 pub causer: Option<CardId>,
233 pub produced: Option<String>,
235 pub mode: Option<String>,
237 pub num: Option<i32>,
239 pub number: Option<i32>,
241 pub blocker: Option<CardId>,
244 pub blocked_attacker: Option<CardId>,
246 pub life_amount: Option<i32>,
248 pub counter_type: Option<String>,
250 pub counter_amount: Option<i32>,
252 pub attacker_ids: Option<Vec<CardId>>,
255 pub blocker_ids: Option<Vec<CardId>>,
257 pub original_controller: Option<PlayerId>,
259 pub mana_expend_amount: Option<i32>,
261 pub enlisted: Option<CardId>,
263 pub cause_card: Option<CardId>,
265 pub coin_flip_won: Option<bool>,
267 pub die_result: Option<i32>,
269 pub die_results: Option<Vec<i32>>,
271 pub natural_result: Option<i32>,
273 pub die_sides: Option<i32>,
275 pub num_attackers: Option<usize>,
277 pub exploited_card: Option<CardId>,
279 pub lki_p1p1_counters: Option<i32>,
282 pub lki_power: Option<i32>,
285 pub lki_toughness: Option<i32>,
288 pub cumulative_upkeep_paid: Option<bool>,
290 pub echo_paid: Option<bool>,
292 pub class_level: Option<i32>,
294 pub room_name: Option<String>,
296 pub crew_cards: Option<Vec<CardId>>,
298 pub championed_card: Option<CardId>,
300 pub source_card: Option<CardId>,
302 pub source_player: Option<PlayerId>,
304 pub object_card: Option<CardId>,
306 pub object_player: Option<PlayerId>,
308 pub counter_map: Option<BTreeMap<String, i32>>,
310 pub counter_table: Option<Vec<CounterTableEntry>>,
311 pub damage_map: Option<CardDamageMap>,
313 pub clash_won: Option<bool>,
315 pub card_state_name: Option<String>,
317 pub drawn_this_turn_snapshot: Option<i32>,
321 pub first_time_players: Option<Vec<PlayerId>>,
323 pub all_votes: Option<Vec<(String, Vec<PlayerId>)>>,
325 pub discarded_before: Option<Vec<CardId>>,
327 pub rolled_to_visit_attractions: Option<bool>,
329}
330
331impl RunParams {
332 pub fn add_common_trigger_objects(&self, sa: &mut crate::spellability::SpellAbility) {
333 if let Some(card_id) = self.card {
334 sa.set_triggering_object(crate::ability::AbilityKey::Card, card_id.0.to_string());
335 sa.set_triggering_object(crate::ability::AbilityKey::NewCard, card_id.0.to_string());
336 }
337 if let Some(card_id) = self.card_lki {
338 sa.set_triggering_object(crate::ability::AbilityKey::CardLKI, card_id.0.to_string());
339 }
340 if let Some(player_id) = self.activator.or(self.cause_player) {
341 sa.set_triggering_object(
342 crate::ability::AbilityKey::Activator,
343 player_id.0.to_string(),
344 );
345 }
346 if let Some(player_id) = self.player {
347 sa.set_triggering_object(crate::ability::AbilityKey::Player, player_id.0.to_string());
348 }
349 if let Some(player_id) = self.attacking_player {
350 sa.set_triggering_object(
351 crate::ability::AbilityKey::AttackingPlayer,
352 player_id.0.to_string(),
353 );
354 }
355 if let Some(player_id) = self.defending_player {
356 sa.set_triggering_object(
357 crate::ability::AbilityKey::DefendingPlayer,
358 player_id.0.to_string(),
359 );
360 }
361 if let Some(card_id) = self.causer.or(self.cause_card) {
362 sa.set_triggering_object(crate::ability::AbilityKey::Causer, card_id.0.to_string());
363 }
364 if let Some(card_id) = self.source_card.or(self.spell_card) {
365 sa.set_triggering_object(crate::ability::AbilityKey::Source, card_id.0.to_string());
366 }
367 if let Some(card_id) = self.attacker {
368 sa.set_triggering_object(crate::ability::AbilityKey::Attacker, card_id.0.to_string());
369 }
370 if let Some(card_id) = self.blocker {
371 sa.set_triggering_object(crate::ability::AbilityKey::Blocker, card_id.0.to_string());
372 }
373 if let Some(card_id) = self.attacked_card {
374 sa.set_triggering_object(crate::ability::AbilityKey::Attacked, card_id.0.to_string());
375 }
376 if let Some(player_id) = self.attacked_player {
377 sa.set_triggering_object(
378 crate::ability::AbilityKey::AttackedTarget,
379 player_id.0.to_string(),
380 );
381 }
382 if let Some(card_id) = self.target_card {
383 let value = card_id.0.to_string();
384 sa.set_triggering_object(crate::ability::AbilityKey::Target, &value);
385 sa.set_triggering_object(crate::ability::AbilityKey::TargetCard, &value);
386 }
387 if let Some(player_id) = self.target_player {
388 let value = player_id.0.to_string();
389 sa.set_triggering_object(crate::ability::AbilityKey::Target, &value);
390 sa.set_triggering_object(crate::ability::AbilityKey::TargetPlayer, &value);
391 }
392 if self.target_player.is_none() {
393 if let Some(player_id) = self.damage_target_player {
394 let value = player_id.0.to_string();
395 sa.set_triggering_object(crate::ability::AbilityKey::Target, &value);
396 sa.set_triggering_object(crate::ability::AbilityKey::TargetPlayer, &value);
397 }
398 }
399 if self.target_card.is_none() {
400 if let Some(card_id) = self.damage_target_card {
401 let value = card_id.0.to_string();
402 sa.set_triggering_object(crate::ability::AbilityKey::Target, &value);
403 sa.set_triggering_object(crate::ability::AbilityKey::TargetCard, &value);
404 }
405 }
406 if let Some(card_id) = self.explored {
407 sa.set_triggering_object(crate::ability::AbilityKey::Explored, card_id.0.to_string());
408 }
409 if let Some(cards) = self.cards.as_deref() {
410 let csv = cards
411 .iter()
412 .map(|card_id| card_id.0.to_string())
413 .collect::<Vec<_>>()
414 .join(",");
415 if !csv.is_empty() {
416 sa.set_triggering_object(crate::ability::AbilityKey::Cards, &csv);
417 }
418 }
419 if let Some(cards) = self.attacker_ids.as_deref() {
420 let csv = cards
421 .iter()
422 .map(|card_id| card_id.0.to_string())
423 .collect::<Vec<_>>()
424 .join(",");
425 if !csv.is_empty() {
426 sa.set_triggering_object(crate::ability::AbilityKey::Attackers, &csv);
427 }
428 }
429 if let Some(value) = self.life_amount {
430 sa.set_triggering_object(crate::ability::AbilityKey::LifeAmount, value.to_string());
431 }
432 if let Some(value) = self.natural_result {
433 sa.set_triggering_object(crate::ability::AbilityKey::NaturalResult, value.to_string());
434 }
435 if let Some(value) = self.card_state_name.as_deref() {
436 sa.set_triggering_object(crate::ability::AbilityKey::CardState, value);
437 }
438 if let Some(value) = self.room_name.as_deref() {
439 sa.set_triggering_object(crate::ability::AbilityKey::RoomName, value);
440 }
441 if let Some(value) = self.spell_ability.as_ref() {
442 sa.set_triggering_spell_ability("SpellAbility", value.clone());
443 }
444 if let Some(value) = self.source_sa.as_ref() {
445 sa.set_triggering_spell_ability("SourceSA", value.clone());
446 }
447 if let Some(value) = self.ability_mana.as_ref() {
448 sa.set_triggering_spell_ability("AbilityMana", value.clone());
449 }
450 if let Some(value) = self.cause.as_ref() {
451 sa.set_triggering_spell_ability("Cause", value.clone());
452 }
453 if let Some(results) = self.die_results.as_deref() {
454 let csv = results
455 .iter()
456 .map(i32::to_string)
457 .collect::<Vec<_>>()
458 .join(",");
459 if !csv.is_empty() {
460 sa.set_triggering_object(crate::ability::AbilityKey::Result, &csv);
461 }
462 } else if let Some(value) = self.die_result {
463 sa.set_triggering_object(crate::ability::AbilityKey::Result, value.to_string());
464 }
465 if let Some(value) = self.die_sides {
466 sa.set_triggering_object(crate::ability::AbilityKey::Sides, value.to_string());
467 }
468 if let Some(value) = self.number {
469 sa.set_triggering_object(crate::ability::AbilityKey::Number, value.to_string());
470 }
471 }
472
473 pub fn get_value(&self, key: AbilityKey) -> Option<AbilityValue> {
474 use AbilityKey::*;
475 match key {
476 AbilityMana => self.ability_mana.clone().map(AbilityValue::SpellAbility),
477 AllVotes => self.all_votes.clone().map(AbilityValue::VoteMap),
478 Activator => self
479 .activator
480 .or(self.cause_player)
481 .map(AbilityValue::Player),
482 Amount => self
483 .cards
484 .as_ref()
485 .map(|cards| AbilityValue::Int(cards.len() as i32))
486 .or_else(|| self.counter_amount.map(AbilityValue::Int))
487 .or_else(|| self.damage_amount.map(AbilityValue::Int))
488 .or_else(|| self.life_amount.map(AbilityValue::Int)),
489 Attacked => self
490 .attacked_card
491 .map(AbilityValue::Card)
492 .or_else(|| self.attacked_player.map(AbilityValue::Player)),
493 Attacker => self.attacker.map(AbilityValue::Card),
494 Attackers => self.attacker_ids.clone().map(AbilityValue::Cards),
495 AttackingPlayer => self
496 .attacking_player
497 .or(self.spell_controller)
498 .map(AbilityValue::Player),
499 Blocker => self.blocker.map(AbilityValue::Card),
500 Blockers => self.blocker_ids.clone().map(AbilityValue::Cards),
501 Card => self.card.map(AbilityValue::Card),
502 CardState => self.card_state_name.clone().map(AbilityValue::String),
503 Cards => self
504 .change_zone_table
505 .clone()
506 .map(AbilityValue::CardZoneTable)
507 .or_else(|| self.cards.clone().map(AbilityValue::Cards)),
508 CardLKI => self.card_lki.map(AbilityValue::Card),
509 Causer => self
510 .causer
511 .map(AbilityValue::Card)
512 .or_else(|| self.cause_card.map(AbilityValue::Card))
513 .or_else(|| {
514 self.cause
515 .as_ref()
516 .and_then(|sa| sa.source)
517 .map(AbilityValue::Card)
518 }),
519 Cause => self
520 .cause
521 .clone()
522 .map(AbilityValue::SpellAbility)
523 .or_else(|| self.cause_card.map(AbilityValue::Card))
524 .or_else(|| self.cards.clone().map(AbilityValue::Cards)),
525 Championed => self.championed_card.map(AbilityValue::Card),
526 ClassLevel => self.class_level.map(AbilityValue::Int),
527 CounterAmount | CounterNum | NewCounterAmount => {
528 self.counter_amount.map(AbilityValue::Int)
529 }
530 CounterMap => self.counter_map.clone().map(AbilityValue::CounterMap),
531 CounterType => self.counter_type.clone().map(AbilityValue::String),
532 Crew => self.crew_cards.clone().map(AbilityValue::Cards),
533 CumulativeUpkeepPaid => self.cumulative_upkeep_paid.map(AbilityValue::Bool),
534 DamageAmount | LifeGained | LifeAmount | Num | Number | PreventedAmount => self
535 .damage_amount
536 .or(self.life_amount)
537 .or(self.num)
538 .or(self.number)
539 .or(self.drawn_this_turn_snapshot)
540 .map(AbilityValue::Int),
541 DamageMap => self.damage_map.clone().map(AbilityValue::DamageMap),
542 DamageSource => self.damage_source.map(AbilityValue::Card),
543 DamageTarget | Target => self
544 .damage_target_card
545 .map(AbilityValue::Card)
546 .or_else(|| self.damage_target_player.map(AbilityValue::Player))
547 .or_else(|| self.target_card.map(AbilityValue::Card))
548 .or_else(|| self.target_player.map(AbilityValue::Player)),
549 Defenders => self
550 .defenders_card_ids
551 .clone()
552 .map(AbilityValue::Cards)
553 .or_else(|| self.defenders_player_ids.clone().map(AbilityValue::Players)),
554 DefendingPlayer => self.defending_player.map(AbilityValue::Player),
555 Destination => self
556 .destinations
557 .clone()
558 .map(AbilityValue::String)
559 .or_else(|| self.destination.map(AbilityValue::Zone)),
560 EchoPaid => self.echo_paid.map(AbilityValue::Bool),
561 Enlisted => self.enlisted.map(AbilityValue::Card),
562 Exploited => self.exploited_card.map(AbilityValue::Card),
563 Explored => self.explored.map(AbilityValue::Card),
564 Explorer => self.card.map(AbilityValue::Card),
565 FirstTime => self
566 .first_time_players
567 .clone()
568 .map(AbilityValue::Players)
569 .or_else(|| self.first_time.map(AbilityValue::Bool)),
570 Fizzle => self.fizzle.map(AbilityValue::Bool),
571 IsCombat | IsCombatDamage => self.is_combat_damage.map(AbilityValue::Bool),
572 LastStateBattlefield => self
573 .change_zone_table
574 .as_ref()
575 .map(|table| AbilityValue::Cards(table.last_state_battlefield().to_vec())),
576 LastStateGraveyard => self
577 .change_zone_table
578 .as_ref()
579 .map(|table| AbilityValue::Cards(table.last_state_graveyard().to_vec())),
580 Mana | Produced => self.produced.clone().map(AbilityValue::String),
581 Mode => self.mode.clone().map(AbilityValue::String),
582 NewCard => self.card2.map(AbilityValue::Card),
583 Object => self
584 .object_card
585 .map(AbilityValue::Card)
586 .or_else(|| self.object_player.map(AbilityValue::Player))
587 .or_else(|| self.card.map(AbilityValue::Card)),
588 OtherAttackers => self.other_attacker_ids.clone().map(AbilityValue::Cards),
589 Origin => self.origin.map(AbilityValue::Zone),
590 OriginalController => self.original_controller.map(AbilityValue::Player),
591 Phase => self.phase.map(AbilityValue::Phase),
592 Player => self.player.map(AbilityValue::Player),
593 Result | Won => self
594 .coin_flip_won
595 .map(AbilityValue::Bool)
596 .or_else(|| self.clash_won.map(AbilityValue::Bool))
597 .or_else(|| self.mode.clone().map(AbilityValue::String))
598 .or_else(|| self.die_result.map(AbilityValue::Int)),
599 NaturalResult => self.natural_result.map(AbilityValue::Int),
600 RoomName => self.room_name.clone().map(AbilityValue::String),
601 RolledToVisitAttractions => self.rolled_to_visit_attractions.map(AbilityValue::Bool),
602 Scheme => self.card.map(AbilityValue::Card),
603 Sides => self.die_sides.map(AbilityValue::Int),
604 Source => self
605 .source_card
606 .map(AbilityValue::Card)
607 .or_else(|| self.source_player.map(AbilityValue::Player))
608 .or_else(|| self.spell_card.map(AbilityValue::Card)),
609 SourceSA | SpellAbility | StackSa => self
610 .source_sa
611 .clone()
612 .or_else(|| self.spell_ability.clone())
613 .map(AbilityValue::SpellAbility),
614 Valiant => self.valiant.map(AbilityValue::Bool),
615 InternalTriggerTable => self
616 .change_zone_table
617 .clone()
618 .map(AbilityValue::CardZoneTable),
619 _ => None,
620 }
621 }
622
623 pub fn as_ability_map(&self) -> HashMap<AbilityKey, AbilityValue> {
624 crate::ability::ability_key::all_ability_keys()
625 .iter()
626 .filter_map(|key| self.get_value(*key).map(|value| (*key, value)))
627 .collect()
628 }
629
630 pub fn get(&self, key: AbilityKey) -> Option<AbilityValue> {
632 self.get_value(key)
633 }
634
635 pub fn get_card(&self, key: crate::ability::AbilityKey) -> Option<CardId> {
639 match self.get_value(key) {
640 Some(AbilityValue::Card(card)) => Some(card),
641 _ => None,
642 }
643 }
644
645 pub fn get_player(&self, key: crate::ability::AbilityKey) -> Option<PlayerId> {
647 match self.get_value(key) {
648 Some(AbilityValue::Player(player)) => Some(player),
649 _ => None,
650 }
651 }
652
653 pub fn get_amount(&self, key: crate::ability::AbilityKey) -> Option<i32> {
655 match self.get_value(key) {
656 Some(AbilityValue::Int(value)) => Some(value),
657 _ => None,
658 }
659 }
660
661 pub fn get_bool(&self, key: crate::ability::AbilityKey) -> Option<bool> {
663 use crate::ability::AbilityKey;
664 match key {
665 AbilityKey::IsCombatDamage => self.is_combat_damage,
666 AbilityKey::FirstTime => self.first_time,
667 AbilityKey::Valiant => self.valiant,
668 _ => None,
669 }
670 }
671
672 pub fn get_spell_ability(
674 &self,
675 key: crate::ability::AbilityKey,
676 ) -> Option<&crate::spellability::SpellAbility> {
677 use crate::ability::AbilityKey;
678 match key {
679 AbilityKey::SpellAbility => self.spell_ability.as_ref(),
680 AbilityKey::SourceSA => self.source_sa.as_ref(),
681 AbilityKey::AbilityMana => self.ability_mana.as_ref(),
682 AbilityKey::Cause => self.cause.as_ref(),
683 _ => None,
684 }
685 }
686
687 pub fn get_cards(&self, key: crate::ability::AbilityKey) -> Option<&[CardId]> {
689 use crate::ability::AbilityKey;
690 match key {
691 AbilityKey::Cards => self.cards.as_deref(),
692 AbilityKey::DamageTargets => self.cards.as_deref(),
693 AbilityKey::Attackers => self.attacker_ids.as_deref(),
694 AbilityKey::Blockers => self.blocker_ids.as_deref(),
695 AbilityKey::OtherAttackers => self.other_attacker_ids.as_deref(),
696 AbilityKey::Defenders => self.defenders_card_ids.as_deref(),
697 _ => None,
698 }
699 }
700
701 pub fn get_string(&self, key: crate::ability::AbilityKey) -> Option<&str> {
703 use crate::ability::AbilityKey;
704 match key {
705 AbilityKey::Produced => self.produced.as_deref(),
706 AbilityKey::Mode => self.mode.as_deref(),
707 _ => None,
708 }
709 }
710}