Merge remote-tracking branch 'remotes/upstream/master'

This commit is contained in:
ciaccona007 2017-07-15 23:24:03 -04:00
commit dfa4bad8c5
138 changed files with 1836 additions and 856 deletions

View file

@ -149,7 +149,7 @@ public class BeginningOfEndStepTriggeredAbility extends TriggeredAbilityImpl {
case ANY:
return sb.insert(0, generateConditionString()).insert(0, abilityWordRule + "At the beginning of each end step, ").toString();
case CONTROLLER_ATTACHED_TO:
return sb.insert(0, generateConditionString()).insert(0, abilityWordRule + "At the beginning of the end step of enchanted creature's controller, ").toString();
return sb.insert(0, generateConditionString()).insert(0, abilityWordRule + "At the beginning of the end step of enchanted permanent's controller, ").toString();
}
return "";
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.abilities.keyword.BuybackAbility;
import mage.cards.Card;
import mage.game.Game;
/**
* @author spjspj
*/
public enum BuybackCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getSourceId());
if (card != null) {
return card.getAbilities().stream()
.filter(a -> a instanceof BuybackAbility)
.anyMatch(b -> ((BuybackAbility) b).isActivated());
}
return false;
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
/**
*
* @author jeffwadsworth
*/
package mage.abilities.costs.common;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.cards.Card;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
public class CyclingDiscardCost extends CostImpl {
public CyclingDiscardCost() {
}
public CyclingDiscardCost(CyclingDiscardCost cost) {
super(cost);
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
return game.getPlayer(controllerId).getHand().contains(sourceId);
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Player player = game.getPlayer(controllerId);
if (player != null) {
Card card = player.getHand().get(sourceId, game);
if (card != null) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CYCLE_CARD, card.getId(), card.getId(), card.getOwnerId()));
paid = player.discard(card, null, game);
if (paid) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CYCLED_CARD, card.getId(), card.getId(), card.getOwnerId()));
}
}
}
return paid;
}
@Override
public String getText() {
return "Discard this card";
}
@Override
public CyclingDiscardCost copy() {
return new CyclingDiscardCost(this);
}
}

View file

@ -32,7 +32,6 @@ import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterCard;

View file

@ -34,7 +34,10 @@ import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
/**
@ -70,11 +73,11 @@ public class PlayWithTheTopCardRevealedEffect extends ContinuousEffectImpl {
if (allPlayers) {
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
if (player != null && !isCastFromPlayersLibrary(game, playerId)) {
player.setTopCardRevealed(true);
}
}
} else {
} else if (!isCastFromPlayersLibrary(game, controller.getId())) {
controller.setTopCardRevealed(true);
}
return true;
@ -82,6 +85,16 @@ public class PlayWithTheTopCardRevealedEffect extends ContinuousEffectImpl {
return false;
}
boolean isCastFromPlayersLibrary(Game game, UUID playerId) {
if (!game.getStack().isEmpty()) {
StackObject stackObject = game.getStack().getLast();
return stackObject instanceof Spell
&& !((Spell) stackObject).isDoneActivatingManaAbilities()
&& Zone.LIBRARY.equals(((Spell) stackObject).getFromZone());
}
return false;
}
@Override
public PlayWithTheTopCardRevealedEffect copy() {
return new PlayWithTheTopCardRevealedEffect(this);

View file

@ -36,6 +36,7 @@ import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card;
@ -66,6 +67,7 @@ public class BuybackAbility extends StaticAbility implements OptionalAdditionalS
private static final String reminderTextCost = "You may {cost} in addition to any other costs as you cast this spell. If you do, put this card into your hand as it resolves.";
private static final String reminderTextMana = "You may pay an additional {cost} as you cast this spell. If you do, put this card into your hand as it resolves.";
protected OptionalAdditionalCost buybackCost;
private int amountToReduceBy = 0;
public BuybackAbility(String manaString) {
super(Zone.STACK, new BuybackEffect());
@ -96,17 +98,51 @@ public class BuybackAbility extends StaticAbility implements OptionalAdditionalS
}
}
public void resetReduceCost() {
amountToReduceBy = 0;
}
// Called by Memory Crystal to reduce mana costs.
public int reduceCost(int genericManaToReduce) {
int amountToReduce = genericManaToReduce;
boolean foundCostToReduce = false;
if (buybackCost != null) {
for (Object cost : ((Costs) buybackCost)) {
if (cost instanceof ManaCostsImpl) {
for (Object c : (ManaCostsImpl) cost) {
if (c instanceof GenericManaCost) {
int newCostCMC = ((GenericManaCost) c).convertedManaCost() - amountToReduceBy - genericManaToReduce;
foundCostToReduce = true;
if (newCostCMC > 0) {
amountToReduceBy += genericManaToReduce;
} else {
amountToReduce = ((GenericManaCost) c).convertedManaCost() - amountToReduceBy;
amountToReduceBy = ((GenericManaCost) c).convertedManaCost();
}
}
}
}
}
}
if (foundCostToReduce) {
return amountToReduce;
}
return 0;
}
@Override
public boolean isActivated() {
if (buybackCost != null) {
return buybackCost.isActivated();
}
resetReduceCost();
return false;
}
public void resetBuyback() {
if (buybackCost != null) {
buybackCost.reset();
resetReduceCost();
}
}

View file

@ -27,20 +27,14 @@
*/
package mage.abilities.keyword;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.abilities.costs.common.CyclingDiscardCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.cards.Card;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.common.TargetCardInLibrary;
/**
@ -90,44 +84,3 @@ public class CyclingAbility extends ActivatedAbilityImpl {
}
}
class CyclingDiscardCost extends CostImpl {
public CyclingDiscardCost() {
}
public CyclingDiscardCost(CyclingDiscardCost cost) {
super(cost);
}
@Override
public boolean canPay(Ability ability, UUID sourceId, UUID controllerId, Game game) {
return game.getPlayer(controllerId).getHand().contains(sourceId);
}
@Override
public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana, Cost costToPay) {
Player player = game.getPlayer(controllerId);
if (player != null) {
Card card = player.getHand().get(sourceId, game);
if (card != null) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CYCLE_CARD, card.getId(), card.getId(), card.getOwnerId()));
paid = player.discard(card, null, game);
if (paid) {
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.CYCLED_CARD, card.getId(), card.getId(), card.getOwnerId()));
}
}
}
return paid;
}
@Override
public String getText() {
return "Discard this card";
}
@Override
public CyclingDiscardCost copy() {
return new CyclingDiscardCost(this);
}
}

View file

@ -53,4 +53,9 @@ public class ActivateAsSorceryManaAbility extends SimpleManaAbility {
public ActivateAsSorceryManaAbility copy() {
return new ActivateAsSorceryManaAbility(this);
}
@Override
public String getRule() {
return super.getRule() + " Activate this ability only any time you could cast a sorcery.";
}
}

View file

@ -35,6 +35,8 @@ import mage.abilities.ActivatedAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.effects.common.ManaEffect;
import mage.constants.AbilityType;
import mage.constants.AsThoughEffectType;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.game.Game;
@ -70,7 +72,12 @@ public abstract class ActivatedManaAbilityImpl extends ActivatedAbilityImpl impl
if (!controlsAbility(playerId, game)) {
return false;
}
// check if player is in the process of playing spell costs and he is no longer allowed to use activated mana abilities (e.g. becaus he started to use improvise)
if (timing == TimingRule.SORCERY
&& !game.canPlaySorcery(playerId)
&& !game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.ACTIVATE_AS_INSTANT, this, controllerId, game)) {
return false;
}
// check if player is in the process of playing spell costs and he is no longer allowed to use activated mana abilities (e.g. because he started to use improvise)
//20091005 - 605.3a
return costs.canPay(this, sourceId, controllerId, game);

View file

@ -322,6 +322,7 @@ public enum SubType {
GIDEON("Gideon", SubTypeSet.PlaneswalkerType, false),
JACE("Jace", SubTypeSet.PlaneswalkerType, false),
KARN("Karn", SubTypeSet.PlaneswalkerType, false),
KAYA("Kaya", SubTypeSet.PlaneswalkerType, false),
KIORA("Kiora", SubTypeSet.PlaneswalkerType, false),
KOTH("Koth", SubTypeSet.PlaneswalkerType, false),
LILIANA("Liliana", SubTypeSet.PlaneswalkerType, false),

View file

@ -38,6 +38,9 @@ public final class StaticFilters {
public static final FilterControlledPermanent FILTER_CONTROLLED_A_CREATURE = new FilterControlledCreaturePermanent("a creature you control");
public static final FilterControlledCreaturePermanent FILTER_CONTROLLED_ANOTHER_CREATURE = new FilterControlledCreaturePermanent("another creature");
public static final FilterControlledPermanent FILTER_CONTROLLED_PERMANENT_NON_LAND = new FilterControlledPermanent("nonland permanent");
public static final FilterLandPermanent FILTER_LAND = new FilterLandPermanent();
public static final FilterLandPermanent FILTER_LANDS = new FilterLandPermanent("lands");
public static final FilterLandPermanent FILTER_BASIC_LAND = new FilterLandPermanent();
public static final FilterCreaturePermanent FILTER_PERMANENT_CREATURE = new FilterCreaturePermanent();
public static final FilterCreaturePermanent FILTER_PERMANENT_A_CREATURE = new FilterCreaturePermanent("a creature");
@ -51,6 +54,10 @@ public final class StaticFilters {
public static final FilterSpell FILTER_SPELL_NON_CREATURE
= (FilterSpell) new FilterSpell("noncreature spell").add(Predicates.not(new CardTypePredicate(CardType.CREATURE)));
public static final FilterSpell FILTER_SPELL = new FilterSpell();
public static final FilterSpell FILTER_INSTANT_OR_SORCERY_SPELL = new FilterSpell("instant or sorcery spell");
public static final FilterPermanent FILTER_CREATURE_TOKENS = new FilterCreaturePermanent("creature tokens");
public static final FilterPermanent FILTER_ATTACKING_CREATURES = new FilterCreaturePermanent("attacking creatures");
@ -58,6 +65,8 @@ public final class StaticFilters {
static {
FILTER_CONTROLLED_PERMANENT_NON_LAND.add(
Predicates.not(new CardTypePredicate(CardType.LAND)));
FILTER_CREATURE_TOKENS.add(new TokenPredicate());
FILTER_ATTACKING_CREATURES.add(new AttackingPredicate());
@ -87,6 +96,11 @@ public final class StaticFilters {
new CardTypePredicate(CardType.ARTIFACT),
new CardTypePredicate(CardType.CREATURE)
));
FILTER_INSTANT_OR_SORCERY_SPELL.add(Predicates.or(
new CardTypePredicate(CardType.INSTANT),
new CardTypePredicate(CardType.SORCERY)
));
}
}

View file

@ -33,14 +33,22 @@ import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.common.continuous.SetPowerToughnessSourceEffect;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.Zone;
import static mage.game.permanent.token.DokaiWeaverofLifeToken.filterLands;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.SubtypePredicate;
/**
*
* @author spjspj
*/
public class KalonianTwingroveTreefolkWarriorToken extends Token {
final static FilterControlledPermanent filterLands = new FilterControlledPermanent("Forests you control");
static {
filterLands.add(new SubtypePredicate(SubType.FOREST));
}
public KalonianTwingroveTreefolkWarriorToken() {
super("Treefolk Warrior", "green Treefolk Warrior creature token with \"This creature's power and toughness are each equal to the number of Forests you control.\"");

View file

@ -44,6 +44,5 @@ public class RhonassLastStandToken extends Token {
subtype.add("Snake");
power = new MageInt(5);
toughness = new MageInt(4);
addAbility(TrampleAbility.getInstance());
}
}

View file

@ -138,6 +138,7 @@ public class Spell extends StackObjImpl implements Card {
}
public boolean activate(Game game, boolean noMana) {
setDoneActivatingManaAbilities(false); // Used for e.g. improvise
if (!spellAbilities.get(0).activate(game, noMana)) {
return false;
}
@ -157,7 +158,7 @@ public class Spell extends StackObjImpl implements Card {
}
}
}
setDoneActivatingManaAbilities(false); // can be activated again maybe during the resolution of the spell (e.g. Metallic Rebuke)
setDoneActivatingManaAbilities(true); // can be activated again maybe during the resolution of the spell (e.g. Metallic Rebuke)
return true;
}

View file

@ -30,6 +30,7 @@ package mage.target;
import mage.abilities.Ability;
import mage.constants.Zone;
import mage.filter.FilterSpell;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
@ -49,7 +50,7 @@ public class TargetSpell extends TargetObject {
private final Set<UUID> sourceIds = new HashSet<>();
public TargetSpell() {
this(1, 1, new FilterSpell());
this(1, 1, StaticFilters.FILTER_SPELL);
}
public TargetSpell(FilterSpell filter) {

View file

@ -3,7 +3,6 @@ package mage.watchers.common;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.constants.CardType;
import mage.constants.WatcherScope;
import mage.game.Game;
import mage.game.events.GameEvent;
@ -16,7 +15,8 @@ import mage.watchers.Watcher;
*/
public class LandfallWatcher extends Watcher {
final Set<UUID> playerPlayedLand = new HashSet<>();
final Set<UUID> playerPlayedLand = new HashSet<>(); // player that played land
final Set<UUID> landPlayed = new HashSet<>(); // land played
public LandfallWatcher() {
super(LandfallWatcher.class.getSimpleName(), WatcherScope.GAME);
@ -25,6 +25,7 @@ public class LandfallWatcher extends Watcher {
public LandfallWatcher(final LandfallWatcher watcher) {
super(watcher);
playerPlayedLand.addAll(watcher.playerPlayedLand);
landPlayed.addAll(watcher.landPlayed);
}
@Override
@ -34,10 +35,13 @@ public class LandfallWatcher extends Watcher {
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) {
if (event.getType() == GameEvent.EventType.LAND_PLAYED) {
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId());
if (permanent != null && permanent.isLand() && !playerPlayedLand.contains(event.getPlayerId())) {
if (permanent != null
&& permanent.isLand()
&& !playerPlayedLand.contains(event.getPlayerId())) {
playerPlayedLand.add(event.getPlayerId());
landPlayed.add(event.getTargetId());
}
}
}
@ -45,10 +49,15 @@ public class LandfallWatcher extends Watcher {
@Override
public void reset() {
playerPlayedLand.clear();
landPlayed.clear();
super.reset();
}
public boolean landPlayed(UUID playerId) {
return playerPlayedLand.contains(playerId);
}
public boolean wasLandPlayed(UUID landId) {
return landPlayed.contains(landId);
}
}