using DominionBase.Currencies; using DominionBase.Enums; using DominionBase.Piles; using DominionBase.Players; using DominionBase.Properties; using DominionBase.Utilities; using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; namespace DominionBase.Cards.Promotional { public static class TypeClass { //public static readonly Type BlackMarket = typeof(BlackMarket); //public static readonly Type Church = typeof(Church); //public static readonly Type Dismantle = typeof(Dismantle); public static readonly Type Envoy = typeof(Envoy); public static readonly Type Governor = typeof(Governor); public static readonly Type Prince = typeof(Prince); public static readonly Type SaunaAvanto = typeof(SaunaAvanto); public static readonly Type Sauna = typeof(Sauna); public static readonly Type Avanto = typeof(Avanto); public static readonly Type Stash = typeof(Stash); public static readonly Type Summon = typeof(Summon); public static readonly Type WalledVillage = typeof(WalledVillage); public static readonly Type BlackMarketSupply = typeof(BlackMarketSupply); public static readonly Type PrinceSetAside = typeof(PrinceSetAside); public static readonly Type SummonSetAside = typeof(SummonSetAside); } public class BlackMarket : Card { public BlackMarket() : this(Edition.First) { } public BlackMarket(Edition edition) : base(Categories.Action, Source.Promotional, Location.Kingdom, Traits.PlusCoin | Traits.PlusBuy | Traits.Terminal, edition: edition) { BaseCost = new Cost(4); Benefit.Currency.Coin.Value = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.Draw(5, DeckLocation.Revealed); } public override void SetupSupply(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); base.SetupSupply(game, supply); var blackMarketSupply = new Supply(game, game.Players, TypeClass.BlackMarketSupply); blackMarketSupply.FullSetup(); game.Table.SpecialPiles.Add(TypeClass.BlackMarketSupply, blackMarketSupply); } public override void CheckSetup(Preset preset, ITable table) { Contract.Requires(preset != null, "preset cannot be null"); Contract.Requires(table != null, "table cannot be null"); preset.CardCards[this] = new List(); // Grab all of the Black Market Supply cards and stick them into the CardCards for Black Market foreach (var type in ((ISupply)table.SpecialPiles[TypeClass.BlackMarketSupply]).Types) preset.CardCards[this].Add(CreateInstance(type)); } public override List GetSerializingTypes() { return new List() { typeof(BlackMarket_NumberOfCards), typeof(BlackMarket_UseGameConstraints), typeof(BlackMarket_Constraints) }; //, typeof(BlackMarket_ErrorOnNotEnoughCards) }; } public override CardSettingCollection GenerateSettings() { var csc = new CardSettingCollection { new BlackMarket_NumberOfCards { Value = 25 }, new BlackMarket_UseGameConstraints { Value = false }, new BlackMarket_Constraints { Value = new ConstraintCollection() } }; //csc.Add(new BlackMarket_ErrorOnNotEnoughCards { Value = true }); return csc; } public override void FinalizeSettings(CardSettingCollection settings) { Contract.Requires(settings != null, "settings cannot be null"); (settings[typeof(BlackMarket_Constraints)].Value as ConstraintCollection).MaxCount = 100; } [Serializable] public class BlackMarket_NumberOfCards : CardSetting { public override string Name => "NumberOfCards"; public override string Text => "Number of cards to use"; public override string Hint => "Number of cards to use in the Black Market supply pile"; public override Type Type => typeof(int); public override int DisplayOrder => 0; public override object LowerBounds => 1; public override bool UseLowerBounds => true; public override bool IsLowerBoundsInclusive => true; } [Serializable] public class BlackMarket_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); public override int DisplayOrder => 2; } [Serializable] public class BlackMarket_Constraints : CardSetting { public override string Name => "Constraints"; public override string Hint => "Constraints to use for selecting cards to use in the Black Market supply"; public override Type Type => typeof(ConstraintCollection); public override int DisplayOrder => 3; } //[Serializable] //public class BlackMarket_ErrorOnNotEnoughCards : CardSetting //{ // public override String Name { get { return "ErrorOnNotEnoughCards"; } } // public override String Text { get { return "Error when not enough matching, allowed cards are found"; } } // public override String Hint { get { return "Error when the game can't find enough cards to use for the Black Market supply pile"; } } // public override Type Type { get { return typeof(Boolean); } } // public override int DisplayOrder { get { return 1; } } //} } public class BlackMarketSupply : Card { public BlackMarketSupply() : base(Categories.Unknown, Source.Promotional, Location.Invisible, Traits.None) { } public override void SetupSupply(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); Contract.Requires(supply != null, "supply cannot be null"); base.SetupSupply(game, supply); IList availableCards = null; try { if (game.Settings.Preset != null) { availableCards = game.Settings.Preset.CardCards[game.Settings.Preset.Cards.First(c => c.Type == typeof(BlackMarket))].ToList(); } else { var cardsToUse = 25; //Boolean errorOnNotEnoughCards = true; var shouldUseGameConstraints = true; var bmConstraints = new ConstraintCollection(); if (game.Settings.CardSettings.ContainsKey("Black Market")) { var bmSettings = game.Settings.CardSettings["Black Market"]; cardsToUse = (int)bmSettings.CardSettingCollection[typeof(BlackMarket.BlackMarket_NumberOfCards)].Value; //errorOnNotEnoughCards = (Boolean)bmSettings.CardSettingCollection[typeof(BlackMarket.BlackMarket_ErrorOnNotEnoughCards)].Value; shouldUseGameConstraints = (bool)bmSettings.CardSettingCollection[typeof(BlackMarket.BlackMarket_UseGameConstraints)].Value; bmConstraints = (ConstraintCollection)bmSettings.CardSettingCollection[typeof(BlackMarket.BlackMarket_Constraints)].Value; } // need to set up a supply pile for Black Market; randomly pick an unused supply card and add it to the pile until we have the requisite number of cards availableCards = game.CardsAvailable; if (shouldUseGameConstraints) { // Skip all "Must Use" constraints var constraints = new ConstraintCollection(game.Settings.Constraints.Where(c => c.ConstraintType != ConstraintType.CardMustUse)); availableCards = constraints.SelectCards(availableCards.ToList(), cardsToUse); } //else //availableCards = bmConstraints.SelectCards(availableCards.Cast().ToList(), cardsToUse); } // Shuffle the cards -- these should definitely not be set up in a known order availableCards.Shuffle(); } catch (ConstraintException ce) { throw new BlackMarketConstraintException($"Problem setting up Black Market constraints: {ce.Message}"); } foreach (var cardToUse in availableCards.OfType()) { game.CardsAvailable.Remove(cardToUse); supply.AddTo(cardToUse); } } } public class BlackMarketConstraintException : ConstraintException { public BlackMarketConstraintException() { } public BlackMarketConstraintException(string message) : base(message) { } public BlackMarketConstraintException(string message, Exception innerException) : base(message, innerException) { } internal BlackMarketConstraintException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } } public class Church : Card { public Church() : base(Categories.Action | Categories.Duration, Source.Promotional, Location.Kingdom, Traits.PlusAction | Traits.Trasher | Traits.RemoveCurses | Traits.DeckReduction) { BaseCost = new Cost(3); Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { base.FollowInstructions(player); throw new NotImplementedException(); } } public class Dismantle : Card { public Dismantle() : base(Categories.Action, Source.Promotional, Location.Kingdom, Traits.Terminal | Traits.Trasher | Traits.RemoveCurses | Traits.DeckReduction | Traits.Gainer | Traits.ConditionalBenefit | Traits.TrashForBenefit) { BaseCost = new Cost(4); } public override void FollowInstructions(IPlayer player) { base.FollowInstructions(player); throw new NotImplementedException(); } } public class Envoy : Card { public Envoy() : this(Edition.First) { } public Envoy(Edition edition) : base(Categories.Action, Source.Promotional, Location.Kingdom, Traits.PlusCard | Traits.Terminal | Traits.NetCardDraw, edition: edition) { BaseCost = new Cost(4); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.Draw(5, DeckLocation.Revealed); var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); // Gets us... which we don't care about here. enumerator.MoveNext(); // Get the player to our left var leftPlayer = enumerator.Current; var choice = new Choice($"Choose a card of {player}'s to discard", this, player.Revealed, ChoiceOutcome.Discard, player); var result = leftPlayer.MakeChoice(choice); // Discard the chosen card if (result.Cards.Any()) player.Discard(DeckLocation.Revealed, result.Cards[0]); player.AddCardsToHand(DeckLocation.Revealed); } } public class Governor : Card { public Governor() : this(Edition.First) { } public Governor(Edition edition) : base( Categories.Action, Source.Promotional, Location.Kingdom, Traits.PlusAction | Traits.PlusCard | Traits.AffectOthers | Traits.DeckReduction | Traits.Gainer | Traits.RemoveCurses | Traits.Trasher | Traits.ConditionalBenefit | Traits.TrashForBenefit | Traits.NetCardDraw | Traits.RemoveFromHand, edition: edition) { BaseCost = new Cost(5); Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice("Choose 1. You get the version in parenthesis; everyone else gets the other:", this, this, new List() { "+1 (+3) Cards", "Gain a Silver (Gold)", "You may trash a card from your hand and gain a card costing exactly 1 (2) more" }, player); var result = player.MakeChoice(choice); var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); while (enumerator.MoveNext()) { var actee = enumerator.Current; if (result.Options.Contains("+1 (+3) Cards")) { // 3 or 1 cards, depending on who it is actee.ReceiveBenefit(this, new CardBenefit() { Cards = (actee == player ? 3 : 1) }); } else if (result.Options.Contains("Gain a Silver (Gold)")) { if (actee == player) actee.Gain(player._Game.Table.Gold, this); else actee.Gain(player._Game.Table.Silver, this); } else { var choiceTrash = new Choice(Resource.ChooseToTrashOptional, this, actee.Hand, ChoiceOutcome.Trash, actee, minimum: 0); var resultTrash = actee.MakeChoice(choiceTrash); actee.Trash(this, actee.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); if (resultTrash.Cards.Any()) { var trashedCardCost = actee._Game.ComputeCost(resultTrash.Cards[0]); var gainableSupplies = new SupplyCollection(actee._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost == (trashedCardCost + new Coin(actee == player ? 2 : 1)))); var choiceGain = new Choice(Resource.GainCard, this, gainableSupplies, ChoiceOutcome.Gain, actee, false); var resultGain = actee.MakeChoice(choiceGain); if (resultGain.Supply != null) actee.Gain(resultGain.Supply, this); } } } } } public class Prince : Card { private Card _SetAsideCard; private Card _SetAsideCardInPlay; private TurnStartedEventHandler _TurnStartedEventHandler; private IPlayer _TurnStartedPlayer; private CardsDiscardingEventHandler _CardsDiscardingEventHandler; public Prince() : base(Categories.Action, Source.Promotional, Location.Kingdom, Traits.Terminal | Traits.DeckReduction) { BaseCost = new Cost(8); } internal override void ReceivedBy(IPlayer player) { base.ReceivedBy(player); if (!player.PlayerMats.ContainsKey(TypeClass.PrinceSetAside)) player.PlayerMats[TypeClass.PrinceSetAside] = new PrinceSetAside(); } public override void TearDown(IGame game) { base.TearDown(game); _SetAsideCard = null; _SetAsideCardInPlay = null; if (_TurnStartedEventHandler != null && _TurnStartedPlayer != null) _TurnStartedPlayer.TurnStarted -= _TurnStartedEventHandler; _TurnStartedPlayer = null; _TurnStartedEventHandler = null; } public override IEnumerable LookThrough(Predicate predicate) { var setAside = new List(); if (_SetAsideCard != null) setAside.Add(_SetAsideCard); return setAside.Where(c => predicate(c)); } public override bool IsStackable { get { if (_TurnStartedPlayer == null) return true; if (_TurnStartedPlayer.Phase == PhaseEnum.Endgame) return true; return false; } } public override DisplayableCollection Stack() { var cc = new DisplayableCollection(); if (_SetAsideCard != null) cc.Add(_SetAsideCard); cc.Add(this); return cc; } public Card SetAsideCard => _SetAsideCard ?? _SetAsideCardInPlay; public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.InPlay.Contains(PhysicalCard)) { var choice = Choice.CreateYesNoChoice("Do you want to set this card aside?", this, player); var result = player.MakeChoice(choice); if (result.Options[0] == Resource.Yes) { player.AddCardInto(TypeClass.PrinceSetAside, player.RetrieveCardFrom(DeckLocation.InPlay, PhysicalCard)); if (_TurnStartedEventHandler != null) player.TurnStarted -= _TurnStartedEventHandler; _TurnStartedPlayer = player; _TurnStartedEventHandler = new TurnStartedEventHandler(Player_TurnStarted); _TurnStartedPlayer.TurnStarted += _TurnStartedEventHandler; var setAsideChoice = new Choice(Resource.ChooseCardToSetAside, this, player.Hand[c => c.Category.HasFlag(Categories.Action) && player._Game.ComputeCost(c) <= new Coin(4)], ChoiceOutcome.Select, player); var setAsideResult = player.MakeChoice(setAsideChoice); if (setAsideResult.Cards.Any()) { _SetAsideCard = player.RetrieveCardFrom(DeckLocation.Hand, setAsideResult.Cards[0]); player.PlayerMats[TypeClass.PrinceSetAside].Refresh(player); player._Game.SendMessage(player, this, "SetAside", _SetAsideCard); } } } } public override void PerformEndgameCalculations(IPlayer player, PointsCollection collection) { Contract.Requires(collection != null, "collection cannot be null"); // Add back any set aside cards that are still on this if (_SetAsideCard != null) collection.Add(_SetAsideCard); if (_SetAsideCardInPlay != null) collection.Add(_SetAsideCardInPlay); _SetAsideCard = null; _SetAsideCardInPlay = null; if (_TurnStartedEventHandler != null && _TurnStartedPlayer != null) _TurnStartedPlayer.TurnStarted -= _TurnStartedEventHandler; _TurnStartedPlayer = null; _TurnStartedEventHandler = null; } private void Player_TurnStarted(object sender, TurnStartedEventArgs e) { if (e.HandledBy.Contains(this)) return; _SetAsideCardInPlay = null; if (_SetAsideCard != null) { string key = $"{this}:{_SetAsideCard}"; if (e.Resolvers.ContainsKey(key)) return; e.Resolvers[key] = new TurnStartedResolver(e.Player, this, $"Play {_SetAsideCard} from {PhysicalCard}", Player_Action, true); } } internal void Player_Action(IPlayer player, ref TurnStartedEventArgs e) { e.Player.Actions++; e.Player.AddCardInto(DeckLocation.InPlay, _SetAsideCard); _SetAsideCardInPlay = _SetAsideCard; _SetAsideCard = null; e.Player.PlayerMats[TypeClass.PrinceSetAside].Refresh(e.Player); e.Player.PlayCardInternal(_SetAsideCardInPlay.LogicalCard, modifier: $" from {Name}"); _CardsDiscardingEventHandler = new CardsDiscardingEventHandler(Player_CardsDiscarding); e.Player.CardsDiscarding += _CardsDiscardingEventHandler; e.HandledBy.Add(this); } private void Player_CardsDiscarding(object sender, CardsDiscardEventArgs e) { if (e.FromLocation != DeckLocation.InPlay && e.FromLocation != DeckLocation.SetAside && e.FromLocation != DeckLocation.InPlayAndSetAside) return; if (e.HandledBy.Contains(this)) return; if (e.Cards.Contains(_SetAsideCardInPlay)) { e.AddResolver(Type, _SetAsideCardInPlay.Type, new CardsDiscardResolver(sender as IPlayer, this, $"Set aside {_SetAsideCardInPlay}", Player_DiscardAction, true) { Data = _SetAsideCardInPlay }); } else { _SetAsideCardInPlay = null; _SetAsideCard = null; } } internal void Player_DiscardAction(IPlayer player, ref CardsDiscardEventArgs e) { if (_CardsDiscardingEventHandler != null) _TurnStartedPlayer.CardsDiscarding -= _CardsDiscardingEventHandler; var cardToSetAside = e.Data as Card; e.Cards.Remove(cardToSetAside); if (player.InPlay.Contains(_SetAsideCardInPlay.PhysicalCard)) player.RetrieveCardFrom(DeckLocation.InPlay, cardToSetAside.PhysicalCard); else player.RetrieveCardFrom(DeckLocation.SetAside, cardToSetAside.PhysicalCard); _SetAsideCard = cardToSetAside; player._Game.SendMessage(player, this, "SetAside", _SetAsideCard); _SetAsideCardInPlay = null; player.PlayerMats[TypeClass.PrinceSetAside].Refresh(player); e.HandledBy.Add(this); } } public class PrinceSetAside : CardMat { public PrinceSetAside() : base(Visibility.All, VisibilityTo.All, new Sorting.ByTypeName(Sorting.SortDirection.Descending), false) { Name = "Set Aside (Prince)"; IsObtainable = false; } } public class SaunaAvanto : Card { public SaunaAvanto() : base(Categories.Action, Source.Promotional, Location.Kingdom, Traits.Randomizer | Traits.PlusCard | Traits.PlusAction | Traits.Cantrip | Traits.SplitPile | Traits.RemoveCurses | Traits.DeckReduction | Traits.Trasher) { BaseCost = new Cost(4); } public override void SetupSupply(IGame game, ISupply supply) { Contract.Requires(supply != null, "supply cannot be null"); base.SetupSupply(game, supply); supply.Empty(); var cards = new CardCollection { new Avanto(), new Avanto(), new Avanto(), new Avanto(), new Avanto(), new Sauna(), new Sauna(), new Sauna(), new Sauna(), new Sauna() }; supply.AddTo(cards); } } public class Sauna : Card { private CardPlayedEventHandler _CardPlayedEventHandler; public Sauna() : base(Categories.Action, Source.Promotional, Location.Special, Traits.PlusCard | Traits.PlusAction | Traits.Cantrip | Traits.RemoveCurses | Traits.DeckReduction | Traits.Trasher | Traits.RemoveFromHand) { BaseCost = new Cost(4); 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 (_CardPlayedEventHandler != null) player.CardPlayed -= _CardPlayedEventHandler; _CardPlayedEventHandler = new CardPlayedEventHandler(Player_CardPlayed); player.CardPlayed += _CardPlayedEventHandler; } } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); if (_CardPlayedEventHandler != null) player.CardPlayed -= _CardPlayedEventHandler; _CardPlayedEventHandler = null; } private void Player_CardPlayed(object sender, CardPlayedEventArgs e) { // Already been processed -- don't need to process this one if (e.HandledBy.Count(h => h == this) >= e.Cards.Count(c => c is Universal.Silver) || e.Resolvers.ContainsKey(Type) || !e.Player.Hand.Any()) return; e.Resolvers[Type] = new CardPlayedResolver(e.Player, this, Resource.TrashACardFromYourHand, Player_PlaySilver, false); } internal void Player_PlaySilver(IPlayer player, ref CardPlayedEventArgs e) { var choiceTrash = new Choice(Resource.ChooseACardToTrash, this, player.Hand, ChoiceOutcome.Trash, player, minimum: 0); var resultTrash = player.MakeChoice(choiceTrash); player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); e.HandledBy.Add(this); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.Hand[TypeClass.Avanto].Any()) { var choicePlayer = Choice.CreateYesNoChoice("Do you want to play an Avanto from your hand?", this, player); var resultPlayer = player.MakeChoice(choicePlayer); if (resultPlayer.Options[0] == Resource.Yes) { player.Actions++; player.PlayCardInternal(player.Hand[TypeClass.Avanto][0]); } } } } public class Avanto : Card { public Avanto() : base(Categories.Action, Source.Promotional, Location.Special, Traits.PlusCard | Traits.NetCardDraw) { BaseCost = new Cost(5); Benefit.Cards = 3; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.Hand[TypeClass.Sauna].Any()) { var choicePlayer = Choice.CreateYesNoChoice("Do you want to play a Sauna from your hand?", this, player); var resultPlayer = player.MakeChoice(choicePlayer); if (resultPlayer.Options[0] == Resource.Yes) { player.Actions++; player.PlayCardInternal(player.Hand[TypeClass.Sauna][0]); } } } } public class Stash : Card { public Stash() : this(Edition.First) { } public Stash(Edition edition) : base(Categories.Treasure, Source.Promotional, Location.Kingdom, Traits.PlusCoin | Traits.CardOrdering, CardBack.Red, edition) { BaseCost = new Cost(5); Benefit.Currency.Coin.Value = 2; OwnerChanged += new OwnerChangedEventHandler(Stash_OwnerChanged); } protected override bool AllowUndo => true; public override void TearDown(IGame game) { Stash_OwnerChanged(this, new OwnerChangedEventArgs(Owner, null)); base.TearDown(game); OwnerChanged -= new OwnerChangedEventHandler(Stash_OwnerChanged); } private void Stash_OwnerChanged(object sender, OwnerChangedEventArgs e) { if (e.OldOwner != null) e.OldOwner.Shuffled -= Player_Shuffled; if (e.NewOwner != null) e.NewOwner.Shuffled += Player_Shuffled; } private void Player_Shuffled(object sender, ShuffleEventArgs e) { // Only do this if we're the first one if (e.HandledBy.Contains(Type) || e.Resolvers.ContainsKey(Type) || !e.ShuffleCards.Contains(this)) return; e.Resolvers[Type] = new ShuffleResolver(e.Player, this, $"Resolve {Name}", (IPlayer player, ref ShuffleEventArgs sea) => { var deck = e.ShuffleCards.OfType(); var choiceShuffle = new Choice(Resource.RearrangeShuffledCards, this, deck, ChoiceOutcome.Select, e.Player, isOrdered: true, minimum: deck.Count(), maximum: deck.Count(), visibility: Visibility.None); var resultShuffle = e.Player.MakeChoice(choiceShuffle); sea.ShuffleCards.Clear(); // Add the cards back (the cards come back reversed) resultShuffle.Cards.Reverse(); sea.ShuffleCards.AddRange(resultShuffle.Cards); e.HandledBy.Add(Type); }, false); } } public class Summon : Event { private readonly Dictionary _SetAsideCards = new Dictionary(); private Card _PlayCard; public Summon() : this(Edition.First) { } public Summon(Edition edition) : base(Source.Promotional, 5, Traits.Gainer, edition: edition) { } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerKVP in _SetAsideCards) { playerKVP.Key.TurnStarted -= Player_TurnStarted; playerKVP.Value.Clear(); } } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); var supplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll( s => s.CanGain() && s.TopCard.Category.HasFlag(Categories.Action) && s.CurrentCost <= new Coin(4) )); var choiceSetAside = new Choice("Choose a card to gain & set aside until next turn", this, supplies, ChoiceOutcome.Gain, player, false); var resultSetAside = player.MakeChoice(choiceSetAside); if (resultSetAside.Supply != null) { if (!player.PlayerMats.ContainsKey(TypeClass.SummonSetAside)) player.PlayerMats[TypeClass.SummonSetAside] = new SummonSetAside(); player.CardGained += Player_CardGained; player.Gain(resultSetAside.Supply, this); player.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 the card has been "lost track of", then we can skip calling it if (e.Cancelled || e.IsLostTrackOf || e.Resolvers.ContainsKey(key)) return; e.Resolvers[key] = new CardGainResolver(player, this, "SetAside", $"Set aside for {this}", Player_SetAside, true); } internal void Player_SetAside(IPlayer player, ref Players.CardGainEventArgs e) { var c = player.RetrieveCardFrom(e.Location, e.Card); e.Cancelled = true; e.Location = DeckLocation.PlayerMat; if (!_SetAsideCards.ContainsKey(player)) _SetAsideCards[player] = new CardCollection(); _SetAsideCards[player].Add(c); player.AddCardInto(TypeClass.SummonSetAside, c); player._Game.SendMessage(player, this, c); player.TurnStarted += Player_TurnStarted; e.HandledBy.Add(this); } private void Player_TurnStarted(object sender, TurnStartedEventArgs e) { if (_SetAsideCards.ContainsKey(e.Player) && _SetAsideCards[e.Player].Count > 0) { foreach (var card in _SetAsideCards[e.Player]) { var key = ToString(); if (e.HandledBy.Contains(card.UniqueId) || e.Resolvers.ContainsKey(key)) continue; e.Resolvers[key] = new TurnStartedResolver( e.Player, this, $"Play {card} from {this}", (IPlayer player, ref TurnStartedEventArgs ea) => { _PlayCard = card; Player_Action(player, ref ea); }, true ); } } } internal void Player_Action(IPlayer player, ref TurnStartedEventArgs e) { e.Player.Actions++; _SetAsideCards[player].Remove(_PlayCard); e.Player.AddCardInto(DeckLocation.InPlay, e.Player.RetrieveCardFrom(TypeClass.SummonSetAside, _PlayCard)); e.Player.PlayerMats[TypeClass.SummonSetAside].Refresh(e.Player); e.Player.PlayCardInternal(_PlayCard.LogicalCard, modifier: $" from {Name}"); e.HandledBy.Add(_PlayCard.UniqueId); } } public class SummonSetAside : CardMat { public SummonSetAside() : base(Visibility.All, VisibilityTo.All, new Sorting.ByTypeName(Sorting.SortDirection.Descending), false) { Name = "Set Aside (Summon)"; IsObtainable = false; } } public class WalledVillage : Card { private PhaseChangingEventHandler _PhaseChangingEventHandler; private CleaningUpEventHandler _CleaningUpEventHandler; private bool _CanPutOnDeck; public WalledVillage() : this(Edition.First) { } public WalledVillage(Edition edition) : base(Categories.Action, Source.Promotional, Location.Kingdom, Traits.CardOrdering | Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.Cantrip, edition: edition) { BaseCost = new Cost(4); Benefit.Cards = 1; Benefit.Actions = 2; } public override void AddedTo(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.AddedTo(location, player); switch (location) { case DeckLocation.InPlay: if (_PhaseChangingEventHandler != null) player.PhaseChanging -= _PhaseChangingEventHandler; _PhaseChangingEventHandler = new PhaseChangingEventHandler(Player_PhaseChanging); player.PhaseChanging += _PhaseChangingEventHandler; if (_CleaningUpEventHandler != null) player.CleaningUp -= _CleaningUpEventHandler; _CleaningUpEventHandler = new CleaningUpEventHandler(Player_CleaningUp); player.CleaningUp += _CleaningUpEventHandler; _CanPutOnDeck = false; break; } } private void Player_PhaseChanging(object sender, PhaseChangingEventArgs e) { switch (e.NewPhase) { case PhaseEnum.Cleanup: if (e.CurrentPlayer.InPlayAndSetAside[Categories.Action].Count <= 2) _CanPutOnDeck = true; else _CanPutOnDeck = false; break; } } private void Player_CleaningUp(object sender, CleaningUpEventArgs e) { if (!e.CurrentPlayer.InPlay.Contains(PhysicalCard) || e.Resolvers.ContainsKey(Type)) return; if (_CanPutOnDeck) e.Resolvers[Type] = new CleaningUpResolver(this, $"Put {PhysicalCard} on top your deck", Player_Action); } internal void Player_Action(IPlayer player, ref CleaningUpEventArgs e) { //e.CardsMovements[this].Destination = DeckLocation.Deck; e.CardsMovements.Remove(e.CardsMovements.Find(cm => cm.Card == PhysicalCard)); //e.CardsMovements.MoveToEnd(this); e.CurrentPlayer.AddCardToDeck(e.CurrentPlayer.RetrieveCardFrom(DeckLocation.InPlay, PhysicalCard), DeckPosition.Top); } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); if (_PhaseChangingEventHandler != null) player.PhaseChanging -= _PhaseChangingEventHandler; _PhaseChangingEventHandler = null; if (_CleaningUpEventHandler != null) player.CleaningUp -= _CleaningUpEventHandler; _CleaningUpEventHandler = null; _CanPutOnDeck = false; } } }