using DominionBase.Currencies; using DominionBase.Enums; using DominionBase.Piles; using DominionBase.Players; using DominionBase.Properties; using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; namespace DominionBase.Cards.Cornucopia { public static class TypeClass { public static readonly Type BagOfGold = typeof(BagOfGold); public static readonly Type Diadem = typeof(Diadem); public static readonly Type Fairgrounds = typeof(Fairgrounds); public static readonly Type FarmingVillage = typeof(FarmingVillage); public static readonly Type Followers = typeof(Followers); public static readonly Type FortuneTeller = typeof(FortuneTeller); public static readonly Type Hamlet = typeof(Hamlet); public static readonly Type Harvest = typeof(Harvest); public static readonly Type HornOfPlenty = typeof(HornOfPlenty); public static readonly Type HorseTraders = typeof(HorseTraders); public static readonly Type HuntingParty = typeof(HuntingParty); public static readonly Type Jester = typeof(Jester); public static readonly Type Menagerie = typeof(Menagerie); public static readonly Type Princess = typeof(Princess); public static readonly Type PrizeSupply = typeof(PrizeSupply); public static readonly Type Remake = typeof(Remake); public static readonly Type Tournament = typeof(Tournament); public static readonly Type TrustySteed = typeof(TrustySteed); public static readonly Type YoungWitch = typeof(YoungWitch); public static readonly Type BaneMarker = typeof(BaneMarker); } public class BagOfGold : Card { public BagOfGold() : this(Edition.First) { } public BagOfGold(Edition edition) : base(Categories.Action | Categories.Prize, Source.Cornucopia, Location.Special, Traits.CardOrdering | Traits.PlusAction | Traits.Gainer, edition: edition) { BaseCost = new Cost(special: true); Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.Gain(player._Game.Table.Gold, this, DeckLocation.Deck, DeckPosition.Top); } } public class BaneMarker : Token { public BaneMarker() : base("B", "Bane") { } public override string Title => "This supply pile is a Bane pile and cards from here may be revealed to block gaining a Curse card from the Young Witch"; public override bool ActDefined => true; internal override void Act(Card card, TokenActionEventArgs e) { base.Act(card, e); var player = e.Actee; // Already been cancelled -- don't need to process this one if (e.Cancelled || !e.Actee.Hand.Contains(card.PhysicalCard) || e.HandledBy.Contains(card.Type)) return; // Bane token/card only protects against other attackers if (player == e.Actor) return; var choice = Choice.CreateYesNoChoice($"Reveal Bane card {card.Name} to block gaining a Curse card?", card, e.ActingCard, player, e); var result = player.MakeChoice(choice); if (result.Options[0] == Resource.Yes) { player.AddCardInto(DeckLocation.Revealed, e.Actee.RetrieveCardFrom(DeckLocation.Hand, card.PhysicalCard)); e.Cancelled = true; player.AddCardInto(DeckLocation.Hand, e.Actee.RetrieveCardFrom(DeckLocation.Revealed, card.PhysicalCard)); } e.HandledBy.Add(card.Type); } } public class Diadem : Card { public Diadem() : base(Categories.Treasure | Categories.Prize, Source.Cornucopia, Location.Special, Traits.PlusCoin | Traits.ConditionalBenefit) { BaseCost = new Cost(special: true); Benefit.Currency.Coin.Value = 2; } protected override bool AllowUndo => true; public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var benefit = new CardBenefit(); benefit.Currency += new Coin((int)player.Actions); player.ReceiveBenefit(this, benefit); } } public class Fairgrounds : Card { public Fairgrounds() : this(Edition.First) { } public Fairgrounds(Edition edition) : base(Categories.Victory, Source.Cornucopia, Location.Kingdom, Traits.VariableVPs, edition: edition) { BaseCost = new Cost(6); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { return base.ComputeVictoryPoints(player, collection) + 2 * (collection.OfType().GroupBy(card => card.Type).Count() / 5); } } public class FarmingVillage : Card { public FarmingVillage() : this(Edition.First) { } public FarmingVillage(Edition edition) : base(Categories.Action, Source.Cornucopia, Location.Kingdom, Traits.PlusAction | Traits.PlusMultipleActions | Traits.Discard | Traits.Cantrip, edition: edition) { BaseCost = new Cost(4); Benefit.Actions = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.BeginDrawing(); while (player.CanDraw) { player.Draw(DeckLocation.Revealed); var lastRevealed = player.Revealed.Last(); if (lastRevealed.Category.HasFlag(Categories.Action) || lastRevealed.Category.HasFlag(Categories.Treasure)) break; } player.EndDrawing(); var revealedActTreasCard = player.Revealed.LastOrDefault(c => c.Category.HasFlag(Categories.Action) || c.Category.HasFlag(Categories.Treasure)); if (revealedActTreasCard != null) player.AddCardToHand(player.RetrieveCardFrom(DeckLocation.Revealed, revealedActTreasCard)); player.DiscardRevealed(); } } public class Followers : Card { public Followers() : this(Edition.First) { } public Followers(Edition edition) : base(Categories.Action | Categories.Attack | Categories.Prize, Source.Cornucopia, Location.Special, Traits.PlusCurses | Traits.PlusCard | Traits.Gainer | Traits.Discard | Traits.Terminal | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(special: true); Benefit.Cards = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.Gain(player._Game.Table.Estate, this); var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); while (enumerator.MoveNext()) { var attackee = enumerator.Current; // Skip if the attack is blocked (Moat, Lighthouse, etc.) if (IsAttackBlocked[attackee]) continue; attackee.Gain(player._Game.Table.Curse, this); var choice = new Choice(Resource.DiscardDownTo3Cards, this, attackee.Hand, ChoiceOutcome.Discard, attackee, minimum: attackee.Hand.Count - 3, maximum: attackee.Hand.Count - 3); var result = attackee.MakeChoice(choice); attackee.Discard(DeckLocation.Hand, result.Cards); } } } public class FortuneTeller : Card { public FortuneTeller() : this(Edition.First) { } public FortuneTeller(Edition edition) : base(Categories.Action | Categories.Attack, Source.Cornucopia, Location.Kingdom, Traits.PlusCoin | Traits.Discard | Traits.Terminal, edition: edition) { BaseCost = new Cost(3); Benefit.Currency.Coin.Value = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); // Perform attack on every player var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); // skip active player while (enumerator.MoveNext()) { var attackee = enumerator.Current; // Skip if the attack is blocked (Moat, Lighthouse, etc.) if (IsAttackBlocked[attackee]) continue; attackee.BeginDrawing(); while (attackee.CanDraw) { attackee.Draw(DeckLocation.Revealed); var lastRevealed = attackee.Revealed.Last(); if (lastRevealed.Category.HasFlag(Categories.Victory) || lastRevealed.Category.HasFlag(Categories.Curse)) break; } attackee.EndDrawing(); var revealedVicCurseCard = attackee.Revealed.LastOrDefault(c => c.Category.HasFlag(Categories.Victory) || c.Category.HasFlag(Categories.Curse)); if (revealedVicCurseCard != null) attackee.AddCardToDeck(attackee.RetrieveCardFrom(DeckLocation.Revealed, revealedVicCurseCard), DeckPosition.Top); attackee.DiscardRevealed(); } } } public class Hamlet : Card { public Hamlet() : this(Edition.First) { } public Hamlet(Edition edition) : base(Categories.Action, Source.Cornucopia, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.PlusBuy | Traits.Discard | Traits.ConditionalBenefit | Traits.Cantrip, edition: edition) { BaseCost = new Cost(2); Benefit.Cards = 1; Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choiceAction = new Choice("You may discard a card for +1 Action.", this, player.Hand, ChoiceOutcome.Discard, player, minimum: 0); var resultAction = player.MakeChoice(choiceAction); if (resultAction.Cards.Any()) { player.Discard(DeckLocation.Hand, resultAction.Cards); player.ReceiveBenefit(this, new CardBenefit { Actions = 1 }); } var choiceBuy = new Choice("You may discard a card for +1 Buy.", this, player.Hand, ChoiceOutcome.Discard, player, minimum: 0); var resultBuy = player.MakeChoice(choiceBuy); if (resultBuy.Cards.Any()) { player.Discard(DeckLocation.Hand, resultBuy.Cards); player.ReceiveBenefit(this, new CardBenefit { Buys = 1 }); } } } public class Harvest : Card { public Harvest() : base(Categories.Action, Source.Cornucopia, Location.Kingdom, Traits.PlusCoin | Traits.Discard | Traits.Terminal | Traits.ConditionalBenefit) { BaseCost = new Cost(5); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var newCards = player.Draw(4, DeckLocation.Revealed); player.DiscardRevealed(); var benefit = new CardBenefit(); benefit.Currency += new Coin(newCards.GroupBy(card => card.Type).Count()); player.ReceiveBenefit(this, benefit); } } public class HornOfPlenty : Card { public HornOfPlenty() : this(Edition.First) { } public HornOfPlenty(Edition edition) : base(Categories.Treasure, Source.Cornucopia, Location.Kingdom, Traits.Gainer | Traits.Trasher, edition: edition) { BaseCost = new Cost(5); Benefit.Currency.Coin.Value = 0; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var types = new List(); foreach (var card in player.InPlay) { var t = card.Type; if (!types.Contains(t)) types.Add(t); } foreach (var card in player.SetAside) { var t = card.Type; if (!types.Contains(t)) types.Add(t); } var uniqueCardsInPlay = new Coin(types.Count); var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost <= uniqueCardsInPlay)); var choice = new Choice(Resource.GainCard, this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) { var topCard = result.Supply.TopCard; player.Gain(result.Supply, this); if (topCard.Category.HasFlag(Categories.Victory)) { player.RetrieveCardFrom(DeckLocation.InPlay, this); player.Trash(this, this); } } } } public class HorseTraders : Card { private IPlayer _turnStartedEventPlayer; private TurnStartedEventHandler _turnStartedEventHandler; private AttackedEventHandler _attackHandler; public HorseTraders() : this(Edition.First) { } public HorseTraders(Edition edition) : base(Categories.Action | Categories.Reaction, Source.Cornucopia, Location.Kingdom, Traits.ReactToAttack | Traits.Defense | Traits.PlusCoin | Traits.PlusBuy | Traits.Discard | Traits.Terminal, edition: edition) { BaseCost = new Cost(4); Benefit.Buys = 1; Benefit.Currency.Coin.Value = 3; Benefit.DiscardCards = 2; } public override void TearDown(IGame game) { base.TearDown(game); if (_turnStartedEventHandler != null && _turnStartedEventPlayer != null) _turnStartedEventPlayer.TurnStarted -= _turnStartedEventHandler; _turnStartedEventPlayer = null; _turnStartedEventHandler = null; } public override void AddedTo(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.AddedTo(location, player); switch (location) { case DeckLocation.Hand: if (_attackHandler != null) player.Attacked -= _attackHandler; _attackHandler = new AttackedEventHandler(Player_Attacked); player.Attacked += _attackHandler; break; case DeckLocation.SetAside: if (_turnStartedEventHandler != null) player.TurnStarted -= _turnStartedEventHandler; _turnStartedEventPlayer = player; _turnStartedEventHandler = new TurnStartedEventHandler(Player_TurnStarted); _turnStartedEventPlayer.TurnStarted += _turnStartedEventHandler; break; } } internal override void Player_Attacked(object sender, AttackedEventArgs e) { var player = sender as IPlayer; // Horse Traders only protects against other attackers if (player == e.Attacker) return; // Make sure it exists already if (player.Hand.Contains(PhysicalCard) && !e.Revealable.ContainsKey(Type)) e.Revealable[Type] = new AttackReaction(this, Resource.RevealCard.Replace("{card}", PhysicalCard.ToString()), Player_RevealHorseTraders); } internal void Player_RevealHorseTraders(IPlayer player, ref AttackedEventArgs e) { player.AddCardInto(DeckLocation.Revealed, player.RetrieveCardFrom(DeckLocation.Hand, PhysicalCard)); player.AddCardInto(DeckLocation.SetAside, player.RetrieveCardFrom(DeckLocation.Revealed, PhysicalCard)); // Attack isn't cancelled... it's just mitigated e.HandledBy.Add(Type); } private void Player_TurnStarted(object sender, TurnStartedEventArgs e) { var key = ToString(); if (!e.Resolvers.ContainsKey(key) && !e.HandledBy.Contains(this)) e.Resolvers[key] = new TurnStartedResolver(e.Player, this, Resource.ResolveCard.Replace("{card}", PhysicalCard.ToString()), Player_Action, true); } internal void Player_Action(IPlayer player, ref TurnStartedEventArgs e) { player.ReceiveBenefit(this, new CardBenefit { Cards = 1 }); player.AddCardToHand(player.RetrieveCardFrom(DeckLocation.SetAside, this)); if (_turnStartedEventHandler != null) e.Player.TurnStarted -= _turnStartedEventHandler; _turnStartedEventPlayer = null; _turnStartedEventHandler = null; e.HandledBy.Add(this); } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); if (_attackHandler != null) player.Attacked -= _attackHandler; _attackHandler = null; } } public class HuntingParty : Card { public HuntingParty() : this(Edition.First) { } public HuntingParty(Edition edition) : base(Categories.Action, Source.Cornucopia, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.Discard | Traits.Cantrip | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(5); Benefit.Cards = 1; Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.ReturnHand(player.RevealHand()); var foundUniqueCard = false; player.BeginDrawing(); while (player.CanDraw) { player.Draw(DeckLocation.Revealed); if (player.Hand[player.Revealed.Last().Type].Count == 0) { foundUniqueCard = true; break; } } player.EndDrawing(); if (foundUniqueCard && player.Revealed.Any()) player.AddCardToHand(player.RetrieveCardFrom(DeckLocation.Revealed, player.Revealed.Last())); player.DiscardRevealed(); } } public class Jester : Card { public Jester() : this(Edition.First) { } public Jester(Edition edition) : base(Categories.Action | Categories.Attack, Source.Cornucopia, Location.Kingdom, Traits.PlusCurses | Traits.PlusCoin | Traits.Gainer | Traits.Discard | Traits.Terminal, edition: edition) { BaseCost = new Cost(5); Benefit.Currency.Coin.Value = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); // Perform attack on every player var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); // skip active player while (enumerator.MoveNext()) { var attackee = enumerator.Current; // Skip if the attack is blocked (Moat, Lighthouse, etc.) if (IsAttackBlocked[attackee]) continue; if (attackee.CanDraw) { var card = attackee.Draw(DeckLocation.Revealed); attackee.DiscardRevealed(); if (card.Category.HasFlag(Categories.Victory)) { attackee.Gain(player._Game.Table.Curse, this); } else { ISupply supply = null; if (player._Game.Table.TableEntities.ContainsKey(card)) supply = (ISupply)player._Game.Table[card]; if (supply != null && supply.CanGain() && supply.TopCard.Name == card.Name) { var choice = new Choice($"Who should receive the copy of {card}?", this, card, new List { player.ToString(), attackee.ToString() }, player); var result = player.MakeChoice(choice); if (result.Options[0] == player.ToString()) player.Gain(supply, this); else attackee.Gain(supply, this); } } } } } } public class Menagerie : Card { public Menagerie() : this(Edition.First) { } public Menagerie(Edition edition) : base(Categories.Action, Source.Cornucopia, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.ConditionalBenefit | Traits.Cantrip | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(3); Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.ReturnHand(player.RevealHand()); var types = new List(); foreach (var card in player.Hand) { var t = card.Type; if (!types.Contains(t)) types.Add(t); } var benefit = new CardBenefit { Cards = player.Hand.Count == types.Count ? 3 : 1 }; player.ReceiveBenefit(this, benefit); } } public class Princess : Card { private CostComputeEventHandler _CostComputeEventHandler; public Princess() : base(Categories.Action | Categories.Prize, Source.Cornucopia, Location.Special, Traits.PlusBuy | Traits.ModifyCost | Traits.Terminal) { BaseCost = new Cost(special: true); Benefit.Buys = 1; } protected override bool AllowUndo => true; public override void AddedTo(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.AddedTo(location, player); if (location == DeckLocation.InPlay) { if (_CostComputeEventHandler != null) player._Game.CostCompute -= _CostComputeEventHandler; _CostComputeEventHandler = new CostComputeEventHandler(Player_PrincessInPlayArea); player._Game.CostCompute += _CostComputeEventHandler; player._Game.SendMessage(player, this, 2); } } private void Player_PrincessInPlayArea(object sender, CostComputeEventArgs e) { if (e.Card is ICard || e.Card is ISupply) e.Cost.Coin -= 2; } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); if (_CostComputeEventHandler != null) player._Game.CostCompute -= _CostComputeEventHandler; _CostComputeEventHandler = null; } } public class PrizeSupply : Card { public PrizeSupply() : base(Categories.Prize, Source.Cornucopia, Location.Invisible, edition: Edition.First) { } public static void SetupSupply(IGame game) { Contract.Requires(game != null, "game cannot be null"); if (!game.Table.SpecialPiles.ContainsKey(TypeClass.PrizeSupply)) { var supply = new Supply(game, game.Players, TypeClass.PrizeSupply); supply.FullSetup(); game.Table.SpecialPiles.Add(TypeClass.PrizeSupply, supply); } } public override void SetupSupply(IGame game, ISupply supply) { Contract.Requires(supply != null, "supply cannot be null"); base.SetupSupply(game, supply); supply.AddTo(new BagOfGold()); supply.AddTo(new Diadem()); supply.AddTo(new Followers()); supply.AddTo(new Princess()); supply.AddTo(new TrustySteed()); } } public class Remake : Card { public Remake() : this(Edition.First) { } public Remake(Edition edition) : base(Categories.Action, Source.Cornucopia, Location.Kingdom, Traits.DeckReduction | Traits.Gainer | Traits.Trasher | Traits.RemoveCurses | Traits.Terminal | Traits.TrashForBenefit | Traits.RemoveFromHand, edition: edition) { BaseCost = new Cost(4); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); for (var count = 0; count < 2; count++) { var choiceTrash = new Choice(Resource.ChooseACardToTrash, this, player.Hand, ChoiceOutcome.Trash, player); var resultTrash = player.MakeChoice(choiceTrash); player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); if (resultTrash.Cards.Any()) { var trashedCardCost = player._Game.ComputeCost(resultTrash.Cards[0]); var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost == trashedCardCost + new Coin(1))); var choice = new Choice(Resource.GainCard, this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) player.Gain(result.Supply, this); } } } } public class Tournament : Card { public Tournament() : base(Categories.Action, Source.Cornucopia, Location.Kingdom, Traits.IncludesExtraPiles | Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.Gainer | Traits.Discard | Traits.ConditionalBenefit, edition: Edition.First) { BaseCost = new Cost(4); Benefit.Actions = 1; } public override void SetupSupply(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); base.SetupSupply(game, supply); PrizeSupply.SetupSupply(game); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var playerRevealedProvince = false; var anyoneElseRevealedProvince = false; // Perform on every player (including you) var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); while (enumerator.MoveNext()) { var actor = enumerator.Current; if (actor.Hand[Universal.TypeClass.Province].Count == 0) continue; var showChoice = Choice.CreateYesNoChoice("Do you want to reveal a Province from your hand?", this, actor); var showResult = actor.MakeChoice(showChoice); if (showResult.Options[0] == Resource.Yes) { var shownProvince = actor.RetrieveCardFrom(DeckLocation.Hand, Universal.TypeClass.Province); actor.AddCardInto(DeckLocation.Revealed, shownProvince); if (actor == player) { playerRevealedProvince = true; } else { actor.AddCardInto(DeckLocation.Hand, actor.RetrieveCardFrom(DeckLocation.Revealed, shownProvince)); anyoneElseRevealedProvince = true; } } } if (playerRevealedProvince) { player.Discard(DeckLocation.Revealed); var prizes = (ISupply)player._Game.Table.SpecialPiles[TypeClass.PrizeSupply]; var isOptional = !((ISupply)player._Game.Table[Universal.TypeClass.Duchy]).Any() || !prizes.Any(); var cards = new CardCollection(prizes) { new Universal.Duchy() }; var prizeChoice = new Choice("Select a Prize or a Duchy", this, cards, ChoiceOutcome.Gain, player, minimum: isOptional ? 0 : 1); var prizeResult = player.MakeChoice(prizeChoice); if (prizeResult.Cards.Any()) { if (prizeResult.Cards[0] is Universal.Duchy) player.Gain(player._Game.Table.Duchy, this, DeckLocation.Deck, DeckPosition.Top); else player.Gain(prizes, prizeResult.Cards[0].Type, this, DeckLocation.Deck, DeckPosition.Top); } } if (!anyoneElseRevealedProvince) { var benefit = new CardBenefit { Cards = 1, Currency = new Currency(1) }; player.ReceiveBenefit(this, benefit); } } } public class TrustySteed : Card { public TrustySteed() : this(Edition.First) { } public TrustySteed(Edition edition) : base(Categories.Action | Categories.Prize, Source.Cornucopia, Location.Special, Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.PlusCoin | Traits.CardOrdering | Traits.Gainer | Traits.ConditionalBenefit | Traits.Cantrip | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(special: true); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice(Resource.ChooseTwo, this, this, new List { "+2Cards", "+2Actions", "+2", "Gain 4 Silvers & discard deck" }, player, minimum: 2, maximum: 2); var result = player.MakeChoice(choice); foreach (var option in result.Options) { var benefit = new CardBenefit(); if (option == "+2Cards") benefit.Cards = 2; if (option == "+2Actions") benefit.Actions += 2; if (option == "+2") benefit.Currency += new Coin(2); if (option == "Gain 4 Silvers & discard deck") { player.Gain(player._Game.Table.Silver, this, 4); player._Game.SendMessage(player, this); var cc = player.RetrieveCardsFrom(DeckLocation.Deck); player.AddCardsInto(DeckLocation.Discard, cc); } player.ReceiveBenefit(this, benefit); } } } public class YoungWitch : Card { public YoungWitch() : this(Edition.First) { } public YoungWitch(Edition edition) : base(Categories.Action | Categories.Attack, Source.Cornucopia, Location.Kingdom, Traits.PlusCurses | Traits.PlusCard | Traits.Discard | Traits.Terminal, edition: edition) { BaseCost = new Cost(4); Benefit.Cards = 2; } public override string SpecialPresetKey => "Bane"; public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); // discard 2 cards var choiceDiscard = new Choice(Resource.Discard2Cards, this, player.Hand, ChoiceOutcome.Discard, player, minimum: 2, maximum: 2); var resultDiscard = player.MakeChoice(choiceDiscard); player.Discard(DeckLocation.Hand, resultDiscard.Cards); var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); while (enumerator.MoveNext()) { var attackee = enumerator.Current; if (IsAttackBlocked[attackee]) continue; if (!attackee.TokenActOn(player, this)) continue; attackee.Gain(player._Game.Table.Curse, this); } } public override void SetupSupply(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); base.SetupSupply(game, supply); Card baneCard; try { if (game.Settings.Preset != null) { baneCard = game.Settings.Preset.CardCards[game.Settings.Preset.Cards.First(c => c.Type == Type)].OfType().ElementAt(0); } else { var shouldUseGameConstraints = true; var ywConstraints = new ConstraintCollection(); if (game.Settings.CardSettings.ContainsKey(Name)) { var ywSettings = game.Settings.CardSettings[Name]; shouldUseGameConstraints = (bool)ywSettings.CardSettingCollection[typeof(YoungWitch_UseGameConstraints)].Value; ywConstraints = (ConstraintCollection)ywSettings.CardSettingCollection[typeof(YoungWitch_Constraints)].Value; } // need to setup a bane supply pile here; randomly pick an unused supply card type of cost $2 or $3 from // the Kingdom cards, create a new supply pile of it, and mark it with a Bane token IList availableBaneCards = game.CardsAvailable.OfType().Where(c => c.BaseCost == new Cost(2) || c.BaseCost == new Cost(3)).Cast().ToList(); if (shouldUseGameConstraints) { // Skip all "Must Use" constraints var constraints = new ConstraintCollection(game.Settings.Constraints.Where(c => c.ConstraintType != ConstraintType.CardMustUse)); availableBaneCards = constraints.SelectCards(availableBaneCards, 1); } else availableBaneCards = ywConstraints.SelectCards(availableBaneCards, 1); baneCard = availableBaneCards.OfType().ElementAt(0); } } catch (ConstraintException ce) { throw new YoungWitchConstraintException($"Problem setting up Young Witch constraints: {ce.Message}"); } game.CardsAvailable.Remove(baneCard); game.Table.AddTableItem(game.Players, baneCard.Type); game.Table.TableEntities[baneCard].Setup(); game.Table.TableEntities[baneCard].SnapshotSetup(); game.Table.TableEntities[baneCard].AddToken(new BaneMarker()); } public override void CheckSetup(Preset preset, ITable table) { Contract.Requires(preset != null, "preset cannot be null"); Contract.Requires(table != null, "table cannot be null"); // We need to find out what the Bane card is, remove it from the Preset cards, and add it to our own CardCards in the Preset foreach (var supply in table.TableEntities.Values.OfType()) { if (supply.Tokens.Any(t => t is BaneMarker)) { // This is our supply! preset.Cards.Remove(preset.Cards.First(c => c.Type == supply.Type)); CheckSetup(preset, supply.Name, CreateInstance(supply.Type)); } } } public override void CheckSetup(Preset preset, string cardName, IRandomizable card) { Contract.Requires(preset != null, "preset cannot be null"); Contract.Requires(card != null, "card cannot be null"); base.CheckSetup(preset, cardName, card); preset.CardCards[this] = new List { card }; } public override List GetSerializingTypes() { return new List { typeof(YoungWitch_UseGameConstraints), typeof(YoungWitch_Constraints) }; } public override CardSettingCollection GenerateSettings() { var csc = new CardSettingCollection { new YoungWitch_UseGameConstraints { Value = false }, new YoungWitch_Constraints { Value = new ConstraintCollection() } }; return csc; } public override void FinalizeSettings(CardSettingCollection settings) { Contract.Requires(settings != null, "settings cannot be null"); (settings[typeof(YoungWitch_Constraints)].Value as ConstraintCollection).MaxCount = 1; } [Serializable] public class YoungWitch_UseGameConstraints : CardSetting { public override string Name => "UseGameConstraints"; public override string Text => "Use Game constraints instead of the ones listed below"; public override string Hint => "Use the defined Game constraints instead of the ones defined here"; public override Type Type => typeof(bool); } [Serializable] public class YoungWitch_Constraints : CardSetting { public override string Name => "Constraints"; public override string Hint => "Constraints to use for selecting a Bane card to use"; public override Type Type => typeof(ConstraintCollection); } } public class YoungWitchConstraintException : ConstraintException { public YoungWitchConstraintException() { } public YoungWitchConstraintException(string message) : base(message) { } public YoungWitchConstraintException(string message, Exception innerException) : base(message, innerException) { } internal YoungWitchConstraintException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } }