using DominionBase.Currencies; using DominionBase.Enums; using DominionBase.Interfaces; using DominionBase.Piles; using DominionBase.Players; using DominionBase.Properties; using DominionBase.Utilities; using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Xml; namespace DominionBase.Cards.Empires { public static class TypeClass { public static readonly Type DebtToken = typeof(DebtToken); public static readonly Type Archive = typeof(Archive); public static readonly Type BustlingVillage = typeof(BustlingVillage); public static readonly Type Capital = typeof(Capital); public static readonly Type Castles = typeof(Castles); public static readonly Type CatapultRocks = typeof(CatapultRocks); public static readonly Type Catapult = typeof(Catapult); public static readonly Type ChariotRace = typeof(ChariotRace); public static readonly Type Charm = typeof(Charm); public static readonly Type CityQuarter = typeof(CityQuarter); public static readonly Type Crown = typeof(Crown); public static readonly Type CrumblingCastle = typeof(CrumblingCastle); public static readonly Type Emporium = typeof(Emporium); public static readonly Type EncampmentPlunder = typeof(EncampmentPlunder); public static readonly Type Encampment = typeof(Encampment); public static readonly Type Enchantress = typeof(Enchantress); public static readonly Type Engineer = typeof(Engineer); public static readonly Type FarmersMarket = typeof(FarmersMarket); public static readonly Type Fortune = typeof(Fortune); public static readonly Type Forum = typeof(Forum); public static readonly Type GladiatorFortune = typeof(GladiatorFortune); public static readonly Type Gladiator = typeof(Gladiator); public static readonly Type GrandCastle = typeof(GrandCastle); public static readonly Type Groundskeeper = typeof(Groundskeeper); public static readonly Type HauntedCastle = typeof(HauntedCastle); public static readonly Type HumbleCastle = typeof(HumbleCastle); public static readonly Type KingsCastle = typeof(KingsCastle); public static readonly Type Legionary = typeof(Legionary); public static readonly Type OpulentCastle = typeof(OpulentCastle); public static readonly Type Overlord = typeof(Overlord); public static readonly Type PatricianEmporium = typeof(PatricianEmporium); public static readonly Type Patrician = typeof(Patrician); public static readonly Type Plunder = typeof(Plunder); public static readonly Type Rocks = typeof(Rocks); public static readonly Type RoyalBlacksmith = typeof(RoyalBlacksmith); public static readonly Type Sacrifice = typeof(Sacrifice); public static readonly Type SettlersBustlingVillage = typeof(SettlersBustlingVillage); public static readonly Type Settlers = typeof(Settlers); public static readonly Type SmallCastle = typeof(SmallCastle); public static readonly Type SprawlingCastle = typeof(SprawlingCastle); public static readonly Type Temple = typeof(Temple); public static readonly Type Villa = typeof(Villa); public static readonly Type WildHunt = typeof(WildHunt); public static readonly Type Advance = typeof(Advance); public static readonly Type Annex = typeof(Annex); public static readonly Type Banquet = typeof(Banquet); public static readonly Type Conquest = typeof(Conquest); public static readonly Type Delve = typeof(Delve); public static readonly Type Dominate = typeof(Dominate); public static readonly Type Donate = typeof(Donate); public static readonly Type Ritual = typeof(Ritual); public static readonly Type SaltTheEarth = typeof(SaltTheEarth); public static readonly Type Tax = typeof(Tax); public static readonly Type Triumph = typeof(Triumph); public static readonly Type Wedding = typeof(Wedding); public static readonly Type Windfall = typeof(Windfall); public static readonly Type Aqueduct = typeof(Aqueduct); public static readonly Type Arena = typeof(Arena); public static readonly Type BanditFort = typeof(BanditFort); public static readonly Type Basilica = typeof(Basilica); public static readonly Type Baths = typeof(Baths); public static readonly Type Battlefield = typeof(Battlefield); public static readonly Type Colonnade = typeof(Colonnade); public static readonly Type DefiledShrine = typeof(DefiledShrine); public static readonly Type Fountain = typeof(Fountain); public static readonly Type Keep = typeof(Keep); public static readonly Type Labyrinth = typeof(Labyrinth); public static readonly Type MountainPass = typeof(MountainPass); public static readonly Type Museum = typeof(Museum); public static readonly Type ObeliskMarker = typeof(ObeliskMarker); public static readonly Type Obelisk = typeof(Obelisk); public static readonly Type Orchard = typeof(Orchard); public static readonly Type Palace = typeof(Palace); public static readonly Type Tomb = typeof(Tomb); public static readonly Type Tower = typeof(Tower); public static readonly Type TriumphalArch = typeof(TriumphalArch); public static readonly Type Wall = typeof(Wall); public static readonly Type WolfDen = typeof(WolfDen); } public class DebtToken : Token { public DebtToken() : base("", "Debt token") { } public override string Title => "Each token represents a coin that needs to be repaid"; public override bool IsPlayable => true; public override IEnumerable PlayablePhases => new List { PhaseEnum.BuyTreasure, PhaseEnum.Buy, PhaseEnum.Cleanup }; /// /// Used internally by the base Card class -- Don't use this. /// internal override void Play(IPlayer player, int count) { if (count <= 0) throw new ArgumentOutOfRangeException(nameof(count), "Count must be positive."); player.ReceiveBenefit(this, new CardBenefit { Currency = new Currency(-count) }); } } public class Archive : Card { private readonly List> _archivedCards = new List>(); private int _activeIndex = -1; public Archive() : base(Categories.Action | Categories.Duration, Source.Empires, Location.Kingdom, Traits.PlusAction | Traits.PlusCard | Traits.Cantrip | Traits.NetCardDraw) { BaseCost = new Cost(5); Benefit.Actions = 1; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); _archivedCards.Clear(); CanCleanUpPlayed.Clear(); foreach (var player in game.Players) { player.TurnEnded -= Player_TurnEnded; player.TurnStarted -= Player_TurnStarted; } } public override IEnumerable LookThrough(Predicate predicate) { return _archivedCards.SelectMany(ic => ic.Where(c => predicate(c))); } public override bool IsStackable { get { return _archivedCards.Sum(l => l.Count) == 0; } } public override DisplayableCollection Stack() { var cc = new DisplayableCollection(); _archivedCards.ForEach(l => l.ForEach(c => cc.Add(Universal.Utility.GenerateCardBack(c.CardBack)))); cc.Add(this); return cc; } public override void AddedTo(DeckLocation location, IPlayer player) { base.AddedTo(location, player); if (location == DeckLocation.InPlay) { _archivedCards.Clear(); CanCleanUpPlayed.Clear(); } } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var archivedCards = player.RetrieveCardsFrom(DeckLocation.Deck, c => true, 3); // Show the cards to the owner player.AddCardsInto(DeckLocation.Private, archivedCards); player.RetrieveCardsFrom(DeckLocation.Private, archivedCards); _activeIndex = _archivedCards.Count; _archivedCards.Add(archivedCards); CanCleanUpPlayed.Add(!archivedCards.Any()); player._Game.SendMessage(player, this, archivedCards.ToArray()); AddArchivedCardToHand(player); player.TurnEnded += Player_TurnEnded; } private void Player_TurnEnded(object sender, TurnEndedEventArgs e) { e.Player.TurnEnded -= Player_TurnEnded; e.Player.TurnStarted += Player_TurnStarted; } public override void ResolveDuration(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.ResolveDuration(player); AddArchivedCardToHand(player); } private void AddArchivedCardToHand(IPlayer player) { if (_archivedCards.Count - 1 >= _activeIndex) { var archivePile = _archivedCards[_activeIndex]; var choice = new Choice(Resource.CardToAddToHand, this, archivePile, ChoiceOutcome.Select, player); var result = player.MakeChoice(choice); if (result.Cards.Any()) { archivePile.Remove(result.Cards[0]); player.AddCardToHand(result.Cards[0]); } CanCleanUpPlayed[_activeIndex] = !archivePile.Any(); } else { player.TurnStarted -= Player_TurnStarted; } } public override void PerformEndgameCalculations(IPlayer player, PointsCollection collection) { Contract.Requires(collection != null, "collection cannot be null"); // Add back any Archived cards that are still on this foreach (var cards in _archivedCards) collection.AddRange(cards); _archivedCards.Clear(); CanCleanUpPlayed.Clear(); } private void Player_TurnStarted(object sender, TurnStartedEventArgs e) { for (var i = 0; i < _archivedCards.Count; i++) { var key = $"{UniqueId}_{i}"; // Skip empty archived piles (they'll be eventually removed when Archive is removed from play) if (_archivedCards[i].Count == 0) continue; if (!e.HandledBy.Contains(key) && !e.Resolvers.ContainsKey(key)) e.Resolvers[key] = new TurnStartedResolver( e.Player, this, Resource.ResolveCard.Replace("{card}", PhysicalCard.ToString()), (IPlayer player, ref TurnStartedEventArgs eAction) => { _activeIndex = (int)eAction.Resolvers[key].Data; ResolveDuration(eAction.Player); eAction.HandledBy.Add(key); }, true, i); } } } public class BustlingVillage : Card { public BustlingVillage() : base(Categories.Action, Source.Empires, Location.Special, Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.Cantrip) { BaseCost = new Cost(5); Benefit.Cards = 1; Benefit.Actions = 3; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var discardPileCards = player.DiscardPile.LookThrough(c => true); player._Game.SendMessage(player, this, "LookThroughDiscard", discardPileCards.ToArray()); var settlersCount = discardPileCards.Count(c => c is Settlers); if (settlersCount > 0) { var choice = Choice.CreateYesNoChoice(Resource.ShouldRevealSettlers, this, player); var result = player.MakeChoice(choice); if (result.Options[0] == Resource.Yes) { player.AddCardsInto(DeckLocation.Revealed, player.RetrieveCardsFrom(DeckLocation.Discard, TypeClass.Settlers, 1)); player.AddCardsToHand(DeckLocation.Revealed); } } } } public class Capital : Card { private CardsDiscardingEventHandler _cardsDiscardingEventHandler; public Capital() : base(Categories.Treasure, Source.Empires, Location.Kingdom, Traits.Component | Traits.PlusCoin | Traits.PlusBuy | Traits.Debts) { BaseCost = new Cost(5); Benefit.Currency.Coin.Value = 6; Benefit.Buys = 1; OwnerChanged += new OwnerChangedEventHandler(Capital_OwnerChanged); } public override void TearDown(IGame game) { Capital_OwnerChanged(this, new OwnerChangedEventArgs(Owner, null)); base.TearDown(game); OwnerChanged -= new OwnerChangedEventHandler(Capital_OwnerChanged); } private void Capital_OwnerChanged(object sender, OwnerChangedEventArgs e) { if (_cardsDiscardingEventHandler != null && e.OldOwner != null) { e.OldOwner.CardsDiscarding -= _cardsDiscardingEventHandler; _cardsDiscardingEventHandler = null; } if (e.NewOwner != null) { _cardsDiscardingEventHandler = new CardsDiscardingEventHandler(Player_CardsDiscarding); e.NewOwner.CardsDiscarding += _cardsDiscardingEventHandler; } } private void Player_CardsDiscarding(object sender, CardsDiscardEventArgs e) { if (!e.Cards.Contains(PhysicalCard) || e.GetResolver(TypeClass.Capital) != null || e.HandledBy.Contains(this) || (e.FromLocation != DeckLocation.InPlay && e.FromLocation != DeckLocation.SetAside && e.FromLocation != DeckLocation.InPlayAndSetAside)) return; e.AddResolver(TypeClass.Capital, new CardsDiscardResolver(sender as IPlayer, this, "Take 6", Player_Action, true)); } internal void Player_Action(IPlayer player, ref CardsDiscardEventArgs e) { player.AddTokens(new TokenCollection() { new DebtToken(), new DebtToken(), new DebtToken(), new DebtToken(), new DebtToken(), new DebtToken() }); e.HandledBy.Add(this); var debtCount = Math.Min(player.TokenPiles[TypeClass.DebtToken].Count, player.Currency.Coin.Value); if (debtCount > 0) { var options = new List(); for (var i = 0; i <= debtCount; i++) options.Add(i.ToString(CultureInfo.InvariantCulture)); var choice = new Choice("How many Debt tokens would you like to pay off?", this, this, options, player); var result = player.MakeChoice(choice); var number = int.Parse(result.Options[0], CultureInfo.InvariantCulture); player.PlayTokens(player._Game, TypeClass.DebtToken, number); } } public override bool Finalize(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); if (base.Finalize(game, supply)) return true; foreach (var player in game.Players) if (!player.TokenPiles.ContainsKey(TypeClass.DebtToken)) player.TokenPiles[TypeClass.DebtToken] = new TokenCollection(); return false; } } public abstract class Castle : Card { internal Castle(Categories category, Traits group) : base(category | Categories.Victory | Categories.Castle, Source.Empires, Location.Special, group) { } } public class Castles : Card { public Castles() : base(Categories.Victory | Categories.Castle, Source.Empires, Location.Kingdom, Traits.Randomizer | Traits.VariableVPs) { BaseCost = new Cost(3); } 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); supply.Empty(); var cards = new CardCollection { new KingsCastle() }; if (game.Players.Count > 2) cards.Add(new KingsCastle()); cards.Add(new GrandCastle()); cards.Add(new SprawlingCastle()); cards.Add(new OpulentCastle()); if (game.Players.Count > 2) cards.Add(new OpulentCastle()); cards.Add(new HauntedCastle()); cards.Add(new SmallCastle()); if (game.Players.Count > 2) cards.Add(new SmallCastle()); cards.Add(new CrumblingCastle()); cards.Add(new HumbleCastle()); if (game.Players.Count > 2) cards.Add(new HumbleCastle()); supply.AddTo(cards); } } public class CatapultRocks : Card { public CatapultRocks() : base(Categories.Action | Categories.Attack, Source.Empires, Location.Kingdom, Traits.PlusCoin | Traits.Gainer | Traits.ReactToGain | Traits.ReactToTrashing | Traits.Trasher | Traits.RemoveCurses | Traits.Terminal | Traits.PlusCurses | Traits.Discard | Traits.AffectOthers | Traits.DeckReduction | Traits.SplitPile | Traits.RemoveFromHand) { BaseCost = new Cost(3); } 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 Rocks(), new Rocks(), new Rocks(), new Rocks(), new Rocks(), new Catapult(), new Catapult(), new Catapult(), new Catapult(), new Catapult() }; supply.AddTo(cards); } } public class Catapult : Card { public Catapult() : base(Categories.Action | Categories.Attack, Source.Empires, Location.Special, Traits.PlusCoin | Traits.Trasher | Traits.RemoveCurses | Traits.Terminal | Traits.PlusCurses | Traits.Discard | Traits.AffectOthers | Traits.DeckReduction | Traits.RemoveFromHand) { BaseCost = new Cost(3); Benefit.Currency.Coin.Value = 1; } 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]); if (trashedCardCost >= new Coin(3)) { 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); } } if (resultTrash.Cards[0].Category.HasFlag(Categories.Treasure)) { 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; 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 ChariotRace : Card { public ChariotRace() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.PlusAction | Traits.PlusCard | Traits.ConditionalBenefit | Traits.Component | Traits.VPTokens | Traits.Cantrip) { BaseCost = new Cost(3); Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var card = player.Draw(DeckLocation.Revealed); if (card != null) player.AddCardToHand(player.RetrieveCardFrom(DeckLocation.Revealed, card)); // Get the player to my left var playerToLeft = player._Game.GetPlayerFromIndex(player, 1); playerToLeft.Draw(DeckLocation.Revealed); var ptlCard = playerToLeft.Revealed.FirstOrDefault(); playerToLeft.AddCardsToDeck(playerToLeft.RetrieveCardsFrom(DeckLocation.Revealed), DeckPosition.Top); if (card != null && ptlCard != null && player._Game.ComputeCost(card) > playerToLeft._Game.ComputeCost(ptlCard)) player.ReceiveBenefit(this, new CardBenefit { Currency = new Currency(1), VictoryPoints = 1 }); } } public class Charm : Card { public Charm() : base(Categories.Treasure, Source.Empires, Location.Kingdom, Traits.ConditionalBenefit | Traits.PlusCoin | Traits.PlusBuy | Traits.Gainer | Traits.ReactToBuy) { BaseCost = new Cost(5); Benefit.Currency.Coin.IsVariable = true; } public override void TearDown(IGame game) { if (Owner != null) { Owner.CardBought -= Player_CardBought; Owner.CardBuyFinished -= Player_ResetTrigger; } base.TearDown(game); } private void Player_CardBought(object sender, CardBuyEventArgs e) { var player = sender as IPlayer; // Already been cancelled -- don't need to process this one if (e.Cancelled || e.Resolvers.ContainsKey(TypeClass.Charm) || !e.Card.Type.IsSubclassOf(typeof(Card)) || e.HandledBy.Contains(this)) return; var gainCardCost = e.Game.ComputeCost(e.Card); var gainCardName = e.Card.Name; var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll( supply => supply.CanGain() && supply.CurrentCost == gainCardCost && supply.TopCard.Name != gainCardName )); if (gainableSupplies.Any()) e.Resolvers[TypeClass.Charm] = new CardBuyResolver(Owner, this, "Gain another card", Player_GainCard, false); } internal void Player_GainCard(IPlayer player, ref CardBuyEventArgs e) { var gainCardCost = e.Game.ComputeCost(e.Card); var gainCardName = e.Card.Name; var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll( supply => supply.CanGain() && supply.CurrentCost == gainCardCost && supply.TopCard.Name != gainCardName )); var choice = new Choice($"Gain a differently-named card costing {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); } internal void Player_ResetTrigger(object sender, CardBuyEventArgs e) { var player = sender as IPlayer; player.CardBought -= Player_CardBought; player.CardBuyFinished -= Player_ResetTrigger; } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); player.CardBought -= Player_CardBought; player.CardBuyFinished -= Player_ResetTrigger; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice(Resource.ChooseOne, this, this, new List { "+1 Buy and +2", "The next time you buy a card this turn, you may also gain a differently named card with the same cost" }, player); var result = player.MakeChoice(choice); if (result.Options.Contains("+1 Buy and +2")) player.ReceiveBenefit(this, new CardBenefit { Buys = 1, Currency = new Currency(2) }); if (result.Options.Contains("The next time you buy a card this turn, you may also gain a differently named card with the same cost")) { player.CardBought += Player_CardBought; player.CardBuyFinished += Player_ResetTrigger; } } } public class CityQuarter : Card { public CityQuarter() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.Component | Traits.PlusAction | Traits.PlusMultipleActions | Traits.PlusCard | Traits.ConditionalBenefit | Traits.Debts) { BaseCost = new Cost(debtCost: 8); Benefit.Actions = 2; } public override bool Finalize(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); if (base.Finalize(game, supply)) return true; foreach (var player in game.Players) if (!player.TokenPiles.ContainsKey(TypeClass.DebtToken)) player.TokenPiles[TypeClass.DebtToken] = new TokenCollection(); return false; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.ReturnHand(player.RevealHand()); player.ReceiveBenefit(this, new CardBenefit { Cards = player.Hand[Categories.Action].Count }); } } public class Crown : Card { private readonly List CardsPlayed = new List(); public Crown() : base(Categories.Action | Categories.Treasure, Source.Empires, Location.Kingdom, Traits.Multiplier) { BaseCost = new Cost(5); } public override bool CanCleanUp { // If Crown played a Duration card but the Duration card only did something once (e.g. Tactician), then Crown can be cleaned up get => CardsPlayed.All(cp => cp.CanCleanUpPlayed.Count(cup => !cup) < 2) && base.CanCleanUp; } public override void AddedTo(DeckLocation location, IPlayer player) { base.AddedTo(location, player); CardsPlayed.Clear(); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.Phase == PhaseEnum.Action || player.Phase == PhaseEnum.ActionTreasure) { var choice = new Choice(Resource.ChooseActionPlay2x, this, player.Hand[Categories.Action], ChoiceOutcome.Select, player, minimum: 0); var result = player.MakeChoice(choice); if (result.Cards.Any()) { var cardToPlayTwice = result.Cards[0]; CardsPlayed.Add(cardToPlayTwice); cardToPlayTwice.ModifiedBy = this; player.Actions++; var previousPlayerMode = player.PutCardIntoPlay(cardToPlayTwice); var logicalCard = cardToPlayTwice.LogicalCard; player.PlayCard(cardToPlayTwice.LogicalCard, previousPlayerMode); player.Actions++; previousPlayerMode = player.PutCardIntoPlay(cardToPlayTwice, Resource.AgainFromCard.Replace("{card}", ToString())); player.PlayCard(logicalCard, previousPlayerMode); } else player.PlayNothing(); } else if (player.Phase == PhaseEnum.Buy || player.Phase == PhaseEnum.BuyTreasure) { var choice = new Choice(Resource.ChooseTreasurePlay2x, this, player.Hand[Categories.Treasure], ChoiceOutcome.Select, player, minimum: 0); var result = player.MakeChoice(choice); if (result.Cards.Any()) { var card = result.Cards[0]; player.PlayCardInternal(card); player.PlayCardInternal(card, modifier: Resource.AgainFromCard.Replace("{card}", ToString())); } else player.PlayNothing(); } } protected override void ModifyDuration(IPlayer player, Card card) { base.ModifyDuration(player, card); base.ModifyDuration(player, card); } } public class CrumblingCastle : Castle { public CrumblingCastle() : base(Categories.Unknown, Traits.ReactToGain | Traits.ReactToTrashing | Traits.Gainer | Traits.Component | Traits.VPTokens) { BaseCost = new Cost(4); VictoryPoints = 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; player.Trashed += Player_Trashed; } } 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; player.Trashed -= Player_Trashed; } } private void Player_Trashed(object sender, TrashEventArgs e) { // Already being processed or been handled -- don't need to process this one if (!e.TrashedCards.Contains(this) || e.Resolvers.ContainsKey(TypeClass.CrumblingCastle) || e.HandledBy.Contains(this)) return; if (e.TrashedCards.Contains(PhysicalCard)) e.Resolvers[TypeClass.CrumblingCastle] = new TrashResolver(e.CurrentPlayer, this, Resource.Gain1VPAndSilver, Player_CardTrashed, true); } internal void Player_CardTrashed(IPlayer player, ref TrashEventArgs e) { GainTrashBenefit(player); e.HandledBy.Add(this); } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = TypeClass.CrumblingCastle.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, "Gain", Resource.Gain1VPAndSilver, Player_GainCrumblingCastle, true); } internal void Player_GainCrumblingCastle(IPlayer player, ref Players.CardGainEventArgs e) { GainTrashBenefit(player); e.HandledBy.Add(this); } private void GainTrashBenefit(IPlayer player) { player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = 1 }); player.Gain(player._Game.Table.Silver, this); } } public class Emporium : Card { public Emporium() : base(Categories.Action, Source.Empires, Location.Special, Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.Component | Traits.VPTokens | Traits.ConditionalBenefit | Traits.Cantrip) { BaseCost = new Cost(5); Benefit.Cards = 1; Benefit.Actions = 1; 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; } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var key = TypeClass.Emporium.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this)) return; var player = sender as IPlayer; if (player.InPlayAndSetAside[Categories.Action].Count >= 5) e.Resolvers[key] = new CardGainResolver(player, this, "GainVPs", "+2", Player_GainEmporium, true); } internal void Player_GainEmporium(IPlayer player, ref Players.CardGainEventArgs e) { player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = 2 }); e.HandledBy.Add(this); } } public class EncampmentPlunder : Card { public EncampmentPlunder() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.Randomizer | Traits.PlusCoin | Traits.VPTokens | Traits.Component | Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.Cantrip | Traits.SplitPile) { BaseCost = new Cost(2); } 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 Plunder(), new Plunder(), new Plunder(), new Plunder(), new Plunder(), new Encampment(), new Encampment(), new Encampment(), new Encampment(), new Encampment() }; supply.AddTo(cards); } } public class Encampment : Card { private bool _returnToSupply; public Encampment() : base(Categories.Action, Source.Empires, Location.Special, Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.Cantrip) { BaseCost = new Cost(2); Benefit.Cards = 2; 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.SetAside: player.CleaningUp += Player_CleaningUp; _returnToSupply = true; break; } } private void Player_CleaningUp(object sender, CleaningUpEventArgs cuea) { if (!cuea.CurrentPlayer.SetAside.Contains(PhysicalCard) || cuea.Resolvers.ContainsKey(TypeClass.Encampment)) return; if (_returnToSupply) cuea.Resolvers[TypeClass.Encampment] = new CleaningUpResolver( this, $"Return {PhysicalCard} to the Supply", (IPlayer player, ref CleaningUpEventArgs e) => { e.CardsMovements.Remove(e.CardsMovements.Find(cm => cm.Card == PhysicalCard)); var cardToReturn = e.CurrentPlayer.RetrieveCardFrom(DeckLocation.SetAside, PhysicalCard); player.Lose(cardToReturn); var supply = (ISupply)player._Game.Table[cardToReturn]; supply.AddTo(cardToReturn); player._Game.SendMessage(player, this, "Return", supply); }, true); } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); player.CleaningUp -= Player_CleaningUp; _returnToSupply = false; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var goldAndPlunders = player.Hand[c => c is Universal.Gold || c.Type == TypeClass.Plunder]; var choice = new Choice("You may reveal a Gold or Plunder card. Otherwise, set this aside and return it to the Supply at the start of Clean-up.", this, goldAndPlunders, ChoiceOutcome.Select, player, minimum: 0); var result = player.MakeChoice(choice); if (result.Cards.Count == 0) { if (player.InPlay.Contains(PhysicalCard)) { player.MoveInPlayToSetAside(c => c == PhysicalCard); player._Game.SendMessage(player, this, "SetAside", PhysicalCard); } } else { player.AddCardInto(DeckLocation.Revealed, player.RetrieveCardFrom(DeckLocation.Hand, result.Cards.First())); player.AddCardInto(DeckLocation.Hand, player.RetrieveCardFrom(DeckLocation.Revealed, result.Cards.First())); } } } public class Enchantress : Card { public Enchantress() : base(Categories.Action | Categories.Attack | Categories.Duration, Source.Empires, Location.Kingdom, Traits.PlusCard | Traits.AffectOthers | Traits.Terminal | Traits.NetCardDraw) { BaseCost = new Cost(3); DurationBenefit.Cards = 2; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) { player.CardFollowingInstructions -= Attackee_CardFollowingInstructions; player.TurnEnded -= Player_TurnEnded; player.TurnStarted -= Player_TurnStarted; } } 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; attackee.CardFollowingInstructions -= Attackee_CardFollowingInstructions; attackee.CardFollowingInstructions += Attackee_CardFollowingInstructions; } CanCleanUpPlayed.Add(false); player.TurnEnded += Player_TurnEnded; } private void Player_TurnEnded(object sender, TurnEndedEventArgs e) { e.Player.TurnEnded -= Player_TurnEnded; e.Player.TurnStarted += Player_TurnStarted; } private void Player_TurnStarted(object sender, TurnStartedEventArgs e) { var key = ToString(); if (!e.Resolvers.ContainsKey(key)) e.Resolvers[key] = new TurnStartedResolver(e.Player, this, Resource.ResolveCard.Replace("{card}", PhysicalCard.ToString()), Player_Action, true); } internal void Player_Action(IPlayer player, ref TurnStartedEventArgs e) { ResolveDuration(e.Player); CanCleanUpPlayed.Remove(false); foreach (var attackee in player._Game.Players) attackee.CardFollowingInstructions -= Attackee_CardFollowingInstructions; e.Player.TurnStarted -= Player_TurnStarted; } private void Attackee_CardFollowingInstructions(object sender, CardFollowingInstructionsEventArgs e) { var player = (IPlayer)sender; if (e.Cancelled || e.HandledBy.Contains(this) || !e.Card.Category.HasFlag(Categories.Action) || player._Game.ActivePlayer != player || player.CurrentTurn.CardsPlayed.Count != 1 || player.CurrentTurn.CardsPlayed.FirstOrDefault(c => c.Category.HasFlag(Categories.Action)) != e.Card ) return; e.Resolvers[TypeClass.Enchantress] = new CardFollowingInstructionsResolver( this, Resource.ResolveViaCard.Replace("{card}", Name), (IPlayer playerAction, ref CardFollowingInstructionsEventArgs eAction) => { eAction.Cancelled = true; playerAction.ReceiveBenefit(this, new CardBenefit { Cards = 1, Actions = 1 }); eAction.HandledBy.Add(this); }, true); } public override void ResolveDuration(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.ResolveDuration(player); var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); while (enumerator.MoveNext()) enumerator.Current.CardFollowingInstructions -= Attackee_CardFollowingInstructions; } } public class Engineer : Card { public Engineer() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.Gainer | Traits.Terminal | Traits.Trasher | Traits.DeckReduction | Traits.Component | Traits.Debts) { BaseCost = new Cost(debtCost: 4); } public override bool Finalize(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); if (base.Finalize(game, supply)) return true; foreach (var player in game.Players) if (!player.TokenPiles.ContainsKey(TypeClass.DebtToken)) player.TokenPiles[TypeClass.DebtToken] = new TokenCollection(); return false; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost <= new Coin(4))); var choice = new Choice(Resource.GainUpTo4, this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) player.Gain(result.Supply, this); if (player.InPlay.Contains(PhysicalCard)) { choice = Choice.CreateYesNoChoice("Do you want to trash this card to gain a card costing up to 4?", this, player); result = player.MakeChoice(choice); if (result.Options[0] == Resource.Yes) { player.Trash(this, player.RetrieveCardFrom(DeckLocation.InPlay, PhysicalCard)); gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost <= new Coin(4))); choice = new Choice(Resource.GainUpTo4, this, gainableSupplies, ChoiceOutcome.Gain, player, false); result = player.MakeChoice(choice); if (result.Supply != null) player.Gain(result.Supply, this); } } } } public class FarmersMarket : Card { public FarmersMarket() : base(Categories.Action | Categories.Gathering, Source.Empires, Location.Kingdom, Traits.PlusBuy | Traits.Terminal | Traits.Trasher | Traits.DeckReduction | Traits.ConditionalBenefit | Traits.Component | Traits.VPTokens) { BaseCost = new Cost(3); Benefit.Buys = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var fmSupply = (ISupply)player._Game.Table.TableEntities[TypeClass.FarmersMarket]; var victoryTokens = fmSupply.Tokens.OfType().ToList(); if (victoryTokens.Count >= 4) { victoryTokens.ForEach(t => fmSupply.RemoveToken(t)); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = victoryTokens.Count }); player.Trash(this, player.RetrieveCardFrom(DeckLocation.InPlay, PhysicalCard)); } else { fmSupply.AddToken(new Prosperity.VictoryToken()); player.ReceiveBenefit(this, new CardBenefit { Currency = new Currency(victoryTokens.Count + 1) }); } } } public class Fortune : Card { public Fortune() : base(Categories.Treasure, Source.Empires, Location.Special, Traits.PlusBuy | Traits.PlusCoin | Traits.ConditionalBenefit | Traits.Component | Traits.Debts | Traits.Gainer) { BaseCost = new Cost(coinCost: 8, debtCost: 8); Benefit.Buys = 1; Benefit.Currency.Coin.SpecialDisplay = "x2"; } 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 = TypeClass.Fortune.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, "GainGold", "Gain Gold per Gladiator", Player_GainFortune, true); } internal void Player_GainFortune(IPlayer player, ref Players.CardGainEventArgs e) { foreach (var gladiator in player.InPlayAndSetAside[TypeClass.Gladiator]) player.Gain(player._Game.Table.Gold, this); e.HandledBy.Add(this); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.CurrentTurn.CardsResolved.Count(c => c.LogicalCard.Type == TypeClass.Fortune) == 1) player.ReceiveBenefit(this, new CardBenefit { Currency = new Currency(player.Currency.Coin) }); } } public class Forum : Card { private readonly Dictionary _cardBoughtHandlers = new Dictionary(); public Forum() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.Discard | Traits.PlusBuy | Traits.ConditionalBenefit | Traits.Cantrip | Traits.ReactToBuy | Traits.NetCardDraw) { BaseCost = new Cost(5); Benefit.Cards = 3; Benefit.Actions = 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 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(TypeClass.Forum) || !e.Card.Type.IsSubclassOf(typeof(Card))) return; e.Resolvers[TypeClass.Forum] = new CardBuyResolver(Owner, this, "+1 Buy", Player_BuyForum, true); } internal void Player_BuyForum(IPlayer player, ref CardBuyEventArgs e) { player.ReceiveBenefit(this, new CardBenefit { Buys = 1 }); e.HandledBy.Add(TypeClass.Forum); // Clear out the Event Triggers -- this only happens when its Bought, so we don't care any more 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); 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); } } public class GladiatorFortune : Card { public GladiatorFortune() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.Randomizer | Traits.PlusBuy | Traits.PlusCoin | Traits.ConditionalBenefit | Traits.Component | Traits.Debts | Traits.Gainer | Traits.Trasher | Traits.AffectOthers | Traits.Terminal | Traits.SplitPile) { BaseCost = new Cost(3); } 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 Fortune(), new Fortune(), new Fortune(), new Fortune(), new Fortune(), new Gladiator(), new Gladiator(), new Gladiator(), new Gladiator(), new Gladiator() }; supply.AddTo(cards); } public override bool Finalize(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); if (base.Finalize(game, supply)) return true; foreach (var player in game.Players) if (!player.TokenPiles.ContainsKey(TypeClass.DebtToken)) player.TokenPiles[TypeClass.DebtToken] = new TokenCollection(); return false; } } public class Gladiator : Card { public Gladiator() : base(Categories.Action, Source.Empires, Location.Special, Traits.PlusCoin | Traits.Trasher | Traits.AffectOthers | Traits.ConditionalBenefit | Traits.Terminal) { BaseCost = new Cost(3); Benefit.Currency.Coin.Value = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choiceCard = new Choice(Resource.RevealCardFromHand, this, player.Hand, ChoiceOutcome.Select, player); var resultCard = player.MakeChoice(choiceCard); var playerToLeftRevealedCopy = false; if (resultCard.Cards.Any()) { var revealedCard = resultCard.Cards[0]; var shownCard = player.RetrieveCardFrom(DeckLocation.Hand, revealedCard.Type); player.AddCardInto(DeckLocation.Revealed, shownCard); player.AddCardInto(DeckLocation.Hand, player.RetrieveCardFrom(DeckLocation.Revealed, shownCard)); // Get the player to my left var playerToLeft = player._Game.GetPlayerFromIndex(player, 1); if (playerToLeft.Hand[revealedCard.Type].Any()) { var revealCopy = Choice.CreateYesNoChoice($"Reveal a copy of {revealedCard}?", this, player); var revealCopyResult = playerToLeft.MakeChoice(revealCopy); if (revealCopyResult.Options[0] == Resource.Yes) { playerToLeftRevealedCopy = true; var shownCardPtl = playerToLeft.RetrieveCardFrom(DeckLocation.Hand, revealedCard.Type); playerToLeft.AddCardInto(DeckLocation.Revealed, shownCardPtl); playerToLeft.AddCardInto(DeckLocation.Hand, playerToLeft.RetrieveCardFrom(DeckLocation.Revealed, shownCardPtl)); } } } if (!playerToLeftRevealedCopy) { player.ReceiveBenefit(this, new CardBenefit { Currency = new Currency(1) }); var gladiatorFortuneSupply = (ISupply)player._Game.Table[TypeClass.GladiatorFortune]; if (gladiatorFortuneSupply.TopCard != null && gladiatorFortuneSupply.TopCard.Type == TypeClass.Gladiator) player.Trash(this, gladiatorFortuneSupply); } } } public class GrandCastle : Castle { public GrandCastle() : base(Categories.Unknown, Traits.ReactToGain | Traits.Component | Traits.VPTokens) { BaseCost = new Cost(9); VictoryPoints = 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; } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = TypeClass.GrandCastle.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, "RevealHand", "Reveal your hand", Player_GainGrandCastle, true); } internal void Player_GainGrandCastle(IPlayer player, ref Players.CardGainEventArgs e) { player.ReturnHand(player.RevealHand()); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = player.Hand[Categories.Victory].Count + player.InPlayAndSetAside[Categories.Victory].Count }); e.HandledBy.Add(this); } } public class Groundskeeper : Card { private CardGainedEventHandler _cardGainedEventHandler; public Groundskeeper() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.Component | Traits.VPTokens) { 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 (_cardGainedEventHandler != null) player.CardGained -= _cardGainedEventHandler; _cardGainedEventHandler = new CardGainedEventHandler(Player_CardGained); player.CardGained += _cardGainedEventHandler; } } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { // Already been cancelled or processed -- don't need to process this one if (e.Cancelled || e.HandledBy.Contains(this)) return; e.HandledBy.Add(this); var player = (IPlayer)sender; if (e.Card.Category.HasFlag(Categories.Victory)) player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = 1 }); } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); if (_cardGainedEventHandler != null) player.CardGained -= _cardGainedEventHandler; _cardGainedEventHandler = null; } } public class HauntedCastle : Castle { public HauntedCastle() : base(Categories.Unknown, Traits.ReactToGain | Traits.Gainer | Traits.AffectOthers) { BaseCost = new Cost(6); VictoryPoints = 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 = TypeClass.HauntedCastle.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this) || e.Game.ActivePlayer != player) return; e.Resolvers[key] = new CardGainResolver(player, this, "GainGold", "Gain a Gold", Player_GainHauntedCastle, true); } internal void Player_GainHauntedCastle(IPlayer player, ref Players.CardGainEventArgs e) { player.Gain(e.Game.Table.Gold, this); var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); while (enumerator.MoveNext()) { var attackee = enumerator.Current; if (attackee.Hand.Count < 5) continue; var choice = new Choice("Put 2 cards on top of your deck.", this, attackee.Hand, ChoiceOutcome.Select, player, isOrdered: true, minimum: 2, maximum: 2); var result = attackee.MakeChoice(choice); attackee.RetrieveCardsFrom(DeckLocation.Hand, result.Cards); attackee.AddCardsToDeck(result.Cards, DeckPosition.Top); } e.HandledBy.Add(this); } } public class HumbleCastle : Castle { public HumbleCastle() : base(Categories.Treasure, Traits.PlusCoin | Traits.VariableVPs) { BaseCost = new Cost(3); Benefit.Currency.Coin.Value = 1; } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { return base.ComputeVictoryPoints(player, collection) + collection.Count(c => c.Category.HasFlag(Categories.Castle)); } } public class KingsCastle : Castle { public KingsCastle() : base(Categories.Unknown, Traits.VariableVPs) { BaseCost = new Cost(10); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { return base.ComputeVictoryPoints(player, collection) + 2 * collection.Count(c => c.Category.HasFlag(Categories.Castle)); } } public class Legionary : Card { public Legionary() : base(Categories.Action | Categories.Attack, Source.Empires, Location.Kingdom, Traits.PlusCoin | Traits.Terminal | Traits.AffectOthers) { BaseCost = new Cost(5); Benefit.Currency.Coin.Value = 3; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); if (player.Hand[Universal.TypeClass.Gold].Any()) { var choice = Choice.CreateYesNoChoice("You may reveal a Gold card to make your opponents discard. Do you want to reveal?", this, player); var result = player.MakeChoice(choice); if (result.Options.Contains(Resource.Yes)) { var singleGold = player.RetrieveCardFrom(DeckLocation.Hand, Universal.TypeClass.Gold); player.AddCardInto(DeckLocation.Revealed, singleGold); 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; choice = new Choice("Choose cards to discard. You must discard down to 2 cards in hand (then draw a card)", this, attackee.Hand, ChoiceOutcome.Discard, attackee, minimum: attackee.Hand.Count - 2, maximum: attackee.Hand.Count - 2); result = attackee.MakeChoice(choice); attackee.Discard(DeckLocation.Hand, result.Cards); attackee.Draw(DeckLocation.Hand); } player.AddCardsToHand(DeckLocation.Revealed); } } } } public class OpulentCastle : Castle { public OpulentCastle() : base(Categories.Action, Traits.Discard | Traits.ConditionalBenefit | Traits.PlusCoin | Traits.Terminal) { BaseCost = new Cost(7); VictoryPoints = 3; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choice = new Choice("Discard any number of Victory cards. +2 per card discarded.", this, player.Hand[Categories.Victory], ChoiceOutcome.Discard, player, minimum: 0, maximum: player.Hand[Categories.Victory].Count); var result = player.MakeChoice(choice); player.Discard(DeckLocation.Hand, result.Cards); var benefit = new CardBenefit(); benefit.Currency += new Coin(2 * result.Cards.Count); player.ReceiveBenefit(this, benefit); } } public class Overlord : Card { public Card ClonedCard { get; private set; } private DeckLocation? _currentLocation; public Overlord() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.ConditionalBenefit | Traits.Component | Traits.Debts) { BaseCost = new Cost(debtCost: 8); } public override bool Finalize(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); if (base.Finalize(game, supply)) return true; foreach (var player in game.Players) if (!player.TokenPiles.ContainsKey(TypeClass.DebtToken)) player.TokenPiles[TypeClass.DebtToken] = new TokenCollection(); return false; } public override void AddedTo(DeckLocation location, IPlayer player) { base.AddedTo(location, player); _currentLocation = location; if (ClonedCard == null) return; switch (location) { case DeckLocation.InPlay: case DeckLocation.SetAside: ClonedCard.AddedTo(location, player); break; } } public override void RemovedFrom(DeckLocation location, IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.RemovedFrom(location, player); _currentLocation = null; if (ClonedCard == null) return; switch (location) { case DeckLocation.InPlay: case DeckLocation.SetAside: case DeckLocation.InPlayAndSetAside: ClonedCard.PhysicalCard = null; ClonedCard.RemovedFrom(location, player); ClonedCard.TearDown(player._Game); ClonedCard = null; break; } } public override Card LogicalCard => ClonedCard != null ? ClonedCard.LogicalCard : base.LogicalCard; public override Categories Category => ClonedCard?.Category ?? base.Category; public override Source Source => ClonedCard?.Source ?? base.Source; public override Location Location => ClonedCard?.Location ?? base.Location; public override Traits Traits => ClonedCard?.Traits ?? base.Traits; public override Cost BaseCost => ClonedCard != null ? ClonedCard.BaseCost : base.BaseCost; public override CardBenefit Benefit => ClonedCard != null ? ClonedCard.Benefit : base.Benefit; public override bool SuppressBenefit => ClonedCard != null ? ClonedCard.SuppressBenefit : base.SuppressBenefit; public override CardBenefit DurationBenefit => ClonedCard != null ? ClonedCard.DurationBenefit : base.DurationBenefit; public override int VictoryPoints => ClonedCard?.VictoryPoints ?? base.VictoryPoints; public override Card ModifiedBy => ClonedCard != null ? ClonedCard.ModifiedBy : base.ModifiedBy; public override ItemCollection ChainedInto => ClonedCard != null ? ClonedCard.ChainedInto : base.ChainedInto; public override bool CanCleanUp => ClonedCard?.CanCleanUp ?? base.CanCleanUp; public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { if (ClonedCard != null) return ClonedCard.ComputeVictoryPoints(player, collection); return base.ComputeVictoryPoints(player, collection); } public override IEnumerable LookThrough(Predicate predicate) { if (ClonedCard != null) return ClonedCard.LookThrough(predicate); return base.LookThrough(predicate); } public override bool IsStackable => ClonedCard == null; public override DisplayableCollection Stack() { var cc = new DisplayableCollection { this }; if (ClonedCard != null) cc.AddRange(ClonedCard.Stack()); return cc; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); if (ClonedCard == null) { base.FollowInstructions(player); var choice = new Choice("Choose a card to clone this card as", this, new SupplyCollection(player._Game.Table.TableEntities.FindAll( supply => supply.Any() && supply.TopCard.Category.HasFlag(Categories.Action) && supply.CurrentCost <= new Cost(5))), ChoiceOutcome.Select, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) { player._Game.SendMessage(player, this, (Card)result.Supply.TopCard); ClonedCard = CreateInstance(result.Supply.TopCard.Type); ClonedCard.PhysicalCard = this; // This is needed in order to set up player mats & any other acutrements ClonedCard.ReceivedBy(player); // Add the card to this card's area ClonedCard.AddedTo(_currentLocation.GetValueOrDefault(DeckLocation.InPlay), player); } else player._Game.SendMessage(player, this); } if (ClonedCard != null) { player.Actions++; var previousPlayerMode = player.PutCardIntoPlay(ClonedCard); player.PlayCard(ClonedCard, previousPlayerMode); } } public override void ResolveDuration(IPlayer player) { ClonedCard?.ResolveDuration(player); } internal override XmlNode GenerateXml(XmlDocument doc, string nodeName) { var xn = base.GenerateXml(doc, nodeName); if (ClonedCard != null) xn.AppendChild(ClonedCard.GenerateXml(doc, "cloned_card")); return xn; } internal override void LoadInstance(XmlNode xnCard) { base.LoadInstance(xnCard); ClonedCard = (Card)Load(xnCard.SelectSingleNode("cloned_card")); } } public class PatricianEmporium : Card { public PatricianEmporium() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.Randomizer | Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.Component | Traits.VPTokens | Traits.ConditionalBenefit | Traits.Cantrip | Traits.SplitPile) { BaseCost = new Cost(2); } 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 Emporium(), new Emporium(), new Emporium(), new Emporium(), new Emporium(), new Patrician(), new Patrician(), new Patrician(), new Patrician(), new Patrician() }; supply.AddTo(cards); } } public class Patrician : Card { public Patrician() : base(Categories.Action, Source.Empires, Location.Special, Traits.PlusCard | Traits.PlusAction | Traits.ConditionalBenefit | Traits.Cantrip) { BaseCost = new Cost(2); Benefit.Cards = 1; Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var card = player.Draw(DeckLocation.Revealed); if (card != null) { if (player._Game.ComputeCost(card) >= new Coin(5)) player.AddCardToHand(player.RetrieveCardFrom(DeckLocation.Revealed, card)); else player.AddCardToDeck(player.RetrieveCardFrom(DeckLocation.Revealed, card), DeckPosition.Top); } } } public class Plunder : Card { public Plunder() : base(Categories.Treasure, Source.Empires, Location.Special, Traits.PlusCoin | Traits.VPTokens | Traits.Component) { BaseCost = new Cost(5); Benefit.Currency.Coin.Value = 2; Benefit.VictoryPoints = 1; } } public class Rocks : Card { public Rocks() : base(Categories.Treasure, Source.Empires, Location.Special, Traits.PlusCoin | Traits.Gainer | Traits.ReactToGain | Traits.ReactToTrashing) { BaseCost = new Cost(4); 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; player.Trashed += Player_Trashed; } } 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; player.Trashed -= Player_Trashed; } } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = TypeClass.Rocks.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, "GainSilver", "Gain Silver", Player_GainRocks, true); } internal void Player_GainRocks(IPlayer player, ref Players.CardGainEventArgs e) { HandleRocks(player); e.HandledBy.Add(this); } private void Player_Trashed(object sender, TrashEventArgs e) { // Already being processed or been handled -- don't need to process this one if (e.Resolvers.ContainsKey(TypeClass.Rocks) || e.HandledBy.Contains(this)) return; if (e.TrashedCards.Contains(PhysicalCard)) e.Resolvers[TypeClass.Rocks] = new TrashResolver(e.CurrentPlayer, this, "Gain Silver", Player_PlusCard, true); } internal void Player_PlusCard(IPlayer player, ref TrashEventArgs e) { HandleRocks(player); e.HandledBy.Add(this); } private void HandleRocks(IPlayer player) { if (player.Phase == PhaseEnum.Buy || player.Phase == PhaseEnum.BuyTreasure) player.Gain(player._Game.Table.Silver, this, DeckLocation.Deck, DeckPosition.Top); else player.Gain(player._Game.Table.Silver, this, DeckLocation.Hand); } } public class RoyalBlacksmith : Card { public RoyalBlacksmith() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.Component | Traits.PlusCard | Traits.Discard | Traits.Debts | Traits.Terminal | Traits.NetCardDraw) { BaseCost = new Cost(debtCost: 8); Benefit.Cards = 5; } public override bool Finalize(IGame game, ISupply supply) { Contract.Requires(game != null, "game cannot be null"); if (base.Finalize(game, supply)) return true; foreach (var player in game.Players) if (!player.TokenPiles.ContainsKey(TypeClass.DebtToken)) player.TokenPiles[TypeClass.DebtToken] = new TokenCollection(); return false; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); player.RevealHand(); player.Discard(DeckLocation.Revealed, c => c is Universal.Copper); player.AddCardsToHand(DeckLocation.Revealed); } } public class Sacrifice : Card { public Sacrifice() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.Trasher | Traits.DeckReduction | Traits.ConditionalBenefit | Traits.RemoveCurses | Traits.VPTokens | Traits.Component | Traits.RemoveFromHand) { 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); if (resultTrash.Cards.Any()) { var trashedCard = resultTrash.Cards[0]; player.Trash(this, player.RetrieveCardFrom(DeckLocation.Hand, trashedCard)); var benefit = new CardBenefit(); if (trashedCard.Category.HasFlag(Categories.Action)) { benefit.Actions = 2; benefit.Cards = 2; } if (trashedCard.Category.HasFlag(Categories.Treasure)) benefit.Currency += new Coin(2); if (trashedCard.Category.HasFlag(Categories.Victory)) benefit.VictoryPoints = 2; player.ReceiveBenefit(this, benefit); } } } public class SettlersBustlingVillage : Card { public SettlersBustlingVillage() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.Randomizer | Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.Cantrip | Traits.SplitPile) { BaseCost = new Cost(2); } 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 BustlingVillage(), new BustlingVillage(), new BustlingVillage(), new BustlingVillage(), new BustlingVillage(), new Settlers(), new Settlers(), new Settlers(), new Settlers(), new Settlers() }; supply.AddTo(cards); } } public class Settlers : Card { public Settlers() : base(Categories.Action, Source.Empires, Location.Special, Traits.PlusCard | Traits.PlusAction | Traits.Cantrip) { BaseCost = new Cost(2); Benefit.Cards = 1; Benefit.Actions = 1; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var discardPileCards = player.DiscardPile.LookThrough(c => true); player._Game.SendMessage(player, this, "LookThroughDiscard", discardPileCards.ToArray()); var copperCount = discardPileCards.Count(c => c is Universal.Copper); if (copperCount > 0) { var choice = Choice.CreateYesNoChoice("Do you want to reveal a Copper and put it into your hand?", this, player); var result = player.MakeChoice(choice); if (result.Options[0] == Resource.Yes) { player.AddCardsInto(DeckLocation.Revealed, player.RetrieveCardsFrom(DeckLocation.Discard, Universal.TypeClass.Copper, 1)); player.AddCardsToHand(DeckLocation.Revealed); } } } } public class SmallCastle : Castle { public SmallCastle() : base(Categories.Action, Traits.Trasher | Traits.Gainer | Traits.Terminal | Traits.RemoveFromHand) { BaseCost = new Cost(5); VictoryPoints = 2; } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); Card trashedCard = null; var choiceTrash = new Choice("You may trash a Castle from your hand (otherwise, trash this)", this, player.Hand[Categories.Castle], ChoiceOutcome.Trash, player, minimum: 0); var resultTrash = player.MakeChoice(choiceTrash); if (resultTrash.Cards.Any()) { trashedCard = resultTrash.Cards[0]; player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); } else if (player.InPlay.Contains(PhysicalCard)) { trashedCard = PhysicalCard; player.Trash(this, player.RetrieveCardFrom(DeckLocation.InPlay, PhysicalCard)); } if (trashedCard != null && ((ISupply)player._Game.Table[TypeClass.Castles]).CanGain()) player.Gain((ISupply)player._Game.Table[TypeClass.Castles], this); } } public class SprawlingCastle : Castle { public SprawlingCastle() : base(Categories.Unknown, Traits.ReactToGain | Traits.Gainer) { BaseCost = new Cost(8); VictoryPoints = 4; } 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 = TypeClass.SprawlingCastle.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, "GainVPCards", "Gain Duchy/Estates", Player_GainGrandCastle, true); } internal void Player_GainGrandCastle(IPlayer player, ref Players.CardGainEventArgs e) { var choice = new Choice(Resource.ChooseOne, this, this, new List { "Gain a Duchy", "Gain 3 Estates" }, player); var result = player.MakeChoice(choice); if (result.Options.Contains("Gain a Duchy")) player.Gain(player._Game.Table.Duchy, this); else player.Gain(player._Game.Table.Estate, this, 3); e.HandledBy.Add(this); } } public class Temple : Card { public Temple() : base(Categories.Action | Categories.Gathering, Source.Empires, Location.Kingdom, Traits.VPTokens | Traits.Trasher | Traits.RemoveCurses | Traits.DeckReduction | Traits.ReactToGain | Traits.Terminal | Traits.Component | Traits.RemoveFromHand) { BaseCost = new Cost(4); Benefit.VictoryPoints = 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; } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var key = TypeClass.Temple.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this)) return; var player = sender as IPlayer; var tSupply = (ISupply)player._Game.Table.TableEntities[TypeClass.Temple]; var victoryTokens = tSupply.Tokens.OfType(); e.Resolvers[key] = new CardGainResolver(player, this, "GainVPs", $"Gain {victoryTokens.Count()} off Temple Supply pile", Player_GainTemple, true); } internal void Player_GainTemple(IPlayer player, ref Players.CardGainEventArgs e) { var tSupply = (ISupply)player._Game.Table.TableEntities[TypeClass.Temple]; var victoryTokens = tSupply.Tokens.OfType().ToList(); foreach (var vpToken in victoryTokens) victoryTokens.ForEach(t => tSupply.RemoveToken(t)); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = victoryTokens.Count }); e.HandledBy.Add(this); } public override void FollowInstructions(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.FollowInstructions(player); var choiceTrash = new Choice("Trash 1 to 3 differently named cards", this, player.Hand.DistinctBy(c => c.Name), ChoiceOutcome.Trash, player, maximum: 3); var resultTrash = player.MakeChoice(choiceTrash); player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); var tSupply = (ISupply)player._Game.Table.TableEntities[TypeClass.Temple]; tSupply.AddToken(new Prosperity.VictoryToken()); } } public class Villa : Card { public Villa() : base(Categories.Action, Source.Empires, Location.Kingdom, Traits.PlusCoin | Traits.PlusAction | Traits.PlusMultipleActions | Traits.PlusBuy | Traits.ReactToGain) { BaseCost = new Cost(4); Benefit.Actions = 2; Benefit.Buys = 1; 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; } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = TypeClass.Villa.ToString(); // This is not the card you are looking for if (e.Card != this || e.Resolvers.ContainsKey(key) || e.HandledBy.Contains(this) || e.IsLostTrackOf) return; e.Resolvers[key] = new CardGainResolver(player, this, "PutCardIntoHand", $"Put {Name} into your hand", Player_GainVilla, true); } internal void Player_GainVilla(IPlayer player, ref Players.CardGainEventArgs e) { e.Cancelled = true; e.IsLostTrackOf = true; e.Location = DeckLocation.Hand; player.ReceiveBenefit(this, new CardBenefit { Actions = 1 }); if (player.Phase == PhaseEnum.Buy || player.Phase == PhaseEnum.BuyTreasure) player.GoToActionPhase(); e.HandledBy.Add(this); } } public class WildHunt : Card { public WildHunt() : base(Categories.Action | Categories.Gathering, Source.Empires, Location.Kingdom, Traits.VPTokens | Traits.Component | Traits.ConditionalBenefit | Traits.Terminal | Traits.Gainer | Traits.NetCardDraw) { 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(Resource.ChooseOne, this, this, new List { "+3 Cards and +1 to the Wild Hunt Supply pile", "Gain an Estate and on the Wild Hunt Supply pile" }, player); var result = player.MakeChoice(choice); var whSupply = (ISupply)player._Game.Table.TableEntities[TypeClass.WildHunt]; if (result.Options.Contains("+3 Cards and +1 to the Wild Hunt Supply pile")) { player.ReceiveBenefit(this, new CardBenefit { Cards = 3 }); whSupply.AddToken(new Prosperity.VictoryToken()); } else if (result.Options.Contains("Gain an Estate and on the Wild Hunt Supply pile")) { if (player.Gain(player._Game.Table.Estate, this)) { var victoryTokens = whSupply.Tokens.OfType().ToList(); foreach (var vpToken in victoryTokens) victoryTokens.ForEach(t => whSupply.RemoveToken(t)); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = victoryTokens.Count }); } } } } public class Advance : Event { public Advance() : base(Source.Empires, 0, Traits.Trasher | Traits.Gainer | Traits.RemoveFromHand) { } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); var choiceTrash = new Choice(Resource.ChooseToTrashOptional, this, player.Hand[Categories.Action], ChoiceOutcome.Trash, player, minimum: 0); var resultTrash = player.MakeChoice(choiceTrash); if (resultTrash.Cards.Any()) { player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll( supply => supply.CanGain() && supply.TopCard.Category.HasFlag(Categories.Action) && supply.CurrentCost <= new Coin(6) )); var choice = new Choice(Resource.GainUpTo6, this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) player.Gain(result.Supply, this); } } } public class Annex : Event { public Annex() : base(Source.Empires, new Cost(debtCost: 8), Traits.CardOrdering | Traits.Gainer | Traits.Component | Traits.Debts) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) if (!player.TokenPiles.ContainsKey(TypeClass.DebtToken)) player.TokenPiles[TypeClass.DebtToken] = new TokenCollection(); } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); var discardPileCards = player.DiscardPile.LookThrough(c => true); player._Game.SendMessage(player, this, "LookThroughDiscard", discardPileCards.ToArray()); var choice = new Choice("Choose up to 5 cards to leave in your discard pile (shuffling the rest into your deck)", this, discardPileCards, ChoiceOutcome.Select, player, minimum: 0, maximum: 5); var result = player.MakeChoice(choice); player.AddCardsToDeck(player.DiscardPile.Retrieve(player, c => !result.Cards.Contains(c)), DeckPosition.Top); player.ShuffleDrawPile(); player.Gain(player._Game.Table.Duchy, this); } } public class Banquet : Event { public Banquet() : base(Source.Empires, 3, Traits.Gainer) { } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); player.Gain(player._Game.Table.Copper, this); player.Gain(player._Game.Table.Copper, this); var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll( supply => supply.CanGain() && !supply.TopCard.Category.HasFlag(Categories.Victory) && supply.CurrentCost <= new Coin(5) )); var choice = new Choice("Gain a non-Victory card costing up to 5", this, gainableSupplies, ChoiceOutcome.Gain, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) player.Gain(result.Supply, this); } } public class Conquest : Event { public Conquest() : base(Source.Empires, 6, Traits.Gainer | Traits.Component | Traits.VPTokens) { } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); player.Gain(player._Game.Table.Silver, this); player.Gain(player._Game.Table.Silver, this); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = player.CurrentTurn.CardsGained.Count(c => c is Universal.Silver) }); } } public class Delve : Event { public Delve() : base(Source.Empires, 2, Traits.PlusBuy | Traits.Gainer) { Benefit.Buys = 1; } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); player.Gain(player._Game.Table.Silver, this); } } public class Dominate : Event { public Dominate() : base(Source.Empires, 14, Traits.Gainer | Traits.Component | Traits.VPTokens) { } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); if (player.Gain(player._Game.Table.Province, this)) player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = 9 }); } } public class Donate : Event { public Donate() : base(Source.Empires, new Cost(debtCost: 8), Traits.Trasher | Traits.DeckReduction | Traits.RemoveCurses | Traits.Component | Traits.Debts | Traits.RemoveFromHand) { } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); foreach (var player in game.Players) player.PhaseChanged -= Player_PhaseChanged; } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); player.PhaseChanged += Player_PhaseChanged; } private void Player_PhaseChanged(object sender, PhaseChangedEventArgs pcea) { if (pcea.NewPhase == PhaseEnum.Waiting && pcea.OldPhase == PhaseEnum.Cleanup) { if (!pcea.HandledBy.Contains(TypeClass.Donate)) pcea.Resolvers[TypeClass.Donate] = new PhaseChangedResolver( this, "Resolve Donate", (IPlayer player, ref PhaseChangedEventArgs e) => { // put all cards from your deck and discard pile into your hand player.AddCardsInto(DeckLocation.Hand, player.RetrieveCardsFrom(DeckLocation.Deck)); player.AddCardsInto(DeckLocation.Hand, player.RetrieveCardsFrom(DeckLocation.Discard)); // trash any number var choiceTrash = new Choice("Choose any number of cards to trash", this, player.Hand, ChoiceOutcome.Trash, player, minimum: 0, maximum: player.Hand.Count); var resultTrash = player.MakeChoice(choiceTrash); player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards)); // shuffle your hand into your deck player.AddCardsInto(DeckLocation.Deck, player.RetrieveCardsFrom(DeckLocation.Hand)); player.ShuffleDrawPile(); // then draw 5 cards player.DrawHand(5); player.PhaseChanged -= Player_PhaseChanged; e.HandledBy.Add(TypeClass.Donate); }, true ); } } } public class Ritual : Event { public Ritual() : base(Source.Empires, 4, Traits.PlusCurses | Traits.Trasher | Traits.Component | Traits.VPTokens | Traits.RemoveFromHand) { } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); if (player.Gain(player._Game.Table.Curse, this)) { var choice = new Choice(Resource.ChooseACardToTrash, this, player.Hand, ChoiceOutcome.Trash, player); var result = player.MakeChoice(choice); if (result.Cards.Any()) { player.Trash(this, player.RetrieveCardFrom(DeckLocation.Hand, result.Cards[0])); var cardCost = player._Game.ComputeCost(result.Cards[0]); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = cardCost.Coin.Value }); } } } } public class SaltTheEarth : Event { public SaltTheEarth() : base(Source.Empires, 4, Traits.Trasher | Traits.Component | Traits.VPTokens) { } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = 1 }); var trashableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll( supply => supply.TopCard != null && supply.TopCard.Category.HasFlag(Categories.Victory) )); var choice = new Choice("Trash a Victory card from the Supply", this, trashableSupplies, ChoiceOutcome.Trash, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) player.Trash(this, result.Supply); } } public class Tax : Event { public Tax() : base(Source.Empires, 2, Traits.Component | Traits.Debts) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); BeginChanges(); foreach (var supply in game.Table.TableEntities.Values.OfType()) supply.AddToken(new DebtToken()); EndChanges(); foreach (var player in game.Players) player.CardBought += Player_CardBought; } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); foreach (var player in game.Players) player.CardBought -= Player_CardBought; } 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 => true)); var choice = new Choice("Add 2 to a Supply pile", this, supplies, ChoiceOutcome.Select, player, false); var result = player.MakeChoice(choice); if (result.Supply != null) { player._Game.SendMessage(player, this, result.Supply); result.Supply.AddToken(new DebtToken()); result.Supply.AddToken(new DebtToken()); } } private void Player_CardBought(object sender, CardBuyEventArgs e) { if (e.From is ISupply supply) { supply.BeginChanges(); var tokens = supply.RemoveTokens(TypeClass.DebtToken); supply.EndChanges(); if (tokens.Any()) ((IPlayer)sender).AddTokens(tokens); } } } public class Triumph : Event { public Triumph() : base(Source.Empires, new Cost(debtCost: 5), Traits.Gainer | Traits.Component | Traits.VPTokens | Traits.Debts) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) if (!player.TokenPiles.ContainsKey(TypeClass.DebtToken)) player.TokenPiles[TypeClass.DebtToken] = new TokenCollection(); } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); if (player.Gain(player._Game.Table.Estate, this)) player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = player.CurrentTurn.CardsGained.Count }); } } public class Wedding : Event { public Wedding() : base(Source.Empires, new Cost(coinCost: 4, debtCost: 3), Traits.Gainer | Traits.Component | Traits.VPTokens | Traits.Debts) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) if (!player.TokenPiles.ContainsKey(TypeClass.DebtToken)) player.TokenPiles[TypeClass.DebtToken] = new TokenCollection(); } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = 1 }); player.Gain(player._Game.Table.Gold, this); } } public class Windfall : Event { public Windfall() : base(Source.Empires, 5, Traits.Gainer) { } public override void Bought(IPlayer player) { Contract.Requires(player != null, "player cannot be null"); base.Bought(player); if (player.DrawPile.Count == 0 && player.DiscardPile.Count == 0) { player.Gain(player._Game.Table.Gold, this, count: 3); } } } public class Aqueduct : Landmark { private readonly Dictionary _cardGainedHandlers = new Dictionary(); public Aqueduct() : base(Source.Empires, Traits.Component | Traits.VPTokens | Traits.ReactToGain) { } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _cardGainedHandlers.Keys) playerLoop.CardGained -= _cardGainedHandlers[playerLoop]; _cardGainedHandlers.Clear(); } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); BeginChanges(); for (var i = 0; i < 8; i++) { game.Table.Silver.AddToken(new Prosperity.VictoryToken()); game.Table.Gold.AddToken(new Prosperity.VictoryToken()); } EndChanges(); var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _cardGainedHandlers[enumPlayers.Current] = new CardGainedEventHandler(Player_CardGained); enumPlayers.Current.CardGained += _cardGainedHandlers[enumPlayers.Current]; } } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key1 = typeof(AqueductTreasure).ToString(); var key2 = typeof(AqueductVictory).ToString(); if (e.Card.Category.HasFlag(Categories.Treasure) && !e.HandledBy.Contains(typeof(AqueductTreasure)) && e.Game.Table[e.Card.Type].Tokens.Any(t => t is Prosperity.VictoryToken)) e.Resolvers[key1] = new CardGainResolver(player, this, "MoveVPs", $"Move token to {this}", Player_GainTreasure, true); if (e.Card.Category.HasFlag(Categories.Victory) && !e.HandledBy.Contains(typeof(AqueductVictory)) && e.Game.Table[TypeClass.Aqueduct].Tokens.Any(t => t is Prosperity.VictoryToken)) e.Resolvers[key2] = new CardGainResolver(player, this, "GainVPs", $"Gain tokens off {this}", Player_GainVictory, true); } internal void Player_GainTreasure(IPlayer player, ref Players.CardGainEventArgs e) { var token = e.Game.Table[e.Card.Type].RemoveToken(Prosperity.TypeClass.VictoryToken); e.Game.Table[TypeClass.Aqueduct].AddToken(token); e.HandledBy.Add(typeof(AqueductTreasure)); } internal void Player_GainVictory(IPlayer player, ref Players.CardGainEventArgs e) { BeginChanges(); var tokens = new TokenCollection(); do { tokens.Add(RemoveToken(Prosperity.TypeClass.VictoryToken)); } while (Tokens.Any(t => t is Prosperity.VictoryToken)); EndChanges(); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = tokens.Count }); e.HandledBy.Add(typeof(AqueductVictory)); } } // Dummy classes used by Action resolution, since Aqueduct has 2 triggers for the same event internal class AqueductTreasure { } internal class AqueductVictory { } public class Arena : Landmark { private readonly Dictionary _phaseChangingEventHandlers = new Dictionary(); public Arena() : base(Source.Empires, Traits.Component | Traits.VPTokens | Traits.ReactToGain) { } public override bool CanUndo => false; public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _phaseChangingEventHandlers.Keys) playerLoop.PhaseChanging -= _phaseChangingEventHandlers[playerLoop]; _phaseChangingEventHandlers.Clear(); } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); BeginChanges(); for (var i = 0; i < game.Players.Count * 6; i++) AddToken(new Prosperity.VictoryToken()); EndChanges(); var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _phaseChangingEventHandlers[enumPlayers.Current] = new PhaseChangingEventHandler(Player_PhaseChanging); enumPlayers.Current.PhaseChanging += _phaseChangingEventHandlers[enumPlayers.Current]; } } private void Player_PhaseChanging(object sender, PhaseChangingEventArgs e) { var player = sender as IPlayer; if (!e.HandledBy.Contains(TypeClass.Arena) && Tokens.Any(t => t is Prosperity.VictoryToken) && e.NewPhase == PhaseEnum.BuyTreasure && player.Hand[Categories.Action].Any()) e.Resolvers[TypeClass.Arena] = new PhaseChangingResolver(this, Resource.DiscardActionCard, Player_DiscardAction, false); } internal void Player_DiscardAction(IPlayer player, ref PhaseChangingEventArgs e) { var choice = new Choice(Resource.DiscardActionCard, this, player.Hand[Categories.Action], ChoiceOutcome.Discard, player, minimum: 0); var result = player.MakeChoice(choice); if (result.Cards.Any()) { player.Discard(DeckLocation.Hand, result.Cards); BeginChanges(); var tokens = new TokenCollection { RemoveToken(Prosperity.TypeClass.VictoryToken), RemoveToken(Prosperity.TypeClass.VictoryToken) }; EndChanges(); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = tokens.Count }); } e.HandledBy.Add(TypeClass.Arena); } } public class BanditFort : Landmark { public BanditFort() : base(Source.Empires, Traits.VariableVPs) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new BanditFort()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { return base.ComputeVictoryPoints(player, collection) - 2 * collection.Count(c => c is Universal.Silver || c is Universal.Gold); } } public class Basilica : Landmark { private readonly Dictionary _CardBoughtEventHandlers = new Dictionary(); public Basilica() : base(Source.Empires, Traits.Component | Traits.VPTokens | Traits.ReactToBuy) { } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _CardBoughtEventHandlers.Keys) playerLoop.CardBought -= _CardBoughtEventHandlers[playerLoop]; _CardBoughtEventHandlers.Clear(); } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); BeginChanges(); for (var i = 0; i < game.Players.Count * 6; i++) AddToken(new Prosperity.VictoryToken()); EndChanges(); var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _CardBoughtEventHandlers[enumPlayers.Current] = new CardBoughtEventHandler(Player_CardBought); enumPlayers.Current.CardBought += _CardBoughtEventHandlers[enumPlayers.Current]; } } private void Player_CardBought(object sender, CardBuyEventArgs e) { var player = sender as IPlayer; if (!e.HandledBy.Contains(TypeClass.Basilica) && Tokens.Any(t => t is Prosperity.VictoryToken) && player.Currency.Coin >= 2 && e.Card.Type.IsSubclassOf(typeof(Card))) e.Resolvers[TypeClass.Basilica] = new CardBuyResolver(player, e.Card, "Gain 2", Player_Action, true); } internal void Player_Action(IPlayer player, ref CardBuyEventArgs e) { BeginChanges(); var tokens = new TokenCollection { RemoveToken(Prosperity.TypeClass.VictoryToken), RemoveToken(Prosperity.TypeClass.VictoryToken) }; EndChanges(); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = tokens.Count }); e.HandledBy.Add(TypeClass.Basilica); } } public class Baths : Landmark { private readonly Dictionary _phaseChangingEventHandlers = new Dictionary(); public Baths() : base(Source.Empires, Traits.Component | Traits.VPTokens) { } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _phaseChangingEventHandlers.Keys) playerLoop.PhaseChanging -= _phaseChangingEventHandlers[playerLoop]; _phaseChangingEventHandlers.Clear(); } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); BeginChanges(); for (var i = 0; i < game.Players.Count * 6; i++) AddToken(new Prosperity.VictoryToken()); EndChanges(); var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _phaseChangingEventHandlers[enumPlayers.Current] = new PhaseChangingEventHandler(Player_PhaseChanging); enumPlayers.Current.PhaseChanging += _phaseChangingEventHandlers[enumPlayers.Current]; } } private void Player_PhaseChanging(object sender, PhaseChangingEventArgs e) { if (!e.HandledBy.Contains(TypeClass.Baths) && Tokens.Any(t => t is Prosperity.VictoryToken) && e.CurrentPhase == PhaseEnum.Cleanup && e.NewPhase == PhaseEnum.Waiting && e.CurrentPlayer.CurrentTurn.CardsGained.Count == 0) e.Resolvers[TypeClass.Baths] = new PhaseChangingResolver(this, "Gain 2", Player_Action, true); } internal void Player_Action(IPlayer player, ref PhaseChangingEventArgs e) { BeginChanges(); var tokens = new TokenCollection { RemoveToken(Prosperity.TypeClass.VictoryToken), RemoveToken(Prosperity.TypeClass.VictoryToken) }; EndChanges(); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = tokens.Count }); e.HandledBy.Add(TypeClass.Baths); } } public class Battlefield : Landmark { private readonly Dictionary _cardGainedHandlers = new Dictionary(); public Battlefield() : base(Source.Empires, Traits.ReactToGain | Traits.Component | Traits.VPTokens) { } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _cardGainedHandlers.Keys) playerLoop.CardGained -= _cardGainedHandlers[playerLoop]; _cardGainedHandlers.Clear(); } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); BeginChanges(); for (var i = 0; i < game.Players.Count * 6; i++) AddToken(new Prosperity.VictoryToken()); EndChanges(); var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _cardGainedHandlers[enumPlayers.Current] = new CardGainedEventHandler(Player_CardGained); enumPlayers.Current.CardGained += _cardGainedHandlers[enumPlayers.Current]; } } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = TypeClass.Battlefield.ToString(); if (e.Card.Category.HasFlag(Categories.Victory) && !e.HandledBy.Contains(TypeClass.Battlefield) && e.Game.Table[TypeClass.Battlefield].Tokens.Any(t => t is Prosperity.VictoryToken)) e.Resolvers[key] = new CardGainResolver(player, this, "GainVPs", "Gain 2 tokens off Battlefield", Player_GainVictory, true); } internal void Player_GainVictory(IPlayer player, ref Players.CardGainEventArgs e) { BeginChanges(); var tokens = new TokenCollection { RemoveToken(Prosperity.TypeClass.VictoryToken), RemoveToken(Prosperity.TypeClass.VictoryToken) }; EndChanges(); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = tokens.Count }); e.HandledBy.Add(TypeClass.Battlefield); } } public class Colonnade : Landmark { private readonly Dictionary _cardBoughtEventHandlers = new Dictionary(); public Colonnade() : base(Source.Empires, Traits.Component | Traits.VPTokens | Traits.ReactToBuy) { } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _cardBoughtEventHandlers.Keys) playerLoop.CardBought -= _cardBoughtEventHandlers[playerLoop]; _cardBoughtEventHandlers.Clear(); } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); BeginChanges(); for (var i = 0; i < game.Players.Count * 6; i++) AddToken(new Prosperity.VictoryToken()); EndChanges(); var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _cardBoughtEventHandlers[enumPlayers.Current] = new CardBoughtEventHandler(Player_CardBought); enumPlayers.Current.CardBought += _cardBoughtEventHandlers[enumPlayers.Current]; } } private void Player_CardBought(object sender, CardBuyEventArgs e) { var player = sender as IPlayer; if (!e.HandledBy.Contains(TypeClass.Colonnade) && Tokens.Any(t => t is Prosperity.VictoryToken) && e.Card.Category.HasFlag(Categories.Action) && player.InPlayAndSetAside[c => c.LogicalCard.Type == e.Card.Type].Any() && e.Card.Type.IsSubclassOf(typeof(Card))) e.Resolvers[TypeClass.Colonnade] = new CardBuyResolver(player, e.Card, "Gain 2", Player_Action, true); } internal void Player_Action(IPlayer player, ref CardBuyEventArgs e) { BeginChanges(); var tokens = new TokenCollection { RemoveToken(Prosperity.TypeClass.VictoryToken), RemoveToken(Prosperity.TypeClass.VictoryToken) }; EndChanges(); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = tokens.Count }); e.HandledBy.Add(TypeClass.Colonnade); } } public class DefiledShrine : Landmark { private readonly Dictionary _cardBoughtEventHandlers = new Dictionary(); private readonly Dictionary _cardGainedHandlers = new Dictionary(); public DefiledShrine() : base(Source.Empires, Traits.Component | Traits.VPTokens | Traits.ReactToGain | Traits.PlusCurses) { } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _cardBoughtEventHandlers.Keys) playerLoop.CardBought -= _cardBoughtEventHandlers[playerLoop]; _cardBoughtEventHandlers.Clear(); foreach (var playerLoop in _cardGainedHandlers.Keys) playerLoop.CardGained -= _cardGainedHandlers[playerLoop]; _cardGainedHandlers.Clear(); } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); BeginChanges(); foreach (var sAction in game.Table.TableEntities.Values.OfType()) { if (sAction.Randomizer.Category.HasFlag(Categories.Action) && !sAction.Randomizer.Category.HasFlag(Categories.Gathering)) { sAction.AddToken(new Prosperity.VictoryToken()); sAction.AddToken(new Prosperity.VictoryToken()); } } EndChanges(); var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _cardBoughtEventHandlers[enumPlayers.Current] = new CardBoughtEventHandler(Player_CardBought); enumPlayers.Current.CardBought += _cardBoughtEventHandlers[enumPlayers.Current]; _cardGainedHandlers[enumPlayers.Current] = new CardGainedEventHandler(Player_CardGained); enumPlayers.Current.CardGained += _cardGainedHandlers[enumPlayers.Current]; } } private void Player_CardBought(object sender, CardBuyEventArgs e) { var player = sender as IPlayer; if (!e.HandledBy.Contains(TypeClass.DefiledShrine) && Tokens.Any(t => t is Prosperity.VictoryToken) && e.Card.Type == Universal.TypeClass.Curse) e.Resolvers[TypeClass.DefiledShrine] = new CardBuyResolver(player, e.Card, $"Take from {this}", Player_TakeVP, true); } internal void Player_TakeVP(IPlayer player, ref CardBuyEventArgs e) { BeginChanges(); var tokens = new TokenCollection(); do { tokens.Add(RemoveToken(Prosperity.TypeClass.VictoryToken)); } while (Tokens.Any(t => t is Prosperity.VictoryToken)); EndChanges(); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = tokens.Count }); e.HandledBy.Add(TypeClass.DefiledShrine); } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; var key = TypeClass.DefiledShrine.ToString(); if (!e.HandledBy.Contains(TypeClass.DefiledShrine) && e.Game.Table[e.Card.Type].Tokens.Any(t => t is Prosperity.VictoryToken) && e.Card.Category.HasFlag(Categories.Action)) e.Resolvers[key] = new CardGainResolver(player, this, "MoveVPs", $"Move token to {this}", Player_GainAction, true); } internal void Player_GainAction(IPlayer player, ref Players.CardGainEventArgs e) { var token = e.Game.Table[e.Card.Type].RemoveToken(Prosperity.TypeClass.VictoryToken); e.Game.Table[TypeClass.DefiledShrine].AddToken(token); e.HandledBy.Add(TypeClass.DefiledShrine); } } public class Fountain : Landmark { public Fountain() : base(Source.Empires, Traits.VariableVPs) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new Fountain()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { return base.ComputeVictoryPoints(player, collection) + collection.Count(c => c.Type == Universal.TypeClass.Copper) >= 10 ? 15 : 0; } } public class Keep : Landmark { public Keep() : base(Source.Empires, Traits.VariableVPs) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new Keep()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { var iPointsList = collection as IList ?? collection.ToList(); var vps = base.ComputeVictoryPoints(player, iPointsList); if (player == null) return vps; foreach (var treasureGroup in iPointsList.Where(c => c.Category.HasFlag(Categories.Treasure)).GroupBy(c => c.Type)) { var most = true; var enumerator = player._Game.GetPlayersStartingWithEnumerator(player); enumerator.MoveNext(); while (enumerator.MoveNext()) { if (treasureGroup.Count() < enumerator.Current.CountAll(player, c => c.Type == treasureGroup.Key, false)) { most = false; break; } } if (most) vps += 5; } return vps; } } public class Labyrinth : Landmark { private readonly Dictionary _cardGainedHandlers = new Dictionary(); public Labyrinth() : base(Source.Empires, Traits.Component | Traits.VPTokens | Traits.ReactToGain) { } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _cardGainedHandlers.Keys) playerLoop.CardGained -= _cardGainedHandlers[playerLoop]; _cardGainedHandlers.Clear(); } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); BeginChanges(); for (var i = 0; i < game.Players.Count * 6; i++) AddToken(new Prosperity.VictoryToken()); EndChanges(); var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _cardGainedHandlers[enumPlayers.Current] = new CardGainedEventHandler(Player_CardGained); enumPlayers.Current.CardGained += _cardGainedHandlers[enumPlayers.Current]; } } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { if (!(sender is IPlayer player)) return; var key = TypeClass.Labyrinth.ToString(); if (!e.HandledBy.Contains(TypeClass.Labyrinth) && e.Game.Table[TypeClass.Labyrinth].Tokens.Any(t => t is Prosperity.VictoryToken) && player._Game.ActivePlayer == player && player.CurrentTurn.CardsGained.Count == 2 && player.CurrentTurn.CardsGained[1] == e.Card) e.Resolvers[key] = new CardGainResolver(player, this, "GainVPs", "Gain 2 tokens off Labyrinth", Player_Gain2ndCard, true); } internal void Player_Gain2ndCard(IPlayer player, ref Players.CardGainEventArgs e) { BeginChanges(); var tokens = new TokenCollection { RemoveToken(Prosperity.TypeClass.VictoryToken), RemoveToken(Prosperity.TypeClass.VictoryToken) }; EndChanges(); player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = tokens.Count }); e.HandledBy.Add(TypeClass.Labyrinth); } } public class MountainPass : Landmark { public MountainPass() : base(Source.Empires, Traits.Component | Traits.VPTokens | Traits.ReactToGain | Traits.Debts | Traits.AffectOthers) { } public override void TearDown(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.TearDown(game); ClearTriggers(game); } private void ClearTriggers(IGame game) { foreach (var player in game.Players) { player.CardGained -= Player_CardGained; player.PhaseChanged -= Player_PhaseChanged; } } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); BeginChanges(); for (var i = 0; i < 8; i++) AddToken(new Prosperity.VictoryToken()); EndChanges(); foreach (var player in game.Players) player.CardGained += Player_CardGained; } private void Player_CardGained(object sender, Players.CardGainEventArgs e) { var player = sender as IPlayer; if (!e.HandledBy.Contains(TypeClass.MountainPass) && e.Card is Universal.Province && player.CurrentTurn.CardsGained.Count(c => c is Universal.Province) == 1) player.PhaseChanged += Player_PhaseChanged; } private void Player_PhaseChanged(object sender, PhaseChangedEventArgs e) { if (e.NewPhase == PhaseEnum.Waiting && e.OldPhase == PhaseEnum.Cleanup) { if (!e.HandledBy.Contains(TypeClass.MountainPass)) e.Resolvers[TypeClass.MountainPass] = new PhaseChangedResolver(this, "Bid on 8", Player_TurnEnded, true); } } private void Player_TurnEnded(object sender, ref PhaseChangedEventArgs e) { var player = sender as IPlayer; Debt maxBid = null; IPlayer maxBidder = null; var enumPlayers = player._Game.GetPlayersStartingWithEnumerator(player._Game.GetPlayerFromIndex(player, 1)); while (enumPlayers.MoveNext()) { var options = new List { "Pass" }; for (var i = (maxBid == null ? 0 : maxBid.Value) + 1; i <= 40; i++) options.Add($"{i}"); var choice = new Choice("Bid on 8", this, this, options, enumPlayers.Current); var result = enumPlayers.Current.MakeChoice(choice); if (result.Options[0] == "Pass") player._Game.SendMessage(enumPlayers.Current, this); else { var currency = new Currency(result.Options[0]); player._Game.SendMessage(enumPlayers.Current, this, currency); maxBid = currency.Debt; maxBidder = enumPlayers.Current; // We can't get higher than 40 if (maxBid.Value == 40) break; } } if (maxBidder != null) { BeginChanges(); var vpTokens = new TokenCollection(); do { vpTokens.Add(RemoveToken(Prosperity.TypeClass.VictoryToken)); } while (Tokens.Any(t => t is Prosperity.VictoryToken)); EndChanges(); maxBidder.ReceiveBenefit(this, new CardBenefit { VictoryPoints = vpTokens.Count }); if (!maxBidder.TokenPiles.ContainsKey(TypeClass.DebtToken)) maxBidder.TokenPiles[TypeClass.DebtToken] = new TokenCollection(); var tokens = new TokenCollection(); for (var i = 0; i < maxBid.Value; i++) tokens.Add(new DebtToken()); maxBidder.AddTokens(tokens); } ClearTriggers(player._Game); e.HandledBy.Add(TypeClass.MountainPass); } } public class Museum : Landmark { public Museum() : base(Source.Empires, Traits.VariableVPs) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new Museum()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { var iPointsList = collection as IList ?? collection.ToList(); return base.ComputeVictoryPoints(player, iPointsList) + 2 * iPointsList.OfType().DistinctBy(c => c.Name).Count(); } } public class ObeliskMarker : Token { public ObeliskMarker() : base("O", "Obelisk marker") { } public override string Title => "This marks the Supply pile randomly chosen by Obelisk"; } public class Obelisk : Landmark { public Obelisk() : base(Source.Empires, Traits.VariableVPs) { } public override void Setup() { base.Setup(); var actionPiles = _Game.Table.TableEntities.Where(te => te.Value.Category.HasFlag(Categories.Action)).ToList(); var chosenPile = actionPiles.Choose(); _Game.Table.TableEntities[chosenPile.Key].AddToken(new ObeliskMarker()); } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new Obelisk()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { var iPointsList = collection as IList ?? collection.ToList(); var vps = base.ComputeVictoryPoints(player, iPointsList); if (player == null) return vps; var obeliskPile = player._Game.Table.TableEntities.Values.FirstOrDefault(te => te.Tokens.Any(t => t is ObeliskMarker)); return vps + 2 * iPointsList.OfType().Count(c => obeliskPile != null && obeliskPile.Types.Contains(c.Type)); } } public class Orchard : Landmark { public Orchard() : base(Source.Empires, Traits.VariableVPs) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new Orchard()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { var iPointsList = collection as IList ?? collection.ToList(); return base.ComputeVictoryPoints(player, iPointsList) + 4 * iPointsList.Where(c => c.Category.HasFlag(Categories.Action)).GroupBy(c => c.Type).Count(k => k.Count() >= 3); } } public class Palace : Landmark { public Palace() : base(Source.Empires, Traits.VariableVPs) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new Palace()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { var iPointsList = collection as IList ?? collection.ToList(); return base.ComputeVictoryPoints(player, iPointsList) + 3 * Math.Min(iPointsList.Count(c => c is Universal.Copper), Math.Min(iPointsList.Count(c => c is Universal.Silver), iPointsList.Count(c => c is Universal.Gold))); } } public class Tomb : Landmark { private readonly Dictionary _trashedEventHandlers = new Dictionary(); public Tomb() : base(Source.Empires, Traits.ReactToTrashing | Traits.Component | Traits.VPTokens) { } public override void TearDown(IGame game) { base.TearDown(game); foreach (var playerLoop in _trashedEventHandlers.Keys) playerLoop.Trashed -= _trashedEventHandlers[playerLoop]; _trashedEventHandlers.Clear(); } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); var enumPlayers = game.GetPlayersStartingWithActiveEnumerator(); while (enumPlayers.MoveNext()) { _trashedEventHandlers[enumPlayers.Current] = new TrashedEventHandler(Player_Trashed); enumPlayers.Current.Trashed += _trashedEventHandlers[enumPlayers.Current]; } } private void Player_Trashed(object sender, TrashEventArgs e) { var player = sender as IPlayer; if (!e.HandledBy.Contains(TypeClass.Tomb)) e.Resolvers[TypeClass.Tomb] = new TrashResolver(player, this, "Gain 1", Player_Trashed, true); } internal void Player_Trashed(IPlayer player, ref TrashEventArgs e) { player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = e.TrashedCards.Count(c => c.Category.HasFlag(Categories.Card)) }); e.HandledBy.Add(TypeClass.Tomb); } } public class Tower : Landmark { public Tower() : base(Source.Empires, Traits.VariableVPs) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new Tower()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { if (player == null) return 0; var emptyPiles = player._Game.Table.TableEntities.Values.Where(t => !t.Category.HasFlag(Categories.Shelter) && t.Count == 0); var iPointsList = collection as IList ?? collection.ToList(); return base.ComputeVictoryPoints(player, iPointsList) + iPointsList.OfType() .Count( c => !c.Category.HasFlag(Categories.Victory) && !c.Category.HasFlag(Categories.Shelter) && emptyPiles.Any(e => e.Types.Contains(c.Type))); } } public class TriumphalArch : Landmark { public TriumphalArch() : base(Source.Empires, Traits.VariableVPs) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new TriumphalArch()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { var iPointsList = collection as IList ?? collection.ToList(); var typeGroups = iPointsList.Where(c => c.Category.HasFlag(Categories.Action)).GroupBy(c => c.Type); var actionType = typeGroups.OrderByDescending(g => g.Count()).Skip(1).FirstOrDefault(); return base.ComputeVictoryPoints(player, iPointsList) + 3 * (actionType?.Count() ?? 0); } } public class Wall : Landmark { public Wall() : base(Source.Empires, Traits.VariableVPs) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new Wall()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { var iPointsList = collection as IList ?? collection.ToList(); return base.ComputeVictoryPoints(player, iPointsList) - Math.Max(0, iPointsList.OfType().Count() - 15); } } public class WolfDen : Landmark { public WolfDen() : base(Source.Empires, Traits.VariableVPs) { } public override void Finalize(IGame game) { Contract.Requires(game != null, "game cannot be null"); base.Finalize(game); foreach (var player in game.Players) player.EndgamePile.Add(new WolfDen()); } public override int ComputeVictoryPoints(IPlayer player, IEnumerable collection) { var iPointsList = collection as IList ?? collection.ToList(); return base.ComputeVictoryPoints(player, iPointsList) - 3 * iPointsList.OfType().GroupBy(c => c.Type).Count(g => g.Count() == 1); } } }