using DominionBase.Currencies;
using DominionBase.Enums;
using DominionBase.Piles;
using DominionBase.Players;
using DominionBase.Properties;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
namespace DominionBase.Cards.Prosperity
{
public static class TypeClass
{
public static readonly Type Bank = typeof(Bank);
public static readonly Type Bishop = typeof(Bishop);
public static readonly Type City = typeof(City);
public static readonly Type Colony = typeof(Colony);
public static readonly Type Contraband = typeof(Contraband);
public static readonly Type CountingHouse = typeof(CountingHouse);
public static readonly Type Expand = typeof(Expand);
public static readonly Type Forge = typeof(Forge);
public static readonly Type Goons = typeof(Goons);
public static readonly Type GrandMarket = typeof(GrandMarket);
public static readonly Type Hoard = typeof(Hoard);
public static readonly Type KingsCourt = typeof(KingsCourt);
public static readonly Type Loan = typeof(Loan);
public static readonly Type Mint = typeof(Mint);
public static readonly Type Monument = typeof(Monument);
public static readonly Type Mountebank = typeof(Mountebank);
public static readonly Type Peddler = typeof(Peddler);
public static readonly Type Platinum = typeof(Platinum);
public static readonly Type Quarry = typeof(Quarry);
public static readonly Type Rabble = typeof(Rabble);
public static readonly Type RoyalSeal = typeof(RoyalSeal);
public static readonly Type Talisman = typeof(Talisman);
public static readonly Type TradeRoute = typeof(TradeRoute);
public static readonly Type Vault = typeof(Vault);
public static readonly Type Venture = typeof(Venture);
public static readonly Type Watchtower = typeof(Watchtower);
public static readonly Type WorkersVillage = typeof(WorkersVillage);
public static readonly Type ContrabandToken = typeof(ContrabandMarker);
public static readonly Type TradeRouteToken = typeof(TradeRouteToken);
public static readonly Type VictoryToken = typeof(VictoryToken);
}
public class Bank : Card
{
public Bank()
: base(Categories.Treasure, Source.Prosperity, Location.Kingdom, Traits.PlusCoin | Traits.ConditionalBenefit)
{
BaseCost = new Cost(7);
Benefit.Currency.Coin.IsVariable = true;
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
var benefit = new CardBenefit();
benefit.Currency += new Coin(player.InPlay[Categories.Treasure].Count);
player.ReceiveBenefit(this, benefit);
}
}
public class Bishop : Card
{
public Bishop() : this(Edition.First) { }
public Bishop(Edition edition)
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.Component | Traits.DeckReduction | Traits.PlusCoin | Traits.Trasher | Traits.RemoveCurses | Traits.AffectOthers | Traits.Terminal | Traits.ConditionalBenefit | Traits.VPTokens | Traits.TrashForBenefit | Traits.RemoveFromHand, edition: edition)
{
BaseCost = new Cost(4);
Benefit.Currency.Coin.Value = 1;
Benefit.VictoryPoints = 1;
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
var choice = new Choice("Trash a card. + equal to half its cost in coins, rounded down.", this, player.Hand, ChoiceOutcome.Trash, player);
var result = player.MakeChoice(choice);
if (result.Cards.Any())
{
var trash = player.RetrieveCardFrom(DeckLocation.Hand, result.Cards[0]);
var trashedCardCost = player._Game.ComputeCost(trash);
player.Trash(this, trash);
player.ReceiveBenefit(this, new CardBenefit { VictoryPoints = trashedCardCost.Coin.Value / 2 });
}
var enumerator = player._Game.GetPlayersStartingWithEnumerator(player);
enumerator.MoveNext(); // skip active player
while (enumerator.MoveNext())
{
var otherPlayer = enumerator.Current;
var choicePlayer = new Choice("Trash a card if you wish", this, otherPlayer.Hand, ChoiceOutcome.Trash, otherPlayer, minimum: 0);
var resultPlayer = otherPlayer.MakeChoice(choicePlayer);
if (resultPlayer.Cards.Any())
{
otherPlayer.Trash(this, otherPlayer.RetrieveCardFrom(DeckLocation.Hand, resultPlayer.Cards[0]));
}
}
}
}
public class City : Card
{
public City() : this(Edition.First) { }
public City(Edition edition)
: base(Categories.Action,
Source.Prosperity,
Location.Kingdom,
Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.PlusCoin | Traits.PlusBuy | Traits.ConditionalBenefit | Traits.Cantrip,
edition: edition)
{
BaseCost = new Cost(5);
Benefit.Actions = 2;
Benefit.Cards = 1;
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
var emptyPiles = player._Game.Table.EmptySupplyPiles;
if (emptyPiles > 0)
{
player.ReceiveBenefit(this, new CardBenefit { Cards = 1 });
if (emptyPiles > 1)
player.ReceiveBenefit(this, new CardBenefit { Currency = new Currency(1), Buys = 1 });
}
}
}
public class Colony : Card
{
public Colony()
: base(Categories.Victory, Source.Prosperity, Location.General)
{
BaseCost = new Cost(11);
VictoryPoints = 10;
}
public override bool IsEndgameTriggered(ISupply supply)
{
Contract.Requires(supply != null, "supply cannot be null");
return (supply.Count == 0);
}
}
public class Contraband : Card
{
private IPlayer _turnEndedPlayer;
private TurnEndedEventHandler _turnEndedEventHandler;
private readonly Dictionary _buyCheckEventHandlers = new Dictionary();
public Contraband()
: base(Categories.Treasure, Source.Prosperity, Location.Kingdom, Traits.PlusBuy | Traits.PlusCoin)
{
BaseCost = new Cost(5);
Benefit.Buys = 1;
Benefit.Currency.Coin.Value = 3;
}
public override void TearDown(IGame game)
{
base.TearDown(game);
foreach (var supply in _buyCheckEventHandlers.Keys.OfType())
supply.BuyCheck -= _buyCheckEventHandlers[supply];
_buyCheckEventHandlers.Clear();
if (_turnEndedEventHandler != null && _turnEndedPlayer != null)
Player_TurnEnded(_turnEndedPlayer, new TurnEndedEventArgs(_turnEndedPlayer));
_turnEndedEventHandler = null;
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
if (_turnEndedEventHandler == null)
{
_turnEndedPlayer = player;
_turnEndedEventHandler = new TurnEndedEventHandler(Player_TurnEnded);
player.TurnEnded += _turnEndedEventHandler;
}
// Get the player to my left
var playerToLeft = player._Game.GetPlayerFromIndex(player, 1);
var availableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(
kvp =>
(kvp.Randomizer != null
&& !kvp.Randomizer.Traits.HasFlag(Traits.Randomizer))
|| kvp.Location == Location.General
));
var cards = new Dictionary();
var choice = new Choice($"Name a card {player} can't buy", this, availableSupplies, ChoiceOutcome.Select, player, false);
foreach (var supply in player._Game.Table.TableEntities.Values.OfType())
{
foreach (var type in supply.Types)
{
if (choice.Supplies.All(kvp => kvp.Value is ISupply kvpSupply && kvpSupply.Type != type))
{
if (!cards.ContainsKey(supply))
cards[supply] = new CardCollection();
cards[supply].Add(CreateInstance(type));
}
}
}
choice.AddCards(cards.SelectMany(s => s.Value).OrderBy(c => c.Name));
var result = playerToLeft.MakeChoice(choice);
IDisplayable namedCard = result.Supply;
var namedSupply = result.Supply;
if (namedSupply == null)
{
namedCard = result.Cards[0];
namedSupply = cards.First(s => s.Value.Contains(namedCard)).Key;
}
player._Game.SendMessage(playerToLeft, this, namedCard);
if (!_buyCheckEventHandlers.ContainsKey(namedCard))
{
namedSupply.AddToken(new ContrabandMarker(namedCard));
_buyCheckEventHandlers[namedCard] = new BuyCheckEventHandler(Supply_BuyCheck);
namedSupply.BuyCheck += _buyCheckEventHandlers[namedCard];
}
}
private void Supply_BuyCheck(object sender, BuyCheckEventArgs e)
{
// Already been cancelled -- don't need to process this one
if (e.Cancelled)
return;
var supply = (ISupply)sender;
var contrabandTokens = supply.Tokens.OfType();
if (contrabandTokens.Any(c => c.Unbuyable.Name == supply.TopCard.Name))
e.Cancelled = true;
}
private void Player_TurnEnded(object sender, TurnEndedEventArgs e)
{
var player = sender as IPlayer;
if (_turnEndedEventHandler != null && _turnEndedPlayer != null)
_turnEndedPlayer.TurnEnded -= _turnEndedEventHandler;
_turnEndedPlayer = null;
_turnEndedEventHandler = null;
foreach (var namedCard in _buyCheckEventHandlers.Keys)
{
var supply = namedCard as ISupply ??
player._Game.Table.TableEntities.Values.OfType().First(s => s.Types.Contains(namedCard.Type));
supply.BuyCheck -= _buyCheckEventHandlers[namedCard];
}
_buyCheckEventHandlers.Clear();
}
}
public class ContrabandMarker : Token
{
public IDisplayable Unbuyable { get; }
public ContrabandMarker()
: base("X", "Unbuyable")
{
}
public ContrabandMarker(IDisplayable unbuyable)
: this()
{
Unbuyable = unbuyable;
}
public override string Title => $"{Unbuyable.Name} cannot be bought this turn";
public override bool IsTemporary => true;
}
public class CountingHouse : Card
{
public CountingHouse() : this(Edition.First) { }
public CountingHouse(Edition edition)
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.PlusCard | Traits.Terminal, edition: edition)
{
BaseCost = new Cost(5);
}
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);
var options = new List();
for (var i = 0; i <= copperCount; i++)
options.Add(i.ToString(CultureInfo.InvariantCulture));
var choice = new Choice("How many Copper cards would you like to reveal and put into your hand?", this, this, options, player);
var result = player.MakeChoice(choice);
var number = int.Parse(result.Options[0], CultureInfo.InvariantCulture);
player.AddCardsInto(DeckLocation.Revealed, player.RetrieveCardsFrom(DeckLocation.Discard, Universal.TypeClass.Copper, number));
player.AddCardsToHand(DeckLocation.Revealed);
}
}
public class Expand : Card
{
public Expand() : this(Edition.First) { }
public Expand(Edition edition)
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.Gainer | Traits.Trasher | Traits.RemoveCurses | Traits.Terminal | Traits.TrashForBenefit | Traits.RemoveFromHand, edition: edition)
{
BaseCost = new Cost(7);
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
var choiceTrash = new Choice(Resource.ChooseACardToTrash, this, player.Hand, ChoiceOutcome.Trash, player);
var resultTrash = player.MakeChoice(choiceTrash);
player.Trash(this, player.RetrieveCardsFrom(DeckLocation.Hand, resultTrash.Cards));
if (resultTrash.Cards.Any())
{
var trashedCardCost = player._Game.ComputeCost(resultTrash.Cards[0]);
var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost <= trashedCardCost + new Coin(3)));
var choice = new Choice(Resource.GainCard, this, gainableSupplies, ChoiceOutcome.Gain, player, false);
var result = player.MakeChoice(choice);
if (result.Supply != null)
player.Gain(result.Supply, this);
}
}
}
public class Forge : Card
{
public Forge() : this(Edition.First) { }
public Forge(Edition edition)
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.DeckReduction | Traits.Gainer | Traits.Trasher | Traits.RemoveCurses | Traits.Terminal | Traits.TrashForBenefit | Traits.RemoveFromHand, edition: edition)
{
BaseCost = new Cost(7);
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
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));
var totalCoinCost = new Coin();
foreach (var card in resultTrash.Cards)
totalCoinCost += player._Game.ComputeCost(card).Coin;
var gainableSupplies = new SupplyCollection(player._Game.Table.TableEntities.FindAll(supply => supply.CanGain() && supply.CurrentCost == new Cost(totalCoinCost)));
var choice = new Choice(Resource.GainCard, this, gainableSupplies, ChoiceOutcome.Gain, player, false);
var result = player.MakeChoice(choice);
if (result.Supply != null)
player.Gain(result.Supply, this);
}
}
public class Goons : Card
{
private CardBoughtEventHandler _cardBoughtHandler;
public Goons() : this(Edition.First) { }
public Goons(Edition edition)
: base(Categories.Action | Categories.Attack, Source.Prosperity, Location.Kingdom, Traits.Component | Traits.PlusCoin | Traits.PlusBuy | Traits.Discard | Traits.Terminal | Traits.VPTokens | Traits.ReactToBuy, edition: edition)
{
BaseCost = new Cost(6);
Benefit.Buys = 1;
Benefit.Currency.Coin.Value = 2;
}
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;
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 override void AddedTo(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.AddedTo(location, player);
if (location == DeckLocation.InPlay)
{
if (_cardBoughtHandler != null)
player.CardBought -= _cardBoughtHandler;
_cardBoughtHandler = new CardBoughtEventHandler(Player_CardBought);
player.CardBought += _cardBoughtHandler;
}
}
private void Player_CardBought(object sender, CardBuyEventArgs e)
{
// Already been cancelled or processed -- don't need to process this one
if (e.Cancelled || e.HandledBy.Contains(this) || !e.Card.Type.IsSubclassOf(typeof(Card)))
return;
e.HandledBy.Add(this);
(sender as IPlayer).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 (_cardBoughtHandler != null)
player.CardBought -= _cardBoughtHandler;
_cardBoughtHandler = null;
}
}
public class GrandMarket : Card
{
public GrandMarket() : this(Edition.First) { }
public GrandMarket(Edition edition)
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.PlusBuy | Traits.Cantrip, edition: edition)
{
BaseCost = new Cost(6);
Benefit.Actions = 1;
Benefit.Cards = 1;
Benefit.Buys = 1;
Benefit.Currency.Coin.Value = 2;
}
public override bool CanBuy(IPlayer player, Currency currency)
{
Contract.Requires(player != default(IPlayer), "player cannot be null");
if (player.InPlay[Universal.TypeClass.Copper].Count > 0)
return false;
return base.CanBuy(player, currency);
}
}
public class Hoard : Card
{
private CardBoughtEventHandler _cardBoughtHandler;
public Hoard()
: base(Categories.Treasure, Source.Prosperity, Location.Kingdom, Traits.Gainer | Traits.PlusCoin | Traits.ReactToBuy)
{
BaseCost = new Cost(6);
Benefit.Currency.Coin.Value = 2;
}
protected override bool AllowUndo => true;
public override void AddedTo(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.AddedTo(location, player);
if (location == DeckLocation.InPlay)
{
if (_cardBoughtHandler != null)
player.CardBought -= _cardBoughtHandler;
_cardBoughtHandler = new CardBoughtEventHandler(Player_CardBought);
player.CardBought += _cardBoughtHandler;
}
}
private void Player_CardBought(object sender, CardBuyEventArgs e)
{
if (e.HandledBy.Contains(this) || e.Resolvers.ContainsKey(Type) || !e.Card.Category.HasFlag(Categories.Victory) || !e.Game.Table.Gold.CanGain() || !e.Card.Type.IsSubclassOf(typeof(Card)))
return;
e.Resolvers[Type] = new CardBuyResolver(Owner, this, "Gain a Gold", Player_BuyWithHoard, true);
}
internal void Player_BuyWithHoard(IPlayer player, ref CardBuyEventArgs e)
{
player.Gain(e.Game.Table.Gold, this);
e.HandledBy.Add(this);
}
public override void RemovedFrom(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.RemovedFrom(location, player);
if (_cardBoughtHandler != null)
player.CardBought -= _cardBoughtHandler;
_cardBoughtHandler = null;
}
}
public class KingsCourt : Card
{
private readonly List CardsPlayed = new List();
public KingsCourt() : this(Edition.First) { }
public KingsCourt(Edition edition)
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.Multiplier, edition: edition)
{
BaseCost = new Cost(7);
}
public override void AddedTo(DeckLocation location, IPlayer player)
{
base.AddedTo(location, player);
CardsPlayed.Clear();
}
public override bool CanCleanUp
{
// If King's Court played a Duration card but the Duration card only did something once (e.g. Tactician), then King's Court can be cleaned up
get => CardsPlayed.All(cp => cp.CanCleanUpPlayed.Count(cup => !cup) < 2) && base.CanCleanUp;
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
var choice = new Choice(Resource.ChooseActionPlay3x, this, player.Hand[Categories.Action], ChoiceOutcome.Select, player, minimum: 0);
var result = player.MakeChoice(choice);
if (result.Cards.Any())
{
var cardToPlayThrice = result.Cards[0];
CardsPlayed.Add(cardToPlayThrice);
cardToPlayThrice.ModifiedBy = this;
player.Actions++;
var previousPlayerMode = player.PutCardIntoPlay(cardToPlayThrice);
var logicalCard = cardToPlayThrice.LogicalCard;
player.PlayCard(cardToPlayThrice.LogicalCard, previousPlayerMode);
player.Actions++;
previousPlayerMode = player.PutCardIntoPlay(cardToPlayThrice, Resource.AgainFromCard.Replace("{card}", this.ToString()));
player.PlayCard(logicalCard, previousPlayerMode);
player.Actions++;
previousPlayerMode = player.PutCardIntoPlay(cardToPlayThrice, Resource.AgainFromCard.Replace("{card}", this.ToString()));
player.PlayCard(logicalCard, previousPlayerMode);
}
else
player.PlayNothing();
}
protected override void ModifyDuration(IPlayer player, Card card)
{
base.ModifyDuration(player, card);
base.ModifyDuration(player, card);
base.ModifyDuration(player, card);
}
}
public class Loan : Card
{
public Loan() : this(Edition.First) { }
public Loan(Edition edition)
: base(Categories.Treasure, Source.Prosperity, Location.Kingdom, Traits.DeckReduction | Traits.Trasher | Traits.PlusCoin | Traits.Discard, edition: edition)
{
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);
player.BeginDrawing();
while (player.Revealed[Categories.Treasure].Count < 1 && player.CanDraw)
player.Draw(DeckLocation.Revealed);
player.EndDrawing();
var treasureCards = player.Revealed[c => c.Category.HasFlag(Categories.Treasure)];
if (treasureCards.Any())
{
var card = treasureCards[0];
var discardText = Resource.DiscardCard.Replace("{card}", card.ToString());
var trashText = Resource.TrashCard.Replace("{card}", card.ToString());
var choice = new Choice($"You revealed a {card.Name}.", this, card, new List { discardText, trashText },
player);
var result = player.MakeChoice(choice)?.Options.FirstOrDefault();
if (result == discardText)
player.Discard(DeckLocation.Revealed, card);
else if (result == trashText)
player.Trash(this, player.RetrieveCardFrom(DeckLocation.Revealed, card));
}
player.DiscardRevealed();
}
}
public class Mint : Card
{
private readonly Dictionary _cardBoughtHandlers = new Dictionary();
public Mint()
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.DeckReduction | Traits.Gainer | Traits.Trasher | Traits.Terminal | Traits.ReactToBuy)
{
BaseCost = new Cost(5);
}
public override void TearDown(IGame game)
{
base.TearDown(game);
foreach (var playerLoop in _cardBoughtHandlers.Keys)
playerLoop.CardBought -= _cardBoughtHandlers[playerLoop];
_cardBoughtHandlers.Clear();
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
if (player.Hand[Categories.Treasure].Any())
{
var choice = new Choice("You may reveal a Treasure card to gain a copy of it.", this, player.Hand[Categories.Treasure], ChoiceOutcome.Gain, player, minimum: 0);
var result = player.MakeChoice(choice);
if (result.Cards.Any())
{
player.AddCardInto(DeckLocation.Revealed, player.RetrieveCardFrom(DeckLocation.Hand, result.Cards[0]));
player.AddCardInto(DeckLocation.Hand, player.RetrieveCardFrom(DeckLocation.Revealed, result.Cards[0]));
var supply = player._Game.Table.FindSupplyPileByCard(result.Cards[0]);
if (supply?.TopCard != null && supply.TopCard.Name == result.Cards[0].Name)
player.Gain(supply, this);
}
}
}
public override void AddedToSupply(IGame game, ISupply supply)
{
Contract.Requires(game != null, "game cannot be null");
base.AddedToSupply(game, supply);
ResetTriggers(game);
}
private void ResetTriggers(IGame game)
{
var enumPlayers = game.GetPlayersStartingWithActiveEnumerator();
while (enumPlayers.MoveNext())
{
_cardBoughtHandlers[enumPlayers.Current] = new CardBoughtEventHandler(Player_CardBought);
enumPlayers.Current.CardBought += _cardBoughtHandlers[enumPlayers.Current];
}
}
private void Player_CardBought(object sender, CardBuyEventArgs e)
{
// This is not the card you are looking for
if (e.Card != this || e.Resolvers.ContainsKey(Type) || !e.Card.Type.IsSubclassOf(typeof(Card)))
return;
e.Resolvers[Type] = new CardBuyResolver(Owner, this, "Trash all Treasures in play", Player_BuyMint, true);
}
internal void Player_BuyMint(IPlayer player, ref CardBuyEventArgs e)
{
player.Trash(this, player.RetrieveCardsFrom(DeckLocation.InPlay, Categories.Treasure));
player.Trash(this, player.RetrieveCardsFrom(DeckLocation.SetAside, Categories.Treasure));
e.HandledBy.Add(Type);
// Clear out the Event Triggers -- this only happens when its Gained, so we don't care any more
foreach (var playerLoop in _cardBoughtHandlers.Keys)
playerLoop.CardBought -= _cardBoughtHandlers[playerLoop];
_cardBoughtHandlers.Clear();
}
}
public class Monument : Card
{
public Monument()
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.Component | Traits.PlusCoin | Traits.Terminal | Traits.VPTokens)
{
BaseCost = new Cost(4);
Benefit.Currency.Coin.Value = 2;
Benefit.VictoryPoints = 1;
}
protected override bool AllowUndo => true;
}
public class Mountebank : Card
{
public Mountebank() : this(Edition.First) { }
public Mountebank(Edition edition)
: base(Categories.Action | Categories.Attack, Source.Prosperity, Location.Kingdom, Traits.PlusCurses | Traits.PlusCoin | Traits.Discard | Traits.Terminal, edition: edition)
{
BaseCost = new Cost(5);
Benefit.Currency.Coin.Value = 2;
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
// Perform attack on every other 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;
var discardedCurse = false;
if (attackee.Hand[Categories.Curse].Any())
{
var choice = new Choice("Do you want to discard a Curse or gain a Curse and a Copper?", this, this, new List() { "Discard Curse", "Gain Curse & Copper" }, attackee);
var result = attackee.MakeChoice(choice);
if (result.Options[0] == "Discard Curse")
{
discardedCurse = true;
attackee.Discard(DeckLocation.Hand, Universal.TypeClass.Curse, 1);
}
}
if (!discardedCurse)
{
attackee.Gain(player._Game.Table.Curse, this);
attackee.Gain(player._Game.Table.Copper, this);
}
}
}
}
public class Peddler : Card
{
public Peddler()
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusCoin | Traits.ModifyCost | Traits.VariableCost | Traits.Cantrip)
{
BaseCost = new Cost(8, special: true);
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);
game.CostCompute += Peddler_CostCompute;
}
public override void TearDown(IGame game)
{
Contract.Requires(game != null, "game cannot be null");
base.TearDown(game);
game.CostCompute -= Peddler_CostCompute;
}
private void Peddler_CostCompute(object sender, CostComputeEventArgs e)
{
if (e.Card != this)
return;
var game = sender as IGame;
if (game.ActivePlayer != null &&
(game.ActivePlayer.Phase == PhaseEnum.Buy || game.ActivePlayer.Phase == PhaseEnum.BuyTreasure))
{
var actionCardsInPlay = game.ActivePlayer.InPlay[Categories.Action].Count + game.ActivePlayer.SetAside[Categories.Action].Count;
e.Cost.Coin -= 2 * actionCardsInPlay;
}
}
}
public class Platinum : Card
{
public Platinum()
: base(Categories.Treasure, Source.Prosperity, Location.General)
{
BaseCost = new Cost(9);
Benefit.Currency.Coin.Value = 5;
}
protected override bool AllowUndo => true;
}
public class Quarry : Card
{
private CostComputeEventHandler _costComputeEventHandler;
public Quarry()
: base(Categories.Treasure, Source.Prosperity, Location.Kingdom, Traits.ModifyCost | Traits.PlusCoin)
{
BaseCost = new Cost(4);
Benefit.Currency.Coin.Value = 1;
}
protected override bool AllowUndo => true;
public override void AddedTo(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.AddedTo(location, player);
if (location == DeckLocation.InPlay)
{
if (_costComputeEventHandler != null)
player._Game.CostCompute -= _costComputeEventHandler;
_costComputeEventHandler = new CostComputeEventHandler(Player_QuarryInPlayArea);
player._Game.CostCompute += _costComputeEventHandler;
player._Game.SendMessage(player, this, 2);
}
}
private void Player_QuarryInPlayArea(object sender, CostComputeEventArgs e)
{
if (e.Card.Category.HasFlag(Categories.Action))
e.Cost.Coin -= 2;
}
public override void RemovedFrom(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.RemovedFrom(location, player);
if (_costComputeEventHandler != null)
player._Game.CostCompute -= _costComputeEventHandler;
_costComputeEventHandler = null;
}
}
public class Rabble : Card
{
public Rabble() : this(Edition.First) { }
public Rabble(Edition edition)
: base(Categories.Action | Categories.Attack, Source.Prosperity, Location.Kingdom, Traits.CardOrdering | Traits.PlusCard | Traits.Discard | Traits.Terminal | Traits.NetCardDraw, edition: edition)
{
BaseCost = new Cost(5);
Benefit.Cards = 3;
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
// Perform attack on every other 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.Draw(3, DeckLocation.Revealed);
Predicate actionsTreasures = (c =>
c.Category.HasFlag(Categories.Action) ||
c.Category.HasFlag(Categories.Treasure));
attackee.Discard(DeckLocation.Revealed, actionsTreasures);
var replaceChoice = new Choice(Resource.ChooseOrderToTopdeck, this, attackee.Revealed, ChoiceOutcome.Select, player, isOrdered: true, minimum: attackee.Revealed.Count, maximum: attackee.Revealed.Count);
var replaceResult = attackee.MakeChoice(replaceChoice);
attackee.AddCardsToDeck(attackee.RetrieveCardsFrom(DeckLocation.Revealed, replaceResult.Cards), DeckPosition.Top);
}
}
}
public class RoyalSeal : Card
{
private CardGainedEventHandler _cardGainedHandler;
public RoyalSeal() : this(Edition.First) { }
public RoyalSeal(Edition edition)
: base(Categories.Treasure, Source.Prosperity, Location.Kingdom, Traits.CardOrdering | Traits.PlusCoin, edition: edition)
{
BaseCost = new Cost(5);
Benefit.Currency.Coin.Value = 2;
}
protected override bool AllowUndo => true;
public override void AddedTo(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.AddedTo(location, player);
if (location == DeckLocation.InPlay)
{
if (_cardGainedHandler != null)
player.CardGained -= _cardGainedHandler;
_cardGainedHandler = new CardGainedEventHandler(Player_CardGained);
player.CardGained += _cardGainedHandler;
}
}
private void Player_CardGained(object sender, Players.CardGainEventArgs e)
{
var player = sender as IPlayer;
var key = Type.ToString();
// Already been cancelled -- don't need to process this one
if (e.Cancelled || e.Resolvers.ContainsKey(key))
return;
e.Resolvers[key] = new CardGainResolver(player, this, "PutOnDeck", Resource.PutOnDeck, Player_Action, false);
}
internal void Player_Action(IPlayer player, ref Players.CardGainEventArgs e)
{
e.Cancelled = true;
e.Location = DeckLocation.Deck;
e.Position = DeckPosition.Top;
e.HandledBy.Add(Type);
}
public override void RemovedFrom(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.RemovedFrom(location, player);
if (_cardGainedHandler != null)
player.CardGained -= _cardGainedHandler;
_cardGainedHandler = null;
}
}
public class Talisman : Card
{
private CardBoughtEventHandler _CardBoughtHandler;
public Talisman() : this(Edition.First) { }
public Talisman(Edition edition)
: base(Categories.Treasure, Source.Prosperity, Location.Kingdom, Traits.Gainer | Traits.PlusCoin | Traits.ReactToBuy, edition: edition)
{
BaseCost = new Cost(4);
Benefit.Currency.Coin.Value = 1;
}
protected override bool AllowUndo => true;
public override void AddedTo(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.AddedTo(location, player);
if (location == DeckLocation.InPlay)
{
if (_CardBoughtHandler != null)
player.CardBought -= _CardBoughtHandler;
_CardBoughtHandler = new CardBoughtEventHandler(Player_CardBought);
player.CardBought += _CardBoughtHandler;
}
}
private void Player_CardBought(object sender, CardBuyEventArgs e)
{
// Already been cancelled -- don't need to process this one
if (e.Cancelled)
return;
if (e.HandledBy.Contains(this) || e.Resolvers.ContainsKey(Type) || !e.Card.Type.IsSubclassOf(typeof(Card)))
return;
if (!e.Card.Category.HasFlag(Categories.Victory) && ((ISupply)e.Game.Table[e.Card]).CurrentCost <= new Coin(4))
{
e.Resolvers[Type] = new CardBuyResolver(Owner, this, $"Gain a copy of {e.Card}", Player_GainFromTalisman, true);
}
}
internal void Player_GainFromTalisman(IPlayer player, ref CardBuyEventArgs e)
{
var supply = (ISupply)e.Game.Table[e.Card.Type];
if (supply != null && supply.CanGain() && supply.TopCard.Name == e.Card.Name)
player.Gain((ISupply)e.Game.Table[e.Card.Type], this);
e.HandledBy.Add(this);
}
public override void RemovedFrom(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.RemovedFrom(location, player);
if (_CardBoughtHandler != null)
player.CardBought -= _CardBoughtHandler;
_CardBoughtHandler = null;
}
}
public class TradeRoute : Card
{
public TradeRoute()
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.Component | Traits.DeckReduction | Traits.PlusCoin | Traits.PlusBuy | Traits.Trasher | Traits.RemoveCurses | Traits.Terminal | Traits.ConditionalBenefit | Traits.RemoveFromHand, edition: Edition.First)
{
BaseCost = new Cost(3);
Benefit.Buys = 1;
}
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 sVictory in game.Table.TableEntities.Values.OfType())
{
if (sVictory.Randomizer.Category.HasFlag(Categories.Victory))
sVictory.AddToken(new TradeRouteToken());
}
game.Table.TokenPiles[TypeClass.TradeRouteToken] = new TokenCollection();
return false;
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
var benefit = new CardBenefit();
benefit.Currency += new Coin(player._Game.Table.TokenPiles[TypeClass.TradeRouteToken].Count);
player.ReceiveBenefit(this, benefit);
var choice = new Choice(Resource.TrashACard, 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]));
}
}
public class TradeRouteToken : Token
{
public bool Used { get; private set; }
public TradeRouteToken()
: base("TR", "Trade Route token")
{
}
public override bool Gaining()
{
if (!Used)
{
Used = true;
return true;
}
return false;
}
public override string Title => "Once a card is gained from this supply pile, this token will get added to the Trade Route Mat";
}
public class Vault : Card
{
public Vault() : this(Edition.First) { }
public Vault(Edition edition)
: base(
Categories.Action, Source.Prosperity, Location.Kingdom,
Traits.PlusCard | Traits.PlusCoin | Traits.Discard | Traits.AffectOthers | Traits.Terminal |
Traits.ConditionalBenefit | Traits.NetCardDraw, edition: edition)
{
BaseCost = new Cost(5);
Benefit.Cards = 2;
}
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 cards. +1 per card discarded.", this, player.Hand, ChoiceOutcome.Discard, player, minimum: 0, maximum: player.Hand.Count);
var result = player.MakeChoice(choice);
player.Discard(DeckLocation.Hand, result.Cards);
var benefit = new CardBenefit();
benefit.Currency += new Coin(result.Cards.Count);
player.ReceiveBenefit(this, benefit);
var enumerator = player._Game.GetPlayersStartingWithEnumerator(player);
enumerator.MoveNext(); // skip active player
while (enumerator.MoveNext())
{
var otherPlayer = enumerator.Current;
if (otherPlayer.Hand.Count >= 2)
{
var choicePlayer = Choice.CreateYesNoChoice("Do you want to discard 2 cards to draw 1 card?", this, otherPlayer);
var resultPlayer = otherPlayer.MakeChoice(choicePlayer);
if (resultPlayer.Options[0] == Resource.Yes)
{
var choiceDiscard = new Choice(Resource.Discard2Cards, this, otherPlayer.Hand, ChoiceOutcome.Discard, otherPlayer, minimum: 2, maximum: 2);
var discards = otherPlayer.MakeChoice(choiceDiscard);
otherPlayer.Discard(DeckLocation.Hand, discards.Cards);
if (otherPlayer.CanDraw)
otherPlayer.DrawHand(1);
}
}
}
Benefit.Currency.Coin.Value = 0;
}
}
public class Venture : Card
{
public Venture()
: base(Categories.Treasure, Source.Prosperity, Location.Kingdom, Traits.PlusCard | Traits.PlusCoin | Traits.Discard)
{
BaseCost = new Cost(5);
Benefit.Currency.Coin.Value = 1;
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
player.BeginDrawing();
while (player.Revealed[Categories.Treasure].Count < 1 && player.CanDraw)
player.Draw(DeckLocation.Revealed);
player.EndDrawing();
var cards = player.Revealed[Categories.Treasure];
player.DiscardRevealed(c => !cards.Contains(c));
if (cards.Any())
player.PlayCardInternal(cards[0]);
}
}
public class VictoryToken : Token
{
public VictoryToken()
: base("", "Victory Point chit")
{
}
public override string Title => "Worth 1 at the end of the game";
}
public class Watchtower : Card
{
private CardGainedEventHandler _CardGainedHandler;
public Watchtower() : this(Edition.First) { }
public Watchtower(Edition edition)
: base(Categories.Action | Categories.Reaction,
Source.Prosperity,
Location.Kingdom,
Traits.ReactToGain | Traits.Defense | Traits.CardOrdering | Traits.PlusCard | Traits.Trasher | Traits.Terminal | Traits.NetCardDraw,
edition: edition)
{
BaseCost = new Cost(3);
}
public override void FollowInstructions(IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.FollowInstructions(player);
while (player.Hand.Count < 6 && player.CanDraw)
player.DrawHand(1);
}
public override void AddedTo(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.AddedTo(location, player);
if (location == DeckLocation.Hand)
{
if (_CardGainedHandler != null)
player.CardGained -= _CardGainedHandler;
_CardGainedHandler = new CardGainedEventHandler(Player_CardGained);
player.CardGained += _CardGainedHandler;
}
}
private void Player_CardGained(object sender, Players.CardGainEventArgs e)
{
var player = sender as IPlayer;
var key = Type.ToString();
// Already been cancelled -- don't need to process this one
// If the card has been "lost track of", then we can skip revealing it
// We also need to make sure we're in the player's hand and we can be revealed
if (e.Cancelled || e.IsLostTrackOf || !player.Hand.Contains(PhysicalCard) || e.Resolvers.ContainsKey(key))
return;
e.Resolvers[key] = new CardGainResolver(player, this, "RevealCard", Resource.RevealCard.Replace("{card}", PhysicalCard.ToString()), Player_RevealWatchtower, false);
}
internal void Player_RevealWatchtower(IPlayer player, ref Players.CardGainEventArgs e)
{
player.AddCardInto(DeckLocation.Revealed, player.RetrieveCardFrom(DeckLocation.Hand, PhysicalCard));
player.AddCardInto(DeckLocation.Hand, player.RetrieveCardFrom(DeckLocation.Revealed, PhysicalCard));
var trashChoice = new Choice(Resource.TrashOrTopDeck.Replace("{card}", e.Card.ToString()), this, e.Card, new List { Resource.Trash, Resource.PutOnDeck }, player);
var trashResult = player.MakeChoice(trashChoice);
if (trashResult.Options[0] == Resource.Trash)
{
e.Cancelled = true;
var card = player.RetrieveCardFrom(e.Location, e.Card);
if (card != null)
player.Trash(this, e.Card);
e.IsLostTrackOf = true;
}
else
{
e.Cancelled = true;
if (e.Location != DeckLocation.Deck && e.Position != DeckPosition.Top)
{
player.RetrieveCardFrom(e.Location, e.Card);
e.Location = DeckLocation.Deck;
e.Position = DeckPosition.Top;
}
}
e.HandledBy.Add(Type);
}
public override void RemovedFrom(DeckLocation location, IPlayer player)
{
Contract.Requires(player != null, "player cannot be null");
base.RemovedFrom(location, player);
if (_CardGainedHandler != null)
player.CardGained -= _CardGainedHandler;
_CardGainedHandler = null;
}
}
public class WorkersVillage : Card
{
public WorkersVillage()
: base(Categories.Action, Source.Prosperity, Location.Kingdom, Traits.PlusCard | Traits.PlusAction | Traits.PlusMultipleActions | Traits.PlusBuy | Traits.Cantrip)
{
BaseCost = new Cost(4);
Benefit.Actions = 2;
Benefit.Cards = 1;
Benefit.Buys = 1;
}
}
}