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.Hinterlands { public static class TypeClass { public static readonly Type BorderVillage = typeof(BorderVillage); public static readonly Type Cache = typeof(Cache); public static readonly Type Cartographer = typeof(Cartographer); public static readonly Type Crossroads = typeof(Crossroads); public static readonly Type Develop = typeof(Develop); public static readonly Type Duchess = typeof(Duchess); public static readonly Type Embassy = typeof(Embassy); public static readonly Type Farmland = typeof(Farmland); public static readonly Type FoolsGold = typeof(FoolsGold); public static readonly Type Haggler = typeof(Haggler); public static readonly Type Highway = typeof(Highway); public static readonly Type IllGottenGains = typeof(IllGottenGains); public static readonly Type Inn = typeof(Inn); public static readonly Type JackOfAllTrades = typeof(JackOfAllTrades); public static readonly Type Mandarin = typeof(Mandarin); public static readonly Type Margrave = typeof(Margrave); public static readonly Type NobleBrigand = typeof(NobleBrigand); public static readonly Type NomadCamp = typeof(NomadCamp); public static readonly Type Oasis = typeof(Oasis); public static readonly Type Oracle = typeof(Oracle); public static readonly Type Scheme = typeof(Scheme); public static readonly Type SilkRoad = typeof(SilkRoad); public static readonly Type SpiceMerchant = typeof(SpiceMerchant); public static readonly Type Stables = typeof(Stables); public static readonly Type Trader = typeof(Trader); public static readonly Type Tunnel = typeof(Tunnel); } public class BorderVillage : Card { public BorderVillage() : this(Edition.First) { } public BorderVillage(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusAction | Traits.PlusMultipleActions | Traits.Gainer | Traits.PlusCard | Traits.Cantrip, edition: edition) { BaseCost = new Cost(6); Benefit.Cards = 1; Benefit.Actions = 2; } public override void SetupCard(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.SetupCard(game); foreach (var player in game.Players) player.CardGained += Player_CardGained; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) player.CardGained -= Player_CardGained; } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = Type.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this)) return; e.Resolvers[key] = new CardGainResolver(player, this, "GainCheaper", Resource.GainCheaper, Player_GainBorderVillage, true); } internal void Player_GainBorderVillage(IPlayer player, ref Players.CardGainEventArgs e) { var myCost = e.Game.ComputeCost(this); var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost < myCost)); var choice = new Choice($"Gain a card costing less than {Name}", this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) player.Gain(result.Supply, this); e.HandledBy.Add(this); } } public class Cache : Card { public Cache() : this(Edition.First) { } public Cache(Edition edition) : base(Categories.Treasure, Source.Hinterlands, Location.Kingdom, Traits.Gainer | Traits.PlusCoin, edition: edition) { BaseCost = new Cost(5); Benefit.Currency.Coin.Value = 3; } protected override bool AllowUndo => true; public override void SetupCard(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.SetupCard(game); foreach (var player in game.Players) player.CardGained += Player_CardGained; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) player.CardGained -= Player_CardGained; } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = Type.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this) || !e.Game.Table.Copper.CanGain()) return; e.Resolvers[key] = new CardGainResolver(player, this, "GainCoppers", Resource.Gain2Coppers, Player_GainCache, true); } internal void Player_GainCache(IPlayer player, ref Players.CardGainEventArgs e) { player.Gain(player._Game.Table.Copper, this, 2); e.HandledBy.Add(this); } } public class Cartographer : Card { public Cartographer() : this(Edition.First) { } public Cartographer(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.CardOrdering | Traits.PlusCard | Traits.PlusAction | Traits.Discard | Traits.Cantrip, 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.Draw(4, DeckLocation.Private); var choiceDiscard = new Choice(Resource.ChooseDiscards, this, player.Private, ChoiceOutcome.Discard, player, minimum: 0, maximum: player.Private.Count); var resultDiscard = player.MakeChoice(choiceDiscard); player.Discard(DeckLocation.Private, resultDiscard.Cards); var replaceChoice = new Choice(Resource.ChooseOrderToTopdeck, this, player.Private, ChoiceOutcome.Select, player, isOrdered: true, minimum: player.Private.Count, maximum: player.Private.Count); var replaceResult = player.MakeChoice(replaceChoice); player.AddCardsToDeck(player.RetrieveCardsFrom(DeckLocation.Private, replaceResult.Cards), DeckPosition.Top); } } public class Crossroads : Card { public Crossroads() : this(Edition.First) { } public Crossroads(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.ConditionalBenefit | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(2); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.ReturnHand(player.RevealHand()); var plusActions = 0; if (player.CurrentTurn.CardsResolved.Count(c => c.LogicalCard.Name == "Crossroads") == 1) plusActions = 3; player.ReceiveBenefit(this, new CardBenefit { Cards = player.Hand[Categories.Victory].Count, Actions = plusActions }); } } public class Develop : Card { public Develop() : this(Edition.First) { } public Develop(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.DeckReduction | Traits.Gainer | Traits.Trasher | Traits.CardOrdering | Traits.RemoveCurses | Traits.Terminal | Traits.TrashForBenefit | Traits.RemoveFromHand, edition: edition) { BaseCost = new Cost(3); } 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 lessThanSupplies = player._Game.Table.TableEntities.FindAll( supply => supply.CanGain() && trashedCardCost.Coin > 0 && supply.CurrentCost == trashedCardCost - new Coin(1)); var greaterThanSupplies = player._Game.Table.TableEntities.FindAll( supply => supply.CanGain() && supply.CurrentCost == trashedCardCost + new Coin(1)); var gainableSupplies = new SupplyCollection(lessThanSupplies.Union(greaterThanSupplies)); var choice = new Choice("Gain a card to put on your deck", this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) { player.Gain(result.Supply, this, DeckLocation.Deck, DeckPosition.Top); // Because of cards like Wayfarer & Destrier, we need to check cost between each gain trashedCardCost = player._Game.ComputeCost(resultTrash.Cards[0]); if (lessThanSupplies.Values.Contains(result.Supply)) { gainableSupplies = new SupplyCollection( player._Game.Table.TableEntities.FindAll( supply => supply.CanGain() && supply.CurrentCost == trashedCardCost + new Coin(1))); } else if (greaterThanSupplies.Values.Contains(result.Supply)) { gainableSupplies = new SupplyCollection( player._Game.Table.TableEntities.FindAll( supply => supply.CanGain() && trashedCardCost.Coin > 0 && supply.CurrentCost == trashedCardCost - new Coin(1))); } choice = new Choice("Gain a card to put on your deck", this, gainableSupplies, ChoiceOutcome.Gain, player, false); result = player.MakeChoice(choice); if (result.Supply != null) player.Gain(result.Supply, this, DeckLocation.Deck, DeckPosition.Top); } } } } public class Duchess : Card { public Duchess() : this(Edition.First) { } public Duchess(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.CardOrdering | Traits.PlusCoin | Traits.Gainer | Traits.AffectOthers | Traits.Terminal, edition: edition) { BaseCost = new Cost(2); Benefit.Currency.Coin.Value = 2; } public override void SetupCard(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.SetupCard(game); foreach (var player in game.Players) player.CardGained += Player_CardGained; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) player.CardGained -= Player_CardGained; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); // Perform action on every player var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); while (enumerator.MoveNext()) { var actor = enumerator.Current; if (actor.CanDraw) { var card = actor.Draw(DeckLocation.Private); var choice = new Choice($"Do you want to discard {card.Name} or put it back on top?", this, card, new List { Resource.Discard, Resource.PutItBack }, actor); var result = actor.MakeChoice(choice); if (result.Options[0] == Resource.Discard) actor.Discard(DeckLocation.Private); else actor.AddCardsToDeck(actor.RetrieveCardsFrom(DeckLocation.Private), DeckPosition.Top); } } } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var key = Type.ToString(); // This is not the card you are looking for if (e.Card.Type != Universal.TypeClass.Duchy || ((ISupply)e.Game.Table[Type]).TopCard != this || e.HandledBy.Contains(Type) || !((ISupply)e.Game.Table[Type]).CanGain()) return; var player = sender as IPlayer; e.Resolvers[key] = new Players.CardGainResolver(player, this, "GainDuchess", "Gain Duchess", Player_GainDuchy, false); } internal void Player_GainDuchy(IPlayer player, ref Players.CardGainEventArgs e) { player.Gain((ISupply)player._Game.Table.TableEntities[Type], this); e.HandledBy.Add(Type); } } public class Embassy : Card { public Embassy() : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusCard | Traits.Gainer | Traits.Discard | Traits.AffectOthers | Traits.Terminal | Traits.NetCardDraw) { BaseCost = new Cost(5); Benefit.Cards = 5; } public override void SetupCard(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.SetupCard(game); foreach (var player in game.Players) player.CardGained += Player_CardGained; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) player.CardGained -= Player_CardGained; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice("Discard 3 cards.", this, player.Hand, ChoiceOutcome.Discard, player, minimum: 3, maximum: 3); var result = player.MakeChoice(choice); player.Discard(DeckLocation.Hand, result.Cards); } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var key = Type.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this) || !e.Game.Table.Silver.CanGain()) return; var player = sender as IPlayer; e.Resolvers[key] = new Players.CardGainResolver(player, this, "OthersGainSilver", "Other players gain a Silver", Player_GainEmbassy, true); } internal void Player_GainEmbassy(IPlayer player, ref Players.CardGainEventArgs e) { // Skip current player (they don't get the Silver) var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); while (enumerator.MoveNext()) enumerator.Current.Gain(player._Game.Table.Silver, this); e.HandledBy.Add(this); } } public class Farmland : Card { private readonly Dictionary _cardBoughtHandlers = new Dictionary(); public Farmland() : this(Edition.First) { } public Farmland(Edition edition) : base(Categories.Victory, Source.Hinterlands, Location.Kingdom, Traits.Gainer | Traits.Trasher | Traits.DeckReduction | Traits.RemoveCurses | Traits.ReactToBuy, edition: edition) { BaseCost = new Cost(6); VictoryPoints = 2; } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _cardBoughtHandlers.Keys) playerLoop.CardBought -= _cardBoughtHandlers[playerLoop]; _cardBoughtHandlers.Clear(); } public override void AddedToSupply(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); base.AddedToSupply(game, supply); ResetTriggers(game); } private void ResetTriggers(IGame game) { var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _cardBoughtHandlers[enumPlayers.Current] = new CardBoughtEventHandler(Player_CardBought); enumPlayers.Current.CardBought += _cardBoughtHandlers[enumPlayers.Current]; } } private void Player_CardBought(object sender, CardBuyEventArgs e) { // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(Type) || !e.Card.Type.IsSubclassOf(typeof(Card))) return; var player = sender as IPlayer; if (player.Hand.Any()) e.Resolvers[Type] = new CardBuyResolver(Owner, this, Resource.TrashACardFromYourHand, Player_BuyFarmland, true); } internal void Player_BuyFarmland(IPlayer player, ref CardBuyEventArgs e) { 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) player.Gain(result.Supply, this); } e.HandledBy.Add(Type); // Clear out the Event Triggers -- this only happens when its Gained, so we don't care any more foreach (var playerLoop in _cardBoughtHandlers.Keys) playerLoop.CardBought -= _cardBoughtHandlers[playerLoop]; _cardBoughtHandlers.Clear(); } } public class FoolsGold : Card { public FoolsGold() : this(Edition.First) { } public FoolsGold(Edition edition) : base(Categories.Treasure | Categories.Reaction, Source.Hinterlands, Location.Kingdom, Traits.PlusCoin | Traits.ReactToGain | Traits.Trasher | Traits.DeckReduction | Traits.ConditionalBenefit, edition: edition) { BaseCost = new Cost(2); Benefit.Currency.Coin.IsVariable = true; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) player.CardGained -= Player_CardGained; } 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(1); if (player.CurrentTurn.CardsResolved.Count(c => c.Type == Type) > 1) benefit.Currency += new Coin(3); player.ReceiveBenefit(this, benefit); } public override void AddedTo(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.AddedTo(location, player); if (location == DeckLocation.Hand) { var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); while (enumerator.MoveNext()) { enumerator.Current.CardGained -= Player_CardGained; if (enumerator.Current != player) enumerator.Current.CardGained += Player_CardGained; } } } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = Type.ToString(); // Already been cancelled -- don't need to process this one if (e.Cancelled || e.Resolvers.ContainsKey(key) || player == Owner || e.Card.Type != Universal.TypeClass.Province || e.HandledBy.Contains(this)) return; // This needs to be "Owner" & not "sender"/"player" because it's in reaction to // someone else gaining the Province e.Resolvers[key] = new CardGainResolver(Owner, this, "TrashCard", $"Trash {PhysicalCard}", Player_FoolsGold, false); } internal void Player_FoolsGold(IPlayer player, ref Players.CardGainEventArgs e) { if (player.Hand.Contains(PhysicalCard)) { player.AddCardInto(DeckLocation.Revealed, player.RetrieveCardFrom(DeckLocation.Hand, PhysicalCard)); player.Trash(this, player.RetrieveCardFrom(DeckLocation.Revealed, PhysicalCard)); player.Gain(player._Game.Table.Gold, this, DeckLocation.Deck, DeckPosition.Top); e.HandledBy.Add(this); } } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); foreach (var otherPlayer in player._Game.Players) otherPlayer.CardGained -= Player_CardGained; } } public class Haggler : Card { private CardBoughtEventHandler _CardBoughtHandler; public Haggler() : this(Edition.First) { } public Haggler(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusCoin | Traits.Gainer | Traits.Terminal | Traits.ReactToBuy, edition: edition) { BaseCost = new Cost(5); Benefit.Currency.Coin.Value = 2; } 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 (_CardBoughtHandler != null) player.CardBought -= _CardBoughtHandler; _CardBoughtHandler = new CardBoughtEventHandler(Player_CardBought); player.CardBought += _CardBoughtHandler; } } private void Player_CardBought(object sender, CardBuyEventArgs e) { // This is not the card you are looking for if (e.HandledBy.Contains(this) || e.Resolvers.ContainsKey(Type) || e.Card.BaseCost == new Cost() || !e.Card.Type.IsSubclassOf(typeof(Card))) return; var player = sender as IPlayer; e.Resolvers[Type] = new CardBuyResolver(Owner, this, "Gain a non-Victory card costing less", Player_BuyWithHaggler, true); } internal void Player_BuyWithHaggler(IPlayer player, ref CardBuyEventArgs e) { var costCard = e.Game.ComputeCost(e.Card); var gainableSupplies = new SupplyCollection( player._Game.Table.TableEntities.FindAll( supply => supply.CanGain() && !supply.TopCard.Category.HasFlag(Categories.Victory) && supply.CurrentCost < costCard)); var choice = new Choice($"Gain a non-Victory card costing less than {e.Card.Name}", this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) player.Gain(result.Supply, this); 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 (_CardBoughtHandler != null) player.CardBought -= _CardBoughtHandler; _CardBoughtHandler = null; } } public class Highway : Card { private CostComputeEventHandler _costComputeEventHandler; public Highway() : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.ModifyCost | Traits.Cantrip) { BaseCost = new Cost(5); Benefit.Cards = 1; Benefit.Actions = 1; } 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_HighwayInPlayArea); player._Game.CostCompute += _costComputeEventHandler; player._Game.SendMessage(player, this, 1); } } private void Player_HighwayInPlayArea(object sender, CostComputeEventArgs e) { if (e.Card is ICard || e.Card is ISupply) e.Cost.Coin -= 1; } 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 IllGottenGains : Card { public IllGottenGains() : this(Edition.First) { } public IllGottenGains(Edition edition) : base(Categories.Treasure, Source.Hinterlands, Location.Kingdom, Traits.Gainer | Traits.PlusCurses | Traits.PlusCoin | Traits.AffectOthers, edition: edition) { BaseCost = new Cost(5); Benefit.Currency.Coin.Value = 1; } public override void SetupCard(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.SetupCard(game); foreach (var player in game.Players) player.CardGained += Player_CardGained; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) player.CardGained -= Player_CardGained; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choicePlayer = Choice.CreateYesNoChoice("Do you want to gain a Copper into your hand?", this, player); var resultPlayer = player.MakeChoice(choicePlayer); if (resultPlayer.Options[0] == Resource.Yes) { player.Gain(player._Game.Table.Copper, this, DeckLocation.Hand); } } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = Type.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this) || !e.Game.Table.Curse.CanGain()) return; e.Resolvers[key] = new CardGainResolver(player, this, "OthersGainCurse", "Other players gain a Curse", Player_GainIllGottenGains, true); } internal void Player_GainIllGottenGains(IPlayer player, ref Players.CardGainEventArgs e) { // Skip current player (they don't get the Curse) var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); while (enumerator.MoveNext()) { enumerator.Current.Gain(player._Game.Table.Curse, this); } e.HandledBy.Add(this); } } public class Inn : Card { public Inn() : this(Edition.First) { } public Inn(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusAction | Traits.PlusMultipleActions | Traits.PlusCard | Traits.CardOrdering | Traits.Discard, edition: edition) { BaseCost = new Cost(5); Benefit.Cards = 2; Benefit.Actions = 2; } public override void SetupCard(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.SetupCard(game); foreach (var player in game.Players) player.CardGained += Player_CardGained; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) player.CardGained -= Player_CardGained; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); 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); } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = Type.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this)) return; e.Resolvers[key] = new CardGainResolver(player, this, "ResolveCard", Resource.ResolveCard.Replace("{card}", PhysicalCard.ToString()), Player_GainInn, true); } internal void Player_GainInn(IPlayer player, ref Players.CardGainEventArgs e) { var discardPileCards = player.DiscardPile.LookThrough(c => true); player._Game.SendMessage(player, this, "LookThroughDiscard", discardPileCards.ToArray()); var actionCards = player.DiscardPile.LookThrough(c => c.Category.HasFlag(Categories.Action)); var choice = new Choice("Choose cards to reveal and shuffle into your deck", this, actionCards, ChoiceOutcome.Select, player, minimum: 0, maximum: actionCards.Count); var result = player.MakeChoice(choice); // We lose track of Inn if we put it on top of the deck and shuffle if (result.Cards.Contains(this)) e.IsLostTrackOf = true; player.AddCardsInto(DeckLocation.Revealed, player.DiscardPile.Retrieve(player, c => result.Cards.Contains(c))); player.AddCardsToDeck(player.Revealed.Retrieve(player, c => result.Cards.Contains(c)), DeckPosition.Top); player.ShuffleDrawPile(); e.HandledBy.Add(this); } } public class JackOfAllTrades : Card { public JackOfAllTrades() : this(Edition.First) { } public JackOfAllTrades(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusCard | Traits.CardOrdering | Traits.Discard | Traits.Gainer | Traits.Trasher | Traits.RemoveCurses | Traits.Terminal, edition: edition) { BaseCost = new Cost(4); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); // Step 1 player.Gain(player._Game.Table.Silver, this); // Step 2 if (player.CanDraw) { var card = player.Draw(DeckLocation.Private); var choice = new Choice($"Do you want to discard {card.Name} or put it back on top?", this, card, new List() { Resource.Discard, Resource.PutItBack }, player); var result = player.MakeChoice(choice); if (result.Options[0] == Resource.Discard) player.Discard(DeckLocation.Private); else player.AddCardsToDeck(player.RetrieveCardsFrom(DeckLocation.Private), DeckPosition.Top); } // Step 3 while (player.Hand.Count < 5 && player.CanDraw) player.DrawHand(1); // Step 4 var choiceTrash = new Choice(Resource.ChooseToTrashOptional, this, player.Hand[c => !c.Category.HasFlag(Categories.Treasure)], ChoiceOutcome.Trash, player, minimum: 0); var resultTrash = player.MakeChoice(choiceTrash); player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); } } public class Mandarin : Card { public Mandarin() : this(Edition.First) { } public Mandarin(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusCoin | Traits.CardOrdering | Traits.Terminal, edition: edition) { BaseCost = new Cost(5); Benefit.Currency.Coin.Value = 3; } public override void SetupCard(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.SetupCard(game); foreach (var player in game.Players) player.CardGained += Player_CardGained; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) player.CardGained -= Player_CardGained; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var replaceChoice = new Choice(Resource.TopdeckCard, this, player.Hand, ChoiceOutcome.Select, player); var replaceResult = player.MakeChoice(replaceChoice); player.RetrieveCardsFrom(DeckLocation.Hand, replaceResult.Cards); player.AddCardsToDeck(replaceResult.Cards, DeckPosition.Top); } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = Type.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this) || player.InPlay[Categories.Treasure].Count == 0) return; e.Resolvers[key] = new CardGainResolver(player, this, "PutTreasuresOnDeck", "Put all Treasures on your deck", Player_GainMandarin, true); } internal void Player_GainMandarin(IPlayer player, ref Players.CardGainEventArgs e) { var cardsInPlay = new CardCollection(player.InPlay[Categories.Treasure]); cardsInPlay.AddRange(player.SetAside[Categories.Treasure]); var replaceChoice = new Choice("Choose order of Treasure cards to put back on your deck", this, cardsInPlay, ChoiceOutcome.Select, player, isOrdered: true, minimum: cardsInPlay.Count, maximum: cardsInPlay.Count); var replaceResult = player.MakeChoice(replaceChoice); player.RetrieveCardsFrom(DeckLocation.InPlay, c => replaceResult.Cards.Contains(c)); player.RetrieveCardsFrom(DeckLocation.SetAside, c => replaceResult.Cards.Contains(c)); player.AddCardsToDeck(replaceResult.Cards, DeckPosition.Top); e.HandledBy.Add(this); } } public class Margrave : Card { public Margrave() : base(Categories.Action | Categories.Attack, Source.Hinterlands, Location.Kingdom, Traits.PlusCard | Traits.PlusBuy | Traits.Discard | Traits.Terminal | Traits.NetCardDraw) { BaseCost = new Cost(5); Benefit.Cards = 3; Benefit.Buys = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); 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; if (attackee.CanDraw) attackee.Draw(DeckLocation.Hand); 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 NobleBrigand : Card { private readonly Dictionary _cardBoughtHandlers = new Dictionary(); public NobleBrigand() : this(Edition.First) { } public NobleBrigand(Edition edition) : base(Categories.Action | Categories.Attack, Source.Hinterlands, Location.Kingdom, Traits.PlusCoin | Traits.Gainer | Traits.Trasher | Traits.Discard | Traits.Terminal | Traits.ReactToBuy, edition: edition) { BaseCost = new Cost(4); Benefit.Currency.Coin.Value = 1; } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _cardBoughtHandlers.Keys) playerLoop.CardBought -= _cardBoughtHandlers[playerLoop]; _cardBoughtHandlers.Clear(); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); CardEffects(player); } public override void AddedToSupply(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); base.AddedToSupply(game, supply); ResetTriggers(game); } private void ResetTriggers(IGame game) { var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _cardBoughtHandlers[enumPlayers.Current] = new CardBoughtEventHandler(Player_CardBought); enumPlayers.Current.CardBought += _cardBoughtHandlers[enumPlayers.Current]; } } private void Player_CardBought(object sender, CardBuyEventArgs e) { // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(Type) || !e.Card.Type.IsSubclassOf(typeof(Card))) return; e.Resolvers[Type] = new CardBuyResolver(Owner, this, $"Perform {PhysicalCard.Name} attack", Player_BuyNobleBrigand, true); } internal void Player_BuyNobleBrigand(IPlayer player, ref CardBuyEventArgs e) { CardEffects(player); e.HandledBy.Add(Type); // Clear out the Event Triggers -- this only happens when its Gained, so we don't care any more foreach (var playerLoop in _cardBoughtHandlers.Keys) playerLoop.CardBought -= _cardBoughtHandlers[playerLoop]; _cardBoughtHandlers.Clear(); } private void CardEffects(IPlayer player) { // Perform attack on every player var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); var trashed = new CardCollection(); while (enumerator.MoveNext()) { var attackee = enumerator.Current; // Skip if the attack is blocked (Moat, Lighthouse, etc.) // The on-buy Attack can't be blocked, so make sure the player is in the dictionary first if (IsAttackBlocked.ContainsKey(attackee) && IsAttackBlocked[attackee]) continue; attackee.Draw(2, DeckLocation.Revealed); var gainCopper = attackee.Revealed[Categories.Treasure].Count == 0; var treasuresSilverGold = attackee.Revealed[c => c is Universal.Silver || c is Universal.Gold]; var choice = new Choice($"Choose a Treasure card of {attackee} to trash", this, treasuresSilverGold, ChoiceOutcome.Trash, attackee); var result = player.MakeChoice(choice); if (result.Cards.Any()) { var trashCard = attackee.RetrieveCardFrom(DeckLocation.Revealed, result.Cards[0]); attackee.Trash(this, trashCard); trashed.Add(trashCard); } attackee.DiscardRevealed(); if (gainCopper) attackee.Gain(player._Game.Table.Copper, this); } // Gain all trashed Silver & Golds foreach (var card in trashed) player.Gain(player._Game.Table.Trash, card, this); } } public class NomadCamp : Card { public NomadCamp() : this(Edition.First) { } public NomadCamp(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusBuy | Traits.PlusCoin | Traits.CardOrdering | Traits.Terminal, edition: edition) { BaseCost = new Cost(4); Benefit.Buys = 1; Benefit.Currency.Coin.Value = 2; } protected override bool AllowUndo => true; public override void SetupCard(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.SetupCard(game); foreach (var player in game.Players) player.CardGained += Player_CardGained; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) player.CardGained -= Player_CardGained; } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = Type.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this) || e.Cancelled) return; // Something else is already moving this card if (e.Location == DeckLocation.Deck) return; e.Resolvers[key] = new CardGainResolver(player, this, "PutCardIntoDeck", Resource.PutCardOnYourDeck.Replace("{card}", PhysicalCard.ToString()), Player_GainNomadCamp, true); } internal void Player_GainNomadCamp(IPlayer player, ref Players.CardGainEventArgs e) { e.Cancelled = true; e.Location = DeckLocation.Deck; e.Position = DeckPosition.Top; e.HandledBy.Add(this); } } public class Oasis : Card { public Oasis() : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.Discard) { BaseCost = new Cost(3); Benefit.Cards = 1; Benefit.Actions = 1; Benefit.Currency.Coin.Value = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice("Discard a card.", this, player.Hand, ChoiceOutcome.Discard, player); var result = player.MakeChoice(choice); player.Discard(DeckLocation.Hand, result.Cards); } } public class Oracle : Card { public Oracle() : this(Edition.First) { } public Oracle(Edition edition) : base(Categories.Action | Categories.Attack, Source.Hinterlands, Location.Kingdom, Traits.CardOrdering | Traits.PlusCard | Traits.Discard | Traits.Terminal | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(3); Benefit.Cards = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); // Perform attack on every player (including you) var enumerator = player._Game.GetPlayersStartingWithEnumerator(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 cards = new ICardBaseCollection(attackee.Draw(2, DeckLocation.Revealed)); var name = attackee == player ? "your" : $"{attackee.Name}'s"; var choice = new Choice( $"Do you want to discard {name} {string.Join(" and ", cards.Select(c => c.Name))} or put them back on top?", this, cards, new List { Resource.Discard, "Put them back" }, attackee); var result = player.MakeChoice(choice); if (result.Options[0] == Resource.Discard) attackee.DiscardRevealed(); else { var replaceChoice = new Choice(Resource.ChooseOrderToTopdeck, this, attackee.Revealed, ChoiceOutcome.Select, attackee, isOrdered: true, minimum: 2, maximum: 2); var replaceResult = attackee.MakeChoice(replaceChoice); attackee.RetrieveCardsFrom(DeckLocation.Revealed); attackee.AddCardsToDeck(replaceResult.Cards, DeckPosition.Top); } } } base.FollowInstructions(player); } } public class Scheme : Card { private IPlayer _turnEndedPlayer; private TurnEndedEventHandler _turnEndedEventHandler; private readonly List _cleaningUpEventHandlers = new List(); private static CardsDiscardingEventHandler _cardsDiscardingEventHandler; private static readonly CardCollection CardsToTopDeck = new CardCollection(); private static CleanedUpEventHandler _cleanedUpEventHandler; public Scheme() : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.CardOrdering | Traits.Cantrip, edition: Edition.First) { BaseCost = new Cost(3); Benefit.Cards = 1; Benefit.Actions = 1; } public override void TearDown(IGame game) { base.TearDown(game); if (_turnEndedEventHandler != null && _turnEndedPlayer != null) Player_TurnEnded(_turnEndedPlayer, new TurnEndedEventArgs(_turnEndedPlayer)); _turnEndedEventHandler = null; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (_turnEndedEventHandler == null) { _turnEndedPlayer = player; _turnEndedEventHandler = new TurnEndedEventHandler(Player_TurnEnded); player.TurnEnded += _turnEndedEventHandler; } _cleaningUpEventHandlers.Add(new CleaningUpEventHandler(Player_CleaningUp)); player.CleaningUp += _cleaningUpEventHandlers.Last(); } private void Player_CleaningUp(object sender, CleaningUpEventArgs e) { if (e.Resolvers.ContainsKey(Type)) { e.Resolvers[Type].Data = ((int)e.Resolvers[Type].Data) + 1; return; } // Check to see if Scheme has been resolved yet if (CardsToTopDeck.Count == 0) e.Resolvers[Type] = new CleaningUpResolver(this, $"Resolve {this}", Player_Action) { Data = 1 }; } internal void Player_Action(IPlayer player, ref CleaningUpEventArgs e) { if (_cleaningUpEventHandlers.Any()) { player.CleaningUp -= _cleaningUpEventHandlers[0]; _cleaningUpEventHandlers.RemoveAt(0); } var allInPlayCards = GetSelectableCards(e); if (allInPlayCards.Any()) { var schemeChoices = (int)e.Resolvers[Type].Data; var choice = new Choice( $"Select up to {schemeChoices} Action {Utilities.StringUtility.Plural("card", schemeChoices, false)} to place on top of your deck", this, allInPlayCards, ChoiceOutcome.Select, player, isSpecific: true, minimum: 0, maximum: schemeChoices); var result = player.MakeChoice(choice); CardsToTopDeck.Clear(); foreach (var cardToMove in result.Cards) CardsToTopDeck.Add(cardToMove); if (CardsToTopDeck.Any()) { _cardsDiscardingEventHandler = new CardsDiscardingEventHandler(Player_CardsDiscarding); e.CurrentPlayer.CardsDiscarding += _cardsDiscardingEventHandler; _cleanedUpEventHandler = new CleanedUpEventHandler(Player_CleanedUp); e.CurrentPlayer.CleanedUp += _cleanedUpEventHandler; } } } private void Player_CardsDiscarding(object sender, CardsDiscardEventArgs e) { var player = (IPlayer)sender; foreach (var card in CardsToTopDeck) { if (e.GetResolver(Type, card.Type) != null || player.ResolveDeck(e.FromLocation)[c => c == card].Count == 0 || !e.Cards.Contains(card)) continue; e.AddResolver(Type, card.Type, new CardsDiscardResolver(player, this, Resource.PutCardOnYourDeck.Replace("{card}", card.ToString()), Player_DiscardAction, true) { Data = card }); } } internal void Player_DiscardAction(IPlayer player, ref CardsDiscardEventArgs e) { var cardToTopDeck = e.Data as Card; e.Cards.Remove(cardToTopDeck); player.RetrieveCardFrom(player.InPlay.Contains(cardToTopDeck) ? DeckLocation.InPlay : DeckLocation.SetAside, cardToTopDeck); player.AddCardToDeck(cardToTopDeck, DeckPosition.Top); e.HandledBy.Add(cardToTopDeck); } private void Player_CleanedUp(object sender, CleanedUpEventArgs e) { if (_cardsDiscardingEventHandler != null) ((IPlayer)sender).CardsDiscarding -= _cardsDiscardingEventHandler; if (_cleanedUpEventHandler != null) ((IPlayer)sender).CleanedUp -= _cleanedUpEventHandler; CardsToTopDeck.Clear(); _cardsDiscardingEventHandler = null; _cleanedUpEventHandler = null; } private static CardCollection GetSelectableCards(CleaningUpEventArgs e) { var allInPlayCards = new CardCollection(); allInPlayCards.AddRange(e.CardsMovements.Where(cm => cm.Card.Category.HasFlag(Categories.Action) && cm.CurrentLocation == DeckLocation.InPlay && (cm.Destination == DeckLocation.SetAside || cm.Destination == DeckLocation.Discard)).Select(cm => cm.Card)); // This is used to separate the In Play from the Set Aside for the Choice.MakeChoice call allInPlayCards.Add(new Universal.Blank()); allInPlayCards.AddRange(e.CardsMovements.Where(cm => cm.Card.Category.HasFlag(Categories.Action) && cm.CurrentLocation == DeckLocation.SetAside && cm.Destination == DeckLocation.Discard).Select(cm => cm.Card)); if (allInPlayCards.FirstOrDefault() is Universal.Blank) allInPlayCards.RemoveAt(0); if (allInPlayCards.LastOrDefault() is Universal.Blank) allInPlayCards.RemoveAt(allInPlayCards.Count - 1); return allInPlayCards; } private void Player_TurnEnded(object sender, TurnEndedEventArgs e) { var player = sender as IPlayer; if (_turnEndedEventHandler != null && _turnEndedPlayer != null) _turnEndedPlayer.TurnEnded -= _turnEndedEventHandler; _turnEndedPlayer = null; _turnEndedEventHandler = null; foreach (var cueh in _cleaningUpEventHandlers) player.CleaningUp -= cueh; _cleaningUpEventHandlers.Clear(); } } public class SilkRoad : Card { public SilkRoad() : this(Edition.First) { } public SilkRoad(Edition edition) : base(Categories.Victory, Source.Hinterlands, Location.Kingdom, Traits.VariableVPs, edition: edition) { BaseCost = new Cost(4); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { return base.ComputeVictoryPoints(player, collection) + collection.Count(c => c.Category.HasFlag(Categories.Victory)) / 4; } } public class SpiceMerchant : Card { public SpiceMerchant() : this(Edition.First) { } public SpiceMerchant(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.DeckReduction | Traits.PlusCard | Traits.PlusAction | Traits.Trasher | Traits.PlusCoin | Traits.PlusBuy | Traits.ConditionalBenefit, edition: edition) { BaseCost = new Cost(4); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice("You may choose a Treasure card to trash", this, player.Hand[Categories.Treasure], ChoiceOutcome.Trash, player, minimum: 0); var result = player.MakeChoice(choice); if (result.Cards.Any()) { player.Trash(this, player.RetrieveCardFrom(DeckLocation.Hand, result.Cards[0])); var benefit = new CardBenefit(); var choiceBenefit = new Choice("Choose either +2 Cards and +1 Action; or +2 and +1 Buy", this, this, new List { "+2Cards and +1Action", "+2 and +1Buy" }, player); var resultBenefit = player.MakeChoice(choiceBenefit); if (resultBenefit.Options[0] == "+2Cards and +1Action") { benefit.Cards = 2; benefit.Actions = 1; } else { benefit.Currency.Coin.Value = 2; benefit.Buys = 1; } player.ReceiveBenefit(this, benefit); } } } public class Stables : Card { public Stables() : this(Edition.First) { } public Stables(Edition edition) : base(Categories.Action, Source.Hinterlands, Location.Kingdom, Traits.Discard | Traits.PlusCard | Traits.PlusAction | Traits.ConditionalBenefit | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(5); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice("You may choose a Treasure card to discard", this, player.Hand[Categories.Treasure], ChoiceOutcome.Discard, player, minimum: 0); var result = player.MakeChoice(choice); if (result.Cards.Any()) { player.Discard(DeckLocation.Hand, result.Cards[0]); var benefit = new CardBenefit { Cards = 3, Actions = 1 }; player.ReceiveBenefit(this, benefit); } } } public class Trader : Card { private CardGainingEventHandler _CardGainingHandler; public Trader() : this(Edition.First) { } public Trader(Edition edition) : base(Categories.Action | Categories.Reaction, Source.Hinterlands, Location.Kingdom, Traits.DeckReduction | Traits.Gainer | Traits.ReactToGain | Traits.Trasher | Traits.RemoveCurses | Traits.Defense | 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); 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()) { player.Gain(player._Game.Table.Silver, this, player._Game.ComputeCost(resultTrash.Cards[0]).Coin.Value); } } public override void AddedTo(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.AddedTo(location, player); if (location == DeckLocation.Hand) { if (_CardGainingHandler != null) player.CardGaining -= _CardGainingHandler; _CardGainingHandler = new CardGainingEventHandler(Player_CardGaining); player.CardGaining += _CardGainingHandler; } } private void Player_CardGaining(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = Type.ToString(); // Already been cancelled -- don't need to process this one; also skip revealing for Silver if (e.Cancelled || !player.Hand.Contains(this) || e.Resolvers.ContainsKey(key) || e.Card is Universal.Silver) return; e.Resolvers[key] = new CardGainResolver(player, this, "RevealCard", Resource.RevealCard.Replace("{card}", PhysicalCard.ToString()), Player_RevealTrader, false); } internal void Player_RevealTrader(IPlayer player, ref Players.CardGainEventArgs e) { player.AddCardInto(DeckLocation.Revealed, player.RetrieveCardFrom(DeckLocation.Hand, this)); player.AddCardInto(DeckLocation.Hand, player.RetrieveCardFrom(DeckLocation.Revealed, this)); // Cancel the gain, add the card back to the Supply pile, and then Gain a Silver instead. e.Cancelled = true; e.IsLostTrackOf = true; //player._Game.Table.TableEntities[e.Card].AddTo(e.Card); player._Game.SendMessage(player, this, e.Card, player._Game.Table.Silver); player.Gain(player._Game.Table.Silver, this); } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); if (_CardGainingHandler != null) player.CardGaining -= _CardGainingHandler; _CardGainingHandler = null; } } // TODO -- Fix this so that it triggers at the same time as VillageGreen/FaithfulHound public class Tunnel : Card { private CardsDiscardedEventHandler _CardsDiscardedEventHandler; public Tunnel() : this(Edition.First) { } public Tunnel(Edition edition) : base(Categories.Victory | Categories.Reaction, Source.Hinterlands, Location.Kingdom, Traits.Gainer | Traits.ReactToDiscard, edition: edition) { BaseCost = new Cost(3); VictoryPoints = 2; OwnerChanged += new OwnerChangedEventHandler(Tunnel_OwnerChanged); } public override void TearDown(IGame game) { Tunnel_OwnerChanged(this, new OwnerChangedEventArgs(Owner, null)); base.TearDown(game); OwnerChanged -= new OwnerChangedEventHandler(Tunnel_OwnerChanged); } private void Tunnel_OwnerChanged(object sender, OwnerChangedEventArgs e) { if (_CardsDiscardedEventHandler != null && e.OldOwner != null) { e.OldOwner.CardsDiscarded -= _CardsDiscardedEventHandler; _CardsDiscardedEventHandler = null; } if (e.NewOwner != null) { _CardsDiscardedEventHandler = new CardsDiscardedEventHandler(Player_CardsDiscarded); e.NewOwner.CardsDiscarded += _CardsDiscardedEventHandler; } } private void Player_CardsDiscarded(object sender, CardsDiscardEventArgs e) { var player = sender as IPlayer; // Already being processed or been handled -- don't need to process this one if (e.GetResolver(Type) != null || e.HandledBy.Contains(this)) return; if (e.Cards.Contains(PhysicalCard) && player.Phase != PhaseEnum.Cleanup) e.AddResolver(Type, new CardsDiscardResolver(Owner, this, Resource.RevealCard.Replace("{card}", PhysicalCard.ToString()), Player_DiscardTunnel)); } internal void Player_DiscardTunnel(IPlayer player, ref CardsDiscardEventArgs e) { player.AddCardInto(DeckLocation.Revealed, this); player.RetrieveCardFrom(DeckLocation.Revealed, this); player.Gain(player._Game.Table.Gold, this); e.HandledBy.Add(this); } } }