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.Intrigue2ndEdition { public static class TypeClass { public static readonly Type Baron = typeof(Baron); public static readonly Type Bridge = typeof(Bridge); public static readonly Type Conspirator = typeof(Conspirator); public static readonly Type Courtier = typeof(Courtier); public static readonly Type Courtyard = typeof(Courtyard); public static readonly Type Diplomat = typeof(Diplomat); public static readonly Type Ironworks = typeof(Ironworks); public static readonly Type Lurker = typeof(Lurker); public static readonly Type Masquerade = typeof(Masquerade); public static readonly Type Mill = typeof(Mill); public static readonly Type MiningVillage = typeof(MiningVillage); public static readonly Type Minion = typeof(Minion); public static readonly Type Nobles = typeof(Nobles); public static readonly Type Patrol = typeof(Patrol); public static readonly Type Pawn = typeof(Pawn); public static readonly Type Replace = typeof(Replace); public static readonly Type SecretPassage = typeof(SecretPassage); public static readonly Type ShantyTown = typeof(ShantyTown); public static readonly Type Steward = typeof(Steward); public static readonly Type Swindler = typeof(Swindler); public static readonly Type Torturer = typeof(Torturer); public static readonly Type TradingPost = typeof(TradingPost); public static readonly Type WishingWell = typeof(WishingWell); } public class Baron : Intrigue.Baron { public Baron() : base(Edition.Second) { } } public class Bridge : Intrigue.Bridge { public Bridge() : base(Edition.Second) { } } public class Conspirator : Intrigue.Conspirator { public Conspirator() : base(Edition.Second) { } } public class Courtier : Card { public Courtier() : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusAction | Traits.PlusBuy | Traits.PlusCoin | Traits.Gainer | Traits.ConditionalBenefit, edition: Edition.Second) { BaseCost = new Cost(5); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choiceCard = new Choice(Resource.RevealCardFromHand, this, player.Hand, ChoiceOutcome.Select, player); var resultCard = player.MakeChoice(choiceCard); var uniqueTypes = 0; if (resultCard.Cards.Count > 0) { player.AddCardInto(DeckLocation.Revealed, player.RetrieveCardFrom(DeckLocation.Hand, resultCard.Cards[0])); player.AddCardInto(DeckLocation.Hand, player.RetrieveCardFrom(DeckLocation.Revealed, resultCard.Cards[0])); uniqueTypes = (int)Math.Min(4, (resultCard.Cards[0].Category & ~Categories.Card).Count()); } var choice = new Choice($"Choose {uniqueTypes}:", this, this, new List() { "+1Action", "+1Buy", "+3", Resource.GainGold }, player, minimum: uniqueTypes, maximum: uniqueTypes); var result = player.MakeChoice(choice); var benefit = new CardBenefit(); if (result.Options.Contains("+1Action")) benefit.Actions = 1; if (result.Options.Contains("+1Buy")) benefit.Buys += 1; if (result.Options.Contains("+3")) benefit.Currency += new Coin(3); player.ReceiveBenefit(this, benefit); if (result.Options.Contains(Resource.GainGold)) player.Gain(player._Game.Table.Gold, this); } } public class Courtyard : Intrigue.Courtyard { public Courtyard() : base(Edition.Second) { } } public class Diplomat : Card { public Diplomat() : base( Categories.Action | Categories.Reaction, Source.Intrigue, Location.Kingdom, Traits.ReactToAttack | Traits.Defense | Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.ConditionalBenefit | Traits.Discard | Traits.NetCardDraw, edition: Edition.Second) { BaseCost = new Cost(4); Benefit.Cards = 2; } public override void AddedTo(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.AddedTo(location, player); if (location == DeckLocation.Hand) { player.Attacked -= Player_Attacked; player.Attacked += Player_Attacked; } } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.Hand.Count <= 5) player.ReceiveBenefit(this, new CardBenefit { Actions = 2 }); } internal override void Player_Attacked(object sender, AttackedEventArgs e) { var player = sender as IPlayer; // Diplomat only protects against other attackers if (player == e.Attacker) return; // You can only reveal Diplomat with 5 or more cards in hand if (player.Hand.Count < 5) return; // Only if our hand contains Diplomat and we don't already have Diplomat on the "stack" if (player.Hand.Contains(PhysicalCard) && !e.Revealable.ContainsKey(Type)) e.Revealable[Type] = new AttackReaction(this, Resource.RevealCard.Replace("{card}", PhysicalCard.ToString()), Player_RevealDiplomat); } internal void Player_RevealDiplomat(IPlayer player, ref AttackedEventArgs e) { player.AddCardInto(DeckLocation.Revealed, player.RetrieveCardFrom(DeckLocation.Hand, PhysicalCard)); player.AddCardInto(DeckLocation.Hand, player.RetrieveCardFrom(DeckLocation.Revealed, PhysicalCard)); player.DrawHand(2); var choice = new Choice("Choose 3 cards to discard.", this, player.Hand, ChoiceOutcome.Discard, player, minimum: 3, maximum: 3); var result = player.MakeChoice(choice); player.Discard(DeckLocation.Hand, result.Cards); e.HandledBy.Add(Type); } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); player.Attacked -= Player_Attacked; } } public class Ironworks : Intrigue.Ironworks { public Ironworks() : base(Edition.Second) { } } public class Lurker : Card { public Lurker() : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusAction | Traits.Trasher | Traits.Gainer, edition: Edition.Second) { BaseCost = new Cost(2); Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice(Resource.ChooseOne, this, new ICardBaseCollection() { this }, new List() { Resource.TrashActionFromSupply, Resource.GainActionFromTrash }, player); var result = player.MakeChoice(choice); if (result.Options.Contains(Resource.TrashActionFromSupply)) { var trashableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll( supply => supply.TopCard != null && supply.TopCard.Category.HasFlag(Categories.Action) )); var choiceTrash = new Choice(Resource.TrashActionFromSupply, this, trashableSupplies, ChoiceOutcome.Trash, player, false); var resultTrash = player.MakeChoice(choiceTrash); if (resultTrash.Supply != null) player.Trash(this, resultTrash.Supply); } else if (result.Options.Contains(Resource.GainActionFromTrash)) { var availableTrashCards = player._Game.Table.Trash.Where(c => c.Category.HasFlag(Categories.Action)); var choiceFromTrash = new Choice(Resource.GainActionFromTrash, this, availableTrashCards, ChoiceOutcome.Gain, player); var resultFromTrash = player.MakeChoice(choiceFromTrash); if (resultFromTrash.Cards.Any()) player.Gain(player._Game.Table.Trash, resultFromTrash.Cards[0], this); } } } public class Masquerade : Card { public Masquerade() : base( Categories.Action, Source.Intrigue, Location.Kingdom, Traits.DeckReduction | Traits.PlusCurses | Traits.PlusCard | Traits.Trasher | Traits.RemoveCurses | Traits.AffectOthers | Traits.Terminal | Traits.NetCardDraw | Traits.RemoveFromHand, edition: Edition.Second) { BaseCost = new Cost(3); Benefit.Cards = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var toPass = new List>(); var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); while (enumerator.MoveNext()) if (enumerator.Current.Hand.Count > 0) toPass.Add(new Tuple(enumerator.Current, null)); if (toPass.Count >= 2) { for (var index = 0; index < toPass.Count; index++) { var choosingPlayer = toPass[index].Item1; var receivingPlayer = toPass[(index + toPass.Count - 1) % toPass.Count].Item1; var choice = new Choice($"Choose a card to pass to {receivingPlayer}", this, choosingPlayer.Hand, ChoiceOutcome.Select, player); var result = choosingPlayer.MakeChoice(choice); if (result.Cards.Any()) { var passingCard = choosingPlayer.RetrieveCardFrom(DeckLocation.Hand, result.Cards[0]); choosingPlayer.Lose(passingCard); toPass[index] = new Tuple(choosingPlayer, passingCard); player._Game.SendMessage(choosingPlayer, player._Game.GetPlayerFromIndex(choosingPlayer, 1), this, result.Cards[0]); } } // Only get players that have selected a card to pass toPass = toPass.Where(tp => tp.Item2 != null).ToList(); for (var index = 0; index < toPass.Count; index++) { var fromRight = toPass[(index + toPass.Count - 1) % toPass.Count]; toPass[index].Item1.Receive(fromRight.Item1, fromRight.Item2, DeckLocation.Hand, DeckPosition.Automatic); } } var choiceTrash = new Choice(Resource.ChooseToTrashOptional, this, player.Hand, ChoiceOutcome.Trash, player, minimum: 0); var resultTrash = player.MakeChoice(choiceTrash); if (resultTrash.Cards.Any()) player.Trash(this, player.RetrieveCardFrom(DeckLocation.Hand, resultTrash.Cards[0])); } } public class Mill : Card { public Mill() : base(Categories.Action | Categories.Victory, Source.Intrigue, Location.Kingdom, Traits.Discard | Traits.PlusCard | Traits.PlusAction | Traits.ConditionalBenefit | Traits.Cantrip, edition: Edition.Second) { BaseCost = new Cost(4); Benefit.Actions = 1; Benefit.Cards = 1; VictoryPoints = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choiceYesNo = Choice.CreateYesNoChoice("You may discard 2 cards.", this, this, player, null); var resultYesNo = player.MakeChoice(choiceYesNo); if (resultYesNo.Options[0] == Resource.Yes) { var choice = new Choice(Resource.Discard2Cards, this, player.Hand, ChoiceOutcome.Discard, player, minimum: 2, maximum: 2); var result = player.MakeChoice(choice); player.Discard(DeckLocation.Hand, result.Cards); if (result.Cards.Count == 2) { var benefit = new CardBenefit() { Currency = new Currency(2) }; player.ReceiveBenefit(this, benefit); } } } } public class MiningVillage : Intrigue.MiningVillage { public MiningVillage() : base(Edition.Second) { } } public class Minion : Intrigue.Minion { public Minion() : base(Edition.Second) { } } public class Nobles : Intrigue.Nobles { public Nobles() : base(Edition.Second) { } } public class Patrol : Card { public Patrol() : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.CardOrdering | Traits.PlusCard | Traits.NetCardDraw, edition: Edition.Second) { BaseCost = new Cost(5); Benefit.Cards = 3; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.Draw(4, DeckLocation.Revealed); player.AddCardsToHand(player.RetrieveCardsFrom(DeckLocation.Revealed, c => c.Category.HasFlag(Categories.Victory) || c.Category.HasFlag(Categories.Curse))); var replaceChoice = new Choice(Resource.ChooseOrderToTopdeck, this, player.Revealed, ChoiceOutcome.Select, player, isOrdered: true, minimum: player.Revealed.Count, maximum: player.Revealed.Count); var replaceResult = player.MakeChoice(replaceChoice); player.RetrieveCardsFrom(DeckLocation.Revealed, replaceResult.Cards); player.AddCardsToDeck(replaceResult.Cards, DeckPosition.Top); } } public class Pawn : Intrigue.Pawn { public Pawn() : base(Edition.Second) { } } public class Replace : Card { public Replace() : base(Categories.Action | Categories.Attack, Source.Intrigue, Location.Kingdom, Traits.Gainer | Traits.Trasher | Traits.RemoveCurses | Traits.Terminal | Traits.TrashForBenefit | Traits.RemoveFromHand, edition: Edition.Second) { BaseCost = new Cost(5); } // TODO -- This needs to be refactored to handle situations where other on-gain effects (e.g. Villa) move the card // TODO -- and Replace loses track of it public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); 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(2)))); var choice = new Choice(Resource.GainCard, this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) { var location = DeckLocation.Discard; var position = DeckPosition.Automatic; var gainVictory = false; if (result.Supply.TopCard.Category.HasFlag(Categories.Action) || result.Supply.TopCard.Category.HasFlag(Categories.Treasure)) { location = DeckLocation.Deck; position = DeckPosition.Top; } if (result.Supply.TopCard.Category.HasFlag(Categories.Victory)) gainVictory = true; if (player.Gain(result.Supply, this, location, position)) { if (gainVictory) { 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); } } } } } } } public class SecretPassage : Card { public SecretPassage() : base(Categories.Action, Source.Intrigue, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.Cantrip | Traits.CardOrdering, edition: Edition.Second) { BaseCost = new Cost(4); Benefit.Actions = 1; Benefit.Cards = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choiceSelect = new Choice("Choose a card to put into your deck", this, player.Hand, ChoiceOutcome.Select, player); var resultSelect = player.MakeChoice(choiceSelect); if (resultSelect.Cards.Any()) { var card = player.RetrieveCardFrom(DeckLocation.Hand, resultSelect.Cards[0]); var placeCards = new CardCollection { card }; var deck = player.DrawPile.LookThrough(c => true); placeCards.AddRange(deck.Select(c => Universal.Utility.GenerateCardBack(c.CardBack))); var choicePlace = new Choice($"Place {card} anywhere in your deck", this, placeCards, ChoiceOutcome.Select, player, isOrdered: true, minimum: placeCards.Count, maximum: placeCards.Count, visibility: Visibility.Top); var resultPlace = player.MakeChoice(choicePlace); // Insert the card into the correct position (the cards come back reversed) var newIndex = resultPlace.Cards.Count - 1 - resultPlace.Cards.IndexOf(card); if (newIndex >= player.DrawPile.Count) player.DrawPile.Add(card); else player.DrawPile.Insert(newIndex, card); player.DrawPile.Refresh(player); player._Game.SendMessage(player, this, card); } } } public class ShantyTown : Intrigue.ShantyTown { public ShantyTown() : base(Edition.Second) { } } public class Steward : Intrigue.Steward { public Steward() : base(Edition.Second) { } } public class Swindler : Intrigue.Swindler { public Swindler() : base(Edition.Second) { } } public class Torturer : Intrigue.Torturer { public Torturer() : base(Edition.Second) { } } public class TradingPost : Intrigue.TradingPost { public TradingPost() : base(Edition.Second) { } } public class WishingWell : Intrigue.WishingWell { public WishingWell() : base(Edition.Second) { } } }