diff --git a/Mage.Sets/src/mage/cards/c/ChecksAndBalances.java b/Mage.Sets/src/mage/cards/c/ChecksAndBalances.java new file mode 100644 index 0000000000..183ef31141 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChecksAndBalances.java @@ -0,0 +1,119 @@ + +package mage.cards.c; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.CastOnlyIfConditionIsTrueAbility; +import mage.abilities.common.SpellCastAllTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SetTargetPointer; +import mage.filter.FilterSpell; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.common.TargetCardInHand; + +/** + * + * @author L_J + */ +public final class ChecksAndBalances extends CardImpl { + + public ChecksAndBalances(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{U}"); + + // Cast this spell only if there are three or more players in the game. + this.addAbility(new CastOnlyIfConditionIsTrueAbility(ChecksAndBalancesCondition.instance, "Cast this spell only if there are three or more players in the game")); + + // Whenever a player casts a spell, each of that player’s opponents may discard a card. If they do, counter that spell. + this.addAbility(new SpellCastAllTriggeredAbility(new ChecksAndBalancesEffect(), new FilterSpell("a spell"), false, SetTargetPointer.SPELL)); + } + + public ChecksAndBalances(final ChecksAndBalances card) { + super(card); + } + + @Override + public ChecksAndBalances copy() { + return new ChecksAndBalances(this); + } +} + +enum ChecksAndBalancesCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game.getPlayerList().size() >= 3; + } + + @Override + public String toString() { + return "there are three or more players in the game"; + } +} + +class ChecksAndBalancesEffect extends OneShotEffect { + + public ChecksAndBalancesEffect() { + super(Outcome.Detriment); + staticText = "each of that player’s opponents may discard a card. If they do, counter that spell"; + } + + public ChecksAndBalancesEffect(final ChecksAndBalancesEffect effect) { + super(effect); + } + + @Override + public ChecksAndBalancesEffect copy() { + return new ChecksAndBalancesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getStack().getSpell(this.getTargetPointer().getFirst(game, source)); + if (spell != null) { + for (UUID uuid : game.getOpponents(spell.getControllerId())) { + Player player = game.getPlayer(uuid); + if (player != null) { + if (player.getHand().isEmpty()) { + game.informPlayers(player.getLogName() + " doesn't have a card in hand to discard to counter " + spell.getLogName() + ", effect aborted."); + return true; + } + } + } + for (UUID uuid : game.getOpponents(spell.getControllerId())) { + Player player = game.getPlayer(uuid); + if (player != null) { + if (!player.chooseUse(outcome, "Do you wish to discard a card to counter " + spell.getLogName() + '?', source, game)) { + game.informPlayers(player.getLogName() + " refuses to discard a card to counter " + spell.getLogName()); + return true; + } else { + game.informPlayers(player.getLogName() + " agrees to discard a card to counter " + spell.getLogName()); + } + } + } + for (UUID uuid : game.getOpponents(spell.getControllerId())) { + Player player = game.getPlayer(uuid); + if (player != null && !player.getHand().isEmpty()) { + TargetCardInHand target = new TargetCardInHand(); + if (player.choose(Outcome.Discard, target, source.getSourceId(), game)) { + Card card = game.getCard(target.getFirstTarget()); + if (card != null) { + player.discard(card, source, game); + } + } + } + } + game.getStack().counter(spell.getId(), source.getSourceId(), game); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/c/ClamIAm.java b/Mage.Sets/src/mage/cards/c/ClamIAm.java new file mode 100644 index 0000000000..5ac727a7c2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ClamIAm.java @@ -0,0 +1,92 @@ + +package mage.cards.c; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +/** + * + * @author L_J + */ +public final class ClamIAm extends CardImpl { + + public ClamIAm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); + this.subtype.add(SubType.CLAMFOLK); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // If you roll a 3 on a six-sided die, you may reroll that die. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ClamIAmEffect())); + } + + public ClamIAm(final ClamIAm card) { + super(card); + } + + @Override + public ClamIAm copy() { + return new ClamIAm(this); + } +} + +class ClamIAmEffect extends ReplacementEffectImpl { + + ClamIAmEffect() { + super(Duration.WhileOnBattlefield, Outcome.Benefit); + staticText = "If you roll a 3 on a six-sided die, you may reroll that die"; + } + + ClamIAmEffect(final ClamIAmEffect effect) { + super(effect); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player player = game.getPlayer(event.getPlayerId()); + if (player != null) { + String data = event.getData(); + int numSides = Integer.parseInt(data); + if (numSides == 6 && event.getAmount() == 3) { + if (player.chooseUse(outcome, "Reroll the die?", source, game)) { + game.informPlayers(player.getLogName() + " chose to reroll the die."); + event.setAmount(player.rollDice(game, 6)); + } + } + } + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ROLL_DICE; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return source.getControllerId().equals(event.getPlayerId()); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public ClamIAmEffect copy() { + return new ClamIAmEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/c/Clambassadors.java b/Mage.Sets/src/mage/cards/c/Clambassadors.java new file mode 100644 index 0000000000..20bd836355 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/Clambassadors.java @@ -0,0 +1,102 @@ + +package mage.cards.c; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetControlledPermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public final class Clambassadors extends CardImpl { + + public Clambassadors(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}"); + this.subtype.add(SubType.CLAMFOLK); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Whenever Clambassadors deals damage to a player, choose an artifact, creature, or land you control. That player gains control of that permanent. + this.addAbility(new DealsDamageToAPlayerTriggeredAbility(new ClambassadorsEffect(), false, true)); + } + + public Clambassadors(final Clambassadors card) { + super(card); + } + + @Override + public Clambassadors copy() { + return new Clambassadors(this); + } +} + + +class ClambassadorsEffect extends OneShotEffect { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("artifact, creature, or land you control"); + + static { + filter.add(Predicates.or( + new CardTypePredicate(CardType.ARTIFACT), + new CardTypePredicate(CardType.CREATURE), + new CardTypePredicate(CardType.LAND))); + } + + static { + filter.add(Predicates.or(new CardTypePredicate(CardType.ARTIFACT), new CardTypePredicate(CardType.CREATURE), new CardTypePredicate(CardType.LAND))); + } + + public ClambassadorsEffect() { + super(Outcome.Detriment); + this.staticText = "choose an artifact, creature, or land you control. That player gains control of that permanent"; + } + + public ClambassadorsEffect(final ClambassadorsEffect effect) { + super(effect); + } + + @Override + public ClambassadorsEffect copy() { + return new ClambassadorsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Target target = new TargetControlledPermanent(1, 1, filter, true); + if (target.canChoose(source.getSourceId(), controller.getId(), game)) { + while (!target.isChosen() && target.canChoose(controller.getId(), game) && controller.canRespond()) { + controller.chooseTarget(outcome, target, source, game); + } + } + Permanent permanent = game.getPermanent(target.getFirstTarget()); + Player opponent = game.getPlayer(this.getTargetPointer().getFirst(game, source)); + if (permanent != null && opponent != null) { + ContinuousEffect effect = new GainControlTargetEffect(Duration.Custom, true, opponent.getId()); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + game.informPlayers(opponent.getLogName() + " has gained control of " + permanent.getLogName()); + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/d/Denied.java b/Mage.Sets/src/mage/cards/d/Denied.java new file mode 100644 index 0000000000..534d1a1a5f --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/Denied.java @@ -0,0 +1,80 @@ + +package mage.cards.d; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ChooseACardNameEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.players.Player; +import mage.game.stack.Spell; +import mage.target.TargetSpell; + +/** + * + * @author L_J + */ +public final class Denied extends CardImpl { + + public Denied(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{U}"); + + // Choose a card name, then target spell's controller reveals their hand. If a card with the chosen name is revealed this way, counter that spell. + this.getSpellAbility().addEffect(new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.ALL)); + this.getSpellAbility().addEffect(new DeniedEffect()); + this.getSpellAbility().addTarget(new TargetSpell()); + } + + public Denied(final Denied card) { + super(card); + } + + @Override + public Denied copy() { + return new Denied(this); + } +} + +class DeniedEffect extends OneShotEffect { + + public DeniedEffect() { + super(Outcome.Detriment); + staticText = "Choose a card name, then target spell's controller reveals their hand. If a card with the chosen name is revealed this way, counter that spell"; + } + + public DeniedEffect(final DeniedEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell targetSpell = game.getStack().getSpell(source.getFirstTarget()); + if (targetSpell == null) { + return true; + } + Player player = game.getPlayer(targetSpell.getControllerId()); + Object object = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY); + if (player != null && object instanceof String) { + player.revealCards("Denied!", player.getHand(), game, true); + String namedCard = (String) object; + for (Card card : player.getHand().getCards(game)) { + if (card != null && card.getName().equals(namedCard)) { + game.getStack().counter(targetSpell.getId(), source.getSourceId(), game); + break; + } + } + return true; + } + return false; + } + + @Override + public DeniedEffect copy() { + return new DeniedEffect(this); + } +} diff --git a/Mage.Sets/src/mage/cards/f/FlockOfRabidSheep.java b/Mage.Sets/src/mage/cards/f/FlockOfRabidSheep.java new file mode 100644 index 0000000000..79ac3e5e97 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FlockOfRabidSheep.java @@ -0,0 +1,71 @@ + +package mage.cards.f; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.permanent.token.RabidSheepToken; +import mage.players.Player; + +/** + * + * @author L_J + */ +public class FlockOfRabidSheep extends CardImpl { + + public FlockOfRabidSheep(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{G}{G}"); + + // Flip X coins. For each flip you win, create a 2/2 green Sheep creature token named Rabid Sheep. + this.getSpellAbility().addEffect(new FlockOfRabidSheepEffect()); + } + + public FlockOfRabidSheep(final FlockOfRabidSheep card) { + super(card); + } + + @Override + public FlockOfRabidSheep copy() { + return new FlockOfRabidSheep(this); + } +} + +class FlockOfRabidSheepEffect extends OneShotEffect { + + public FlockOfRabidSheepEffect() { + super(Outcome.LoseLife); + this.staticText = "Flip X coins. For each flip you win, create a 2/2 green Sheep creature token named Rabid Sheep"; + } + + public FlockOfRabidSheepEffect(final FlockOfRabidSheepEffect effect) { + super(effect); + } + + @Override + public FlockOfRabidSheepEffect copy() { + return new FlockOfRabidSheepEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int repeat = source.getManaCostsToPay().getX(); + int wonCount = 0; + for (int i = 1; i <= repeat; i++) { + if (controller.flipCoin(game)) { + wonCount++; + } + } + new CreateTokenEffect(new RabidSheepToken(), wonCount).apply(game, source); + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FreeForAll.java b/Mage.Sets/src/mage/cards/f/FreeForAll.java new file mode 100644 index 0000000000..631cc6bcd9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FreeForAll.java @@ -0,0 +1,144 @@ + +package mage.cards.f; + +import java.util.List; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.ExileZone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author L_J + */ +public final class FreeForAll extends CardImpl { + + public FreeForAll(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{3}{U}"); + + // When Free-for-All enters the battlefield, exile all creatures face down. + this.addAbility(new EntersBattlefieldTriggeredAbility(new FreeForAllExileAllEffect())); + + // At the beginning of each player's upkeep, that player chooses a card exiled with Free-for-All at random and puts it onto the battlefield. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new FreeForAllReturnFromExileEffect(), TargetController.ANY, false, true)); + + // When Free-for-All leaves the battlefield, put all cards exiled with it into their owners' graveyards. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new FreeForAllLeavesBattlefieldEffect(), false)); + } + + public FreeForAll(final FreeForAll card) { + super(card); + } + + @Override + public FreeForAll copy() { + return new FreeForAll(this); + } +} + +class FreeForAllExileAllEffect extends OneShotEffect { + + public FreeForAllExileAllEffect() { + super(Outcome.Exile); + staticText = "exile all creatures face down"; + } + + public FreeForAllExileAllEffect(final FreeForAllExileAllEffect effect) { + super(effect); + } + + @Override + public FreeForAllExileAllEffect copy() { + return new FreeForAllExileAllEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + List permanents = game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), source.getSourceId(), game); + for (Permanent permanent : permanents) { + Card card = game.getCard(permanent.getId()); + permanent.moveToExile(source.getSourceId(), "Free-for-All", source.getSourceId(), game); + if (card != null) { + card.setFaceDown(true, game); + } + } + return true; + } +} + +class FreeForAllReturnFromExileEffect extends OneShotEffect { + + public FreeForAllReturnFromExileEffect() { + super(Outcome.PutCardInPlay); + staticText = "that player chooses a card exiled with Free-for-All at random and puts it onto the battlefield"; + } + + public FreeForAllReturnFromExileEffect(final FreeForAllReturnFromExileEffect effect) { + super(effect); + } + + @Override + public FreeForAllReturnFromExileEffect copy() { + return new FreeForAllReturnFromExileEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(targetPointer.getFirst(game, source)); + if (player != null) { + ExileZone exZone = game.getExile().getExileZone(source.getSourceId()); + if (exZone != null) { + Cards exiledCards = new CardsImpl(exZone.getCards(game)); + return player.moveCards(exiledCards.getRandom(game), Zone.BATTLEFIELD, source, game); + } + return true; + } + return false; + } +} + +class FreeForAllLeavesBattlefieldEffect extends OneShotEffect { + + public FreeForAllLeavesBattlefieldEffect() { + super(Outcome.Detriment); + this.staticText = "put all cards exiled with it into their owners' graveyards"; + } + + public FreeForAllLeavesBattlefieldEffect(final FreeForAllLeavesBattlefieldEffect effect) { + super(effect); + } + + @Override + public FreeForAllLeavesBattlefieldEffect copy() { + return new FreeForAllLeavesBattlefieldEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + ExileZone exZone = game.getExile().getExileZone(source.getSourceId()); + if (exZone != null) { + return controller.moveCards(exZone.getCards(game), Zone.GRAVEYARD, source, game, false, false, true, null); + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/f/FreeRangeChicken.java b/Mage.Sets/src/mage/cards/f/FreeRangeChicken.java new file mode 100644 index 0000000000..47310ccc4b --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FreeRangeChicken.java @@ -0,0 +1,135 @@ + +package mage.cards.f; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.watchers.Watcher; + +/** + * + * @author L_J + */ +public final class FreeRangeChicken extends CardImpl { + + public FreeRangeChicken(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + this.subtype.add(SubType.CHICKEN); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // {1}{G}: Roll two six-sided dice. If both results are the same, Free-Range Chicken gets +X/+X until end of turn, where X is that result. If the total of those results is equal to any other total you have rolled this turn for Free-Range Chicken, sacrifice it. (For example, if you roll two 3s, Free-Range Chicken gets +3/+3. If you roll a total of 6 for Free-Range Chicken later that turn, sacrifice it.) + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new FreeRangeChickenEffect(), new ManaCostsImpl("{1}{G}")), new FreeRangeChickenWatcher()); + } + + public FreeRangeChicken(final FreeRangeChicken card) { + super(card); + } + + @Override + public FreeRangeChicken copy() { + return new FreeRangeChicken(this); + } +} + +class FreeRangeChickenEffect extends OneShotEffect { + + public FreeRangeChickenEffect() { + super(Outcome.BoostCreature); + this.staticText = "Roll two six-sided dice. If both results are the same, Free-Range Chicken gets +X/+X until end of turn, where X is that result. If the total of those results is equal to any other total you have rolled this turn for Free-Range Chicken, sacrifice it"; + } + + public FreeRangeChickenEffect(final FreeRangeChickenEffect effect) { + super(effect); + } + + @Override + public FreeRangeChickenEffect copy() { + return new FreeRangeChickenEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + int firstRoll = controller.rollDice(game, 6); + int secondRoll = controller.rollDice(game, 6); + if (firstRoll == secondRoll) { + game.addEffect(new BoostSourceEffect(firstRoll, firstRoll, Duration.EndOfTurn), source); + } + FreeRangeChickenWatcher watcher = (FreeRangeChickenWatcher) game.getState().getWatchers().get(FreeRangeChickenWatcher.class.getSimpleName()); + if (watcher != null) { + int totalRoll = firstRoll + secondRoll; + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + if (sourcePermanent != null) { + if (watcher.rolledThisTurn(sourcePermanent.getId(), totalRoll)) { + sourcePermanent.sacrifice(source.getSourceId(), game); + } else { + watcher.addRoll(sourcePermanent.getId(), totalRoll); + } + } + } + return true; + } + return false; + } +} + +class FreeRangeChickenWatcher extends Watcher { + + private final Map totalRolls = new HashMap<>(); + + public FreeRangeChickenWatcher() { + super(FreeRangeChickenWatcher.class.getSimpleName(), WatcherScope.GAME); + } + + public FreeRangeChickenWatcher(final FreeRangeChickenWatcher watcher) { + super(watcher); + for (Map.Entry entry : watcher.totalRolls.entrySet()) { + this.totalRolls.put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void watch(GameEvent event, Game game) { + } + + @Override + public void reset() { + totalRolls.clear(); + } + + @Override + public FreeRangeChickenWatcher copy() { + return new FreeRangeChickenWatcher(this); + } + + public void addRoll(UUID sourceId, int roll) { + totalRolls.put(sourceId, roll); + } + + public boolean rolledThisTurn(UUID sourceId, int roll) { + if (totalRolls.get(sourceId) != null) { + return totalRolls.get(sourceId) == roll; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/g/GoblinBowlingTeam.java b/Mage.Sets/src/mage/cards/g/GoblinBowlingTeam.java new file mode 100644 index 0000000000..663449e3ae --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GoblinBowlingTeam.java @@ -0,0 +1,110 @@ + +package mage.cards.g; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.DamageEvent; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.util.CardUtil; + +/** + * + * @author L_J + */ +public final class GoblinBowlingTeam extends CardImpl { + + public GoblinBowlingTeam(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{R}"); + this.subtype.add(SubType.GOBLIN); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // If Goblin Bowling Team would deal damage to a permanent or player, it deals that much damage plus the result of a six-sided die roll to that permanent or player instead. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GoblinBowlingTeamEffect())); + + } + + public GoblinBowlingTeam(final GoblinBowlingTeam card) { + super(card); + } + + @Override + public GoblinBowlingTeam copy() { + return new GoblinBowlingTeam(this); + } +} + +class GoblinBowlingTeamEffect extends ReplacementEffectImpl { + + public GoblinBowlingTeamEffect() { + super(Duration.WhileOnBattlefield, Outcome.Damage); + staticText = "If {this} would deal damage to a permanent or player, it deals that much damage plus the result of a six-sided die roll to that permanent or player instead"; + } + + public GoblinBowlingTeamEffect(final GoblinBowlingTeamEffect effect) { + super(effect); + } + + @Override + public GoblinBowlingTeamEffect copy() { + return new GoblinBowlingTeamEffect(this); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGE_CREATURE: + case DAMAGE_PLAYER: + case DAMAGE_PLANESWALKER: + return true; + default: + return false; + } + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return event.getSourceId().equals(source.getSourceId()); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + DamageEvent damageEvent = (DamageEvent) event; + if (damageEvent.getType() == EventType.DAMAGE_PLAYER) { + Player targetPlayer = game.getPlayer(event.getTargetId()); + if (targetPlayer != null) { + targetPlayer.damage(CardUtil.addWithOverflowCheck(damageEvent.getAmount(), controller.rollDice(game, 6)), damageEvent.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); + return true; + } + } else { + Permanent targetPermanent = game.getPermanent(event.getTargetId()); + if (targetPermanent != null) { + targetPermanent.damage(CardUtil.addWithOverflowCheck(damageEvent.getAmount(), controller.rollDice(game, 6)), damageEvent.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); + return true; + } + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/r/Ricochet.java b/Mage.Sets/src/mage/cards/r/Ricochet.java new file mode 100644 index 0000000000..f52a37caad --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/Ricochet.java @@ -0,0 +1,154 @@ + +package mage.cards.r; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.SpellCastAllTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SetTargetPointer; +import mage.filter.FilterSpell; +import mage.filter.predicate.ObjectPlayer; +import mage.filter.predicate.ObjectPlayerPredicate; +import mage.filter.predicate.mageobject.NumberOfTargetsPredicate; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.Target; +import mage.target.Targets; +import mage.util.TargetAddress; + +/** + * + * @author L_J + */ +public final class Ricochet extends CardImpl { + + protected static final FilterSpell filter = new FilterSpell("a spell that targets a single player"); + + static { + filter.add(new NumberOfTargetsPredicate(1)); + filter.add(new SpellWithOnlyPlayerTargetsPredicate()); + } + + public Ricochet(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{R}"); + + // Whenever a player casts a spell that targets a single player, each player rolls a six-sided die. Change the target of that spell to the player with the lowest result. Reroll to break ties, if necessary. + this.addAbility(new SpellCastAllTriggeredAbility(new RicochetEffect(), filter, false, SetTargetPointer.SPELL)); + } + + public Ricochet(final Ricochet card) { + super(card); + } + + @Override + public Ricochet copy() { + return new Ricochet(this); + } +} + +class SpellWithOnlyPlayerTargetsPredicate implements ObjectPlayerPredicate> { + + @Override + public boolean apply(ObjectPlayer input, Game game) { + Spell spell = input.getObject(); + if (spell == null) { + return false; + } + for (TargetAddress addr : TargetAddress.walk(spell)) { + Target targetInstance = addr.getTarget(spell); + for (UUID targetId : targetInstance.getTargets()) { + if (game.getPlayer(targetId) == null) { + return false; + } + } + } + return true; + } +} + +class RicochetEffect extends OneShotEffect { + + public RicochetEffect() { + super(Outcome.Detriment); + staticText = "each player rolls a six-sided die. Change the target of that spell to the player with the lowest result. Reroll to break ties, if necessary"; + } + + public RicochetEffect(final RicochetEffect effect) { + super(effect); + } + + @Override + public RicochetEffect copy() { + return new RicochetEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getStack().getSpell(this.getTargetPointer().getFirst(game, source)); + if (spell != null) { + Targets targets = new Targets(); + Ability sourceAbility = spell.getSpellAbility(); + for (UUID modeId : sourceAbility.getModes().getSelectedModes()) { + Mode mode = sourceAbility.getModes().get(modeId); + targets.addAll(mode.getTargets()); + } + if (targets.size() != 1 || targets.get(0).getTargets().size() != 1) { + return false; + } + + Map playerRolls = new HashMap<>(); + for (UUID playerId : game.getPlayerList().copy()) { + Player player = game.getPlayer(playerId); + if (player != null) { + playerRolls.put(player, 7); + } + } + do { + for (Player player : playerRolls.keySet()) { + playerRolls.put(player, player.rollDice(game, 6)); + } + int minValueInMap = Collections.min(playerRolls.values()); + for (Map.Entry mapEntry : new HashSet<>(playerRolls.entrySet())) { + if (mapEntry.getValue() > minValueInMap) { + playerRolls.remove(mapEntry.getKey()); + } + } + } while (playerRolls.size() > 1); + + if (playerRolls.size() == 1) { + Player loser = (Player) playerRolls.keySet().toArray()[0]; + UUID loserId = loser.getId(); + Target target = targets.get(0); + if (target.getFirstTarget().equals(loserId)) { + return true; + } + String oldTargetName = null; + if (target.canTarget(spell.getControllerId(), loserId, sourceAbility, game)) { + Player oldPlayer = game.getPlayer(targets.getFirstTarget()); + if (oldPlayer != null) { + oldTargetName = oldPlayer.getLogName(); + } + target.clearChosen(); + target.addTarget(loserId, sourceAbility, game); + } + MageObject sourceObject = game.getObject(source.getSourceId()); + if (oldTargetName != null && sourceObject != null) { + game.informPlayers(sourceObject.getLogName() + ": Changed target of " + spell.getLogName() + " from " + oldTargetName + " to " + loser.getLogName()); + } + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/cards/s/SpatulaOfTheAges.java b/Mage.Sets/src/mage/cards/s/SpatulaOfTheAges.java new file mode 100644 index 0000000000..c6ece6f912 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpatulaOfTheAges.java @@ -0,0 +1,56 @@ + +package mage.cards.s; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.PutCardFromHandOntoBattlefieldEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.filter.common.FilterPermanentCard; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.filter.predicate.mageobject.SupertypePredicate; +import mage.filter.predicate.other.ExpansionSetPredicate; + +/** + * + * @author L_J + */ +public final class SpatulaOfTheAges extends CardImpl { + + private static final FilterPermanentCard filter = new FilterPermanentCard("a silver-bordered permanent card"); + + static { + filter.add(Predicates.and( + Predicates.not(new SupertypePredicate(SuperType.BASIC)), // all Un-set basic lands are black bordered cards, and thus illegal choices + Predicates.not(new NamePredicate("Steamflogger Boss")), // printed in Unstable with a black border + Predicates.or(new ExpansionSetPredicate("UGL"), new ExpansionSetPredicate("UNH"), new ExpansionSetPredicate("UST")) + )); + } + + public SpatulaOfTheAges(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.ARTIFACT},"{4}"); + + // {4}, {T}, Sacrifice Spatula of the Ages: You may put a silver-bordered permanent card from your hand onto the battlefield. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PutCardFromHandOntoBattlefieldEffect(filter), new ManaCostsImpl("{4}")); + ability.addCost(new TapSourceCost()); + ability.addCost(new SacrificeSourceCost()); + this.addAbility(ability); + } + + public SpatulaOfTheAges(final SpatulaOfTheAges card) { + super(card); + } + + @Override + public SpatulaOfTheAges copy() { + return new SpatulaOfTheAges(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TempOfTheDamned.java b/Mage.Sets/src/mage/cards/t/TempOfTheDamned.java new file mode 100644 index 0000000000..8d1249e920 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TempOfTheDamned.java @@ -0,0 +1,107 @@ + +package mage.cards.t; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Outcome; +import mage.constants.TargetController; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author L_J + */ +public final class TempOfTheDamned extends CardImpl { + + public TempOfTheDamned(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{B}"); + this.subtype.add(SubType.ZOMBIE); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // As Temp of the Damned enters the battlefield, roll a six-sided die. Temp of the Damned enters the battlefield with a number of funk counters on it equal to the result. + this.addAbility(new AsEntersBattlefieldAbility(new TempOfTheDamnedEffect())); + + // At the beginning of your upkeep, remove a funk counter from Temp of the Damned. If you can't, sacrifice it. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new TempOfTheDamnedUpkeepEffect(), TargetController.YOU, false)); + } + + public TempOfTheDamned(final TempOfTheDamned card) { + super(card); + } + + @Override + public TempOfTheDamned copy() { + return new TempOfTheDamned(this); + } +} + +class TempOfTheDamnedEffect extends OneShotEffect { + + public TempOfTheDamnedEffect() { + super(Outcome.Neutral); + staticText = "roll a six-sided die. {this} enters the battlefield with a number of funk counters on it equal to the result"; + } + + public TempOfTheDamnedEffect(final TempOfTheDamnedEffect effect) { + super(effect); + } + + @Override + public TempOfTheDamnedEffect copy() { + return new TempOfTheDamnedEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + return new AddCountersSourceEffect(CounterType.FUNK.createInstance(controller.rollDice(game, 6))).apply(game, source); + } + return false; + } +} + +class TempOfTheDamnedUpkeepEffect extends OneShotEffect { + + TempOfTheDamnedUpkeepEffect() { + super(Outcome.Sacrifice); + staticText = "remove a funk counter from {this}. If you can't, sacrifice it"; + } + + TempOfTheDamnedUpkeepEffect(final TempOfTheDamnedUpkeepEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null) { + int amount = permanent.getCounters(game).getCount(CounterType.FUNK); + if (amount > 0) { + permanent.removeCounters(CounterType.FUNK.createInstance(), game); + } else { + permanent.sacrifice(source.getSourceId(), game); + } + return true; + } + return false; + } + + @Override + public TempOfTheDamnedUpkeepEffect copy() { + return new TempOfTheDamnedUpkeepEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/Unglued.java b/Mage.Sets/src/mage/sets/Unglued.java index baea3f7836..690e1a2520 100644 --- a/Mage.Sets/src/mage/sets/Unglued.java +++ b/Mage.Sets/src/mage/sets/Unglued.java @@ -22,12 +22,21 @@ public final class Unglued extends ExpansionSet { super("Unglued", "UGL", ExpansionSet.buildDate(1998, 8, 11), SetType.JOKESET); cards.add(new SetCardInfo("Burning Cinder Fury of Crimson Chaos Fire", 40, Rarity.RARE, mage.cards.b.BurningCinderFuryOfCrimsonChaosFire.class)); + cards.add(new SetCardInfo("Checks and Balances", 16, Rarity.UNCOMMON, mage.cards.c.ChecksAndBalances.class)); cards.add(new SetCardInfo("Chicken Egg", 41, Rarity.COMMON, mage.cards.c.ChickenEgg.class)); cards.add(new SetCardInfo("Chicken a la King", 17, Rarity.RARE, mage.cards.c.ChickenALaKing.class)); + cards.add(new SetCardInfo("Chicken Egg", 41, Rarity.COMMON, mage.cards.c.ChickenEgg.class)); + cards.add(new SetCardInfo("Clambassadors", 18, Rarity.COMMON, mage.cards.c.Clambassadors.class)); + cards.add(new SetCardInfo("Clam-I-Am", 19, Rarity.COMMON, mage.cards.c.ClamIAm.class)); + cards.add(new SetCardInfo("Denied!", 22, Rarity.COMMON, mage.cards.d.Denied.class)); cards.add(new SetCardInfo("Elvish Impersonators", 56, Rarity.COMMON, mage.cards.e.ElvishImpersonators.class)); + cards.add(new SetCardInfo("Flock of Rabid Sheep", 57, Rarity.UNCOMMON, mage.cards.f.FlockOfRabidSheep.class)); cards.add(new SetCardInfo("Forest", 88, Rarity.LAND, mage.cards.basiclands.Forest.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Fowl Play", 24, Rarity.COMMON, mage.cards.f.FowlPlay.class)); + cards.add(new SetCardInfo("Free-for-All", 25, Rarity.RARE, mage.cards.f.FreeForAll.class)); + cards.add(new SetCardInfo("Free-Range Chicken", 58, Rarity.COMMON, mage.cards.f.FreeRangeChicken.class)); cards.add(new SetCardInfo("Gerrymandering", 59, Rarity.UNCOMMON, mage.cards.g.Gerrymandering.class)); + cards.add(new SetCardInfo("Goblin Bowling Team", 44, Rarity.COMMON, mage.cards.g.GoblinBowlingTeam.class)); cards.add(new SetCardInfo("Goblin Tutor", 45, Rarity.UNCOMMON, mage.cards.g.GoblinTutor.class)); cards.add(new SetCardInfo("Growth Spurt", 61, Rarity.COMMON, mage.cards.g.GrowthSpurt.class)); cards.add(new SetCardInfo("Hungry Hungry Heifer", 63, Rarity.UNCOMMON, mage.cards.h.HungryHungryHeifer.class)); @@ -43,11 +52,14 @@ public final class Unglued extends ExpansionSet { cards.add(new SetCardInfo("Paper Tiger", 78, Rarity.COMMON, mage.cards.p.PaperTiger.class)); cards.add(new SetCardInfo("Plains", 84, Rarity.LAND, mage.cards.basiclands.Plains.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); cards.add(new SetCardInfo("Poultrygeist", 37, Rarity.COMMON, mage.cards.p.Poultrygeist.class)); + cards.add(new SetCardInfo("Ricochet", 50, Rarity.UNCOMMON, mage.cards.r.Ricochet.class)); cards.add(new SetCardInfo("Rock Lobster", 79, Rarity.COMMON, mage.cards.r.RockLobster.class)); cards.add(new SetCardInfo("Scissors Lizard", 80, Rarity.COMMON, mage.cards.s.ScissorsLizard.class)); cards.add(new SetCardInfo("Spark Fiend", 51, Rarity.RARE, mage.cards.s.SparkFiend.class)); + cards.add(new SetCardInfo("Spatula of the Ages", 81, Rarity.UNCOMMON, mage.cards.s.SpatulaOfTheAges.class)); cards.add(new SetCardInfo("Strategy, Schmategy", 52, Rarity.RARE, mage.cards.s.StrategySchmategy.class)); cards.add(new SetCardInfo("Swamp", 86, Rarity.LAND, mage.cards.basiclands.Swamp.class, new CardGraphicInfo(FrameStyle.UGL_FULL_ART_BASIC, false))); + cards.add(new SetCardInfo("Temp of the Damned", 38, Rarity.COMMON, mage.cards.t.TempOfTheDamned.class)); cards.add(new SetCardInfo("The Cheese Stands Alone", 2, Rarity.RARE, mage.cards.t.TheCheeseStandsAlone.class)); cards.add(new SetCardInfo("Timmy, Power Gamer", 68, Rarity.RARE, mage.cards.t.TimmyPowerGamer.class)); cards.add(new SetCardInfo("Urza's Science Fair Project", 83, Rarity.UNCOMMON, mage.cards.u.UrzasScienceFairProject.class)); diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java index fef2c75e13..468874b9d5 100644 --- a/Mage/src/main/java/mage/constants/SubType.java +++ b/Mage/src/main/java/mage/constants/SubType.java @@ -93,6 +93,7 @@ public enum SubType { CHIMERA("Chimera", SubTypeSet.CreatureType), CHISS("Chiss", SubTypeSet.CreatureType, true), CITIZEN("Citizen", SubTypeSet.CreatureType), + CLAMFOLK("Clamfolk", SubTypeSet.CreatureType, true), // Unglued CLERIC("Cleric", SubTypeSet.CreatureType), COCKATRICE("Cockatrice", SubTypeSet.CreatureType), CONSTRUCT("Construct", SubTypeSet.CreatureType), diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java index 865cab23a5..7f8d10f8ae 100644 --- a/Mage/src/main/java/mage/counters/CounterType.java +++ b/Mage/src/main/java/mage/counters/CounterType.java @@ -44,6 +44,7 @@ public enum CounterType { FEATHER("feather"), FILIBUSTER("filibuster"), FLOOD("flood"), + FUNK("funk"), FURY("fury"), FUNGUS("fungus"), FUSE("fuse"), diff --git a/Mage/src/main/java/mage/game/permanent/token/RabidSheepToken.java b/Mage/src/main/java/mage/game/permanent/token/RabidSheepToken.java new file mode 100644 index 0000000000..e3912f9f2b --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/RabidSheepToken.java @@ -0,0 +1,30 @@ + +package mage.game.permanent.token; + +import mage.constants.CardType; +import mage.constants.SubType; +import mage.MageInt; + +/** + * + * @author L_J + */ +public final class RabidSheepToken extends TokenImpl { + + public RabidSheepToken() { + super("Rabid Sheep", "2/2 green Sheep creature token named Rabid Sheep"); + cardType.add(CardType.CREATURE); + subtype.add(SubType.SHEEP); + color.setGreen(true); + power = new MageInt(2); + toughness = new MageInt(2); + } + + public RabidSheepToken(final RabidSheepToken token) { + super(token); + } + + public RabidSheepToken copy() { + return new RabidSheepToken(this); + } +}