From f9b0befac3548f209a73d4b658d60081f54458c3 Mon Sep 17 00:00:00 2001 From: samuelsandeen Date: Sun, 10 Jul 2016 18:39:42 -0400 Subject: [PATCH 1/8] Implement several cards and tests for some of the more complex cards. This also makes a small change to AbilityImpl's handling of variable costs which may not be needed. --- .../src/mage/sets/odyssey/LiquidFire.java | 137 ++++++++++++++++++ .../mage/sets/ravnica/BorosFuryShield.java | 111 ++++++++++++++ .../src/mage/sets/ravnica/Brightflame.java | 120 +++++++++++++++ .../mage/sets/ravnica/LightOfSanction.java | 103 +++++++++++++ .../mage/sets/weatherlight/DebtOfLoyalty.java | 93 ++++++++++++ .../test/cards/control/DebtOfLoyaltyTest.java | 98 +++++++++++++ .../cards/prevention/LightOfSanctionTest.java | 67 +++++++++ .../main/java/mage/abilities/AbilityImpl.java | 5 +- 8 files changed, 733 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/sets/odyssey/LiquidFire.java create mode 100644 Mage.Sets/src/mage/sets/ravnica/BorosFuryShield.java create mode 100644 Mage.Sets/src/mage/sets/ravnica/Brightflame.java create mode 100644 Mage.Sets/src/mage/sets/ravnica/LightOfSanction.java create mode 100644 Mage.Sets/src/mage/sets/weatherlight/DebtOfLoyalty.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/control/DebtOfLoyaltyTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/prevention/LightOfSanctionTest.java diff --git a/Mage.Sets/src/mage/sets/odyssey/LiquidFire.java b/Mage.Sets/src/mage/sets/odyssey/LiquidFire.java new file mode 100644 index 00000000000..34e38936264 --- /dev/null +++ b/Mage.Sets/src/mage/sets/odyssey/LiquidFire.java @@ -0,0 +1,137 @@ +/* + * 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.sets.odyssey; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.VariableCostImpl; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.GetXValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Dilnu + */ +public class LiquidFire extends CardImpl { + + public LiquidFire(UUID ownerId) { + super(ownerId, 201, "Liquid Fire", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{4}{R}{R}"); + this.expansionSetCode = "ODY"; + + // As an additional cost to cast Liquid Fire, choose a number between 0 and 5. + this.getSpellAbility().addCost(new LiquidFireCost()); + // Liquid Fire deals X damage to target creature and 5 minus X damage to that creature's controller, where X is the chosen number. + DynamicValue choiceValue = new GetXValue(); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new LiquidFireEffect(choiceValue)); + + } + + public LiquidFire(final LiquidFire card) { + super(card); + } + + @Override + public LiquidFire copy() { + return new LiquidFire(this); + } + + private static class LiquidFireEffect extends OneShotEffect { + protected DynamicValue choiceValue; + + public LiquidFireEffect(DynamicValue choiceValue) { + super(Outcome.Damage); + this.staticText = "{this} deals X damage to target creature and 5 minus X damage to that creature's controller, where X is the chosen number."; + this.choiceValue = choiceValue; + } + + public LiquidFireEffect(LiquidFireEffect effect) { + super(effect); + this.choiceValue = effect.choiceValue; + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + int creatureDamage = choiceValue.calculate(game, source, this); + int playerDamage = 5 - creatureDamage; + if (target != null) { + target.damage(creatureDamage, source.getSourceId(), game, false, true); + Player controller = game.getPlayer(target.getControllerId()); + if (controller != null) { + controller.damage(playerDamage, source.getSourceId(), game, false, true); + } + return true; + } + return false; + } + + @Override + public Effect copy() { + return new LiquidFireEffect(this); + } + } + + class LiquidFireCost extends VariableCostImpl { + public LiquidFireCost() { + super("Choose a Number"); + this.text = "As an additional cost to cast {source}, choose a number between 0 and 5"; + } + + public LiquidFireCost(final LiquidFireCost cost) { + super(cost); + } + + @Override + public Cost copy() { + return new LiquidFireCost(this); + } + + @Override + public Cost getFixedCostsFromAnnouncedValue(int xValue) { + return null; + } + + @Override + public int getMaxValue(Ability source, Game game) { + return 5; + } + } +} diff --git a/Mage.Sets/src/mage/sets/ravnica/BorosFuryShield.java b/Mage.Sets/src/mage/sets/ravnica/BorosFuryShield.java new file mode 100644 index 00000000000..cd3867d6b10 --- /dev/null +++ b/Mage.Sets/src/mage/sets/ravnica/BorosFuryShield.java @@ -0,0 +1,111 @@ +/* + * 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.sets.ravnica; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.condition.common.ManaWasSpentCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PreventDamageByTargetEffect; +import mage.abilities.effects.common.UntapAllControllerEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.ColoredManaSymbol; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.filter.common.FilterAttackingOrBlockingCreature; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Dilnu + */ +public class BorosFuryShield extends CardImpl { + private static final FilterAttackingOrBlockingCreature filter = new FilterAttackingOrBlockingCreature(); + + public BorosFuryShield(UUID ownerId) { + super(ownerId, 5, "Boros Fury-Shield", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{2}{W}"); + this.expansionSetCode = "RAV"; + + // Prevent all combat damage that would be dealt by target attacking or blocking creature this turn. + this.getSpellAbility().addEffect(new PreventDamageByTargetEffect(Duration.EndOfTurn, true)); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); + + // If {R} was spent to cast Boros Fury-Shield, it deals damage to that creature's controller equal to the creature's power. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new BorosFuryShieldDamageEffect(), + new ManaWasSpentCondition(ColoredManaSymbol.R), "If {R} was spent to cast {this}, it deals damage to that creature's controller equal to the creature's power")); + } + + public BorosFuryShield(final BorosFuryShield card) { + super(card); + } + + @Override + public BorosFuryShield copy() { + return new BorosFuryShield(this); + } + + class BorosFuryShieldDamageEffect extends OneShotEffect { + BorosFuryShieldDamageEffect() { + super(Outcome.Damage); + staticText = "{this} deals damage to that creature's controller equal to the creature's power"; + } + + BorosFuryShieldDamageEffect(final BorosFuryShieldDamageEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + if (target != null) { + Player player = game.getPlayer(target.getControllerId()); + if (player != null) { + int power = target.getPower().getValue(); + player.damage(power, source.getId(), game, false, true); + } + + } + return false; + } + + @Override + public Effect copy() { + return new BorosFuryShieldDamageEffect(this); + } + + } +} diff --git a/Mage.Sets/src/mage/sets/ravnica/Brightflame.java b/Mage.Sets/src/mage/sets/ravnica/Brightflame.java new file mode 100644 index 00000000000..6c600a08f9a --- /dev/null +++ b/Mage.Sets/src/mage/sets/ravnica/Brightflame.java @@ -0,0 +1,120 @@ +/* + * 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.sets.ravnica; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.ManacostVariableValue; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Dilnu + */ +public class Brightflame extends CardImpl { + + public Brightflame(UUID ownerId) { + super(ownerId, 194, "Brightflame", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{X}{R}{R}{W}{W}"); + this.expansionSetCode = "RAV"; + + // Radiance - Brightflame deals X damage to target creature and each other creature that shares a color with it. You gain life equal to the damage dealt this way. + this.getSpellAbility().addEffect(new BrightflameEffect(new ManacostVariableValue())); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().setAbilityWord(AbilityWord.RADIANCE); + } + + public Brightflame(final Brightflame card) { + super(card); + } + + @Override + public Brightflame copy() { + return new Brightflame(this); + } +} + +class BrightflameEffect extends OneShotEffect { + + static final FilterPermanent filter = new FilterPermanent("creature"); + protected DynamicValue amount; + + static { + filter.add(new CardTypePredicate(CardType.CREATURE)); + } + + BrightflameEffect(DynamicValue amount) { + super(Outcome.Damage); + this.amount = amount; + staticText = "{this} deals X damage to target creature and each other creature that shares a color with it. You gain life equal to the damage dealt this way."; + } + + BrightflameEffect(final BrightflameEffect effect) { + super(effect); + this.amount = effect.amount; + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); + int damageDealt = 0; + if (target != null) { + ObjectColor color = target.getColor(game); + damageDealt += target.damage(amount.calculate(game, source, this), source.getSourceId(), game, false, true); + for (Permanent p : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) { + if (!target.getId().equals(p.getId()) && p.getColor(game).shares(color)) { + damageDealt += p.damage(amount.calculate(game, source, this), source.getSourceId(), game, false, true); + } + } + + Player you = game.getPlayer(source.getControllerId()); + if (you != null && damageDealt > 0) { + you.gainLife(damageDealt, game); + } + return true; + } + return false; + } + + @Override + public BrightflameEffect copy() { + return new BrightflameEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/ravnica/LightOfSanction.java b/Mage.Sets/src/mage/sets/ravnica/LightOfSanction.java new file mode 100644 index 00000000000..a416e99cf7b --- /dev/null +++ b/Mage.Sets/src/mage/sets/ravnica/LightOfSanction.java @@ -0,0 +1,103 @@ +/* + * 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.sets.ravnica; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.PreventionEffectImpl; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Controllable; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; + +/** + * + * @author Dilnu + */ +public class LightOfSanction extends CardImpl { + + public LightOfSanction(UUID ownerId) { + super(ownerId, 24, "Light of Sanction", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}{W}"); + this.expansionSetCode = "RAV"; + + // Prevent all damage that would be dealt to creatures you control by sources you control. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new LightOfSanctionEffect())); + } + + public LightOfSanction(final LightOfSanction card) { + super(card); + } + + @Override + public LightOfSanction copy() { + return new LightOfSanction(this); + } +} + +class LightOfSanctionEffect extends PreventionEffectImpl { + + public LightOfSanctionEffect() { + super(Duration.EndOfGame); + this.staticText = "Prevent all damage that would be dealt to creatures you control by sources you control."; + consumable = false; + } + + public LightOfSanctionEffect(LightOfSanctionEffect effect) { + super(effect); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (event.getType().equals(GameEvent.EventType.DAMAGE_CREATURE)) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null && permanent.getControllerId().equals(source.getControllerId())) { + MageObject damageSource = game.getObject(event.getSourceId()); + if (damageSource instanceof Controllable) { + return ((Controllable) damageSource).getControllerId().equals(source.getControllerId()); + } + else if (damageSource instanceof Card) { + return ((Card) damageSource).getOwnerId().equals(source.getControllerId()); + } + } + } + return false; + } + + @Override + public LightOfSanctionEffect copy() { + return new LightOfSanctionEffect(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/weatherlight/DebtOfLoyalty.java b/Mage.Sets/src/mage/sets/weatherlight/DebtOfLoyalty.java new file mode 100644 index 00000000000..d86d1d5c026 --- /dev/null +++ b/Mage.Sets/src/mage/sets/weatherlight/DebtOfLoyalty.java @@ -0,0 +1,93 @@ +/* + * 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.sets.weatherlight; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.common.RegenerateTargetEffect; +import mage.abilities.effects.common.continuous.GainControlTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author Dilnu + */ +public class DebtOfLoyalty extends CardImpl { + + public DebtOfLoyalty(UUID ownerId) { + super(ownerId, 127, "Debt of Loyalty", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{1}{W}{W}"); + this.expansionSetCode = "WTH"; + + // Regenerate target creature. You gain control of that creature if it regenerates this way. + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + this.getSpellAbility().addEffect(new DebtOfLoyaltyEffect()); + } + + public DebtOfLoyalty(final DebtOfLoyalty card) { + super(card); + } + + @Override + public DebtOfLoyalty copy() { + return new DebtOfLoyalty(this); + } + + class DebtOfLoyaltyEffect extends RegenerateTargetEffect { + public DebtOfLoyaltyEffect ( ) { + super(); + this.staticText = "Regenerate target creature. You gain control of that creature if it regenerates this way."; + } + + public DebtOfLoyaltyEffect(final DebtOfLoyaltyEffect effect) { + super(effect); + } + + @Override + public DebtOfLoyaltyEffect copy() { + return new DebtOfLoyaltyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); + if (super.apply(game, source) && permanent != null) { + GainControlTargetEffect effect = new GainControlTargetEffect(Duration.EndOfGame); + effect.setTargetPointer(targetPointer); + game.addEffect(effect, source); + return true; + } + return false; + } + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/DebtOfLoyaltyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/DebtOfLoyaltyTest.java new file mode 100644 index 00000000000..9e1072e6eb0 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/DebtOfLoyaltyTest.java @@ -0,0 +1,98 @@ +/* + * 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 org.mage.test.cards.control; + +import org.mage.test.cards.prevention.*; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class DebtOfLoyaltyTest extends CardTestPlayerBase { + + @Test + public void testDebtOfLoyaltyEffect_regen() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // Tremor deals 1 damage to each creature without flying. + addCard(Zone.HAND, playerA, "Tremor"); // Sorcery {R} + // Regenerate target creature. You gain control of that creature if it regenerates this way. + addCard(Zone.HAND, playerA, "Debt of Loyalty"); // Instant {1WW} + + addCard(Zone.BATTLEFIELD, playerB, "Metallic Sliver"); // 1/1 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Debt of Loyalty", "Metallic Sliver"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tremor"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Tremor", 1); + + assertPermanentCount(playerB, "Metallic Sliver", 0); + assertGraveyardCount(playerB, "Metallic Sliver", 0); + assertPermanentCount(playerA, "Metallic Sliver", 1); + + Permanent sliver = getPermanent("Metallic Sliver", playerA.getId()); + Assert.assertNotNull(sliver); + + // regenerate causes to tap + Assert.assertTrue(sliver.isTapped()); + } + + @Test + public void testDebtOfLoyaltyEffect_noRegen() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + // Tremor deals 1 damage to each creature without flying. + addCard(Zone.HAND, playerA, "Tremor"); // Sorcery {R} + // Regenerate target creature. You gain control of that creature if it regenerates this way. + addCard(Zone.HAND, playerA, "Debt of Loyalty"); // Instant {1WW} + + addCard(Zone.BATTLEFIELD, playerB, "Metallic Sliver"); // 1/1 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Debt of Loyalty", "Metallic Sliver"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerB, "Metallic Sliver", 1); + + Permanent sliver = getPermanent("Metallic Sliver", playerB.getId()); + Assert.assertNotNull(sliver); + + // No regeneration occured. + Assert.assertFalse(sliver.isTapped()); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/prevention/LightOfSanctionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/prevention/LightOfSanctionTest.java new file mode 100644 index 00000000000..ed4d44b215e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/prevention/LightOfSanctionTest.java @@ -0,0 +1,67 @@ +/* + * 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 org.mage.test.cards.prevention; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class LightOfSanctionTest extends CardTestPlayerBase { + + @Test + public void testLightOfSanctionEffect() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // Tremor deals 1 damage to each creature without flying. + addCard(Zone.HAND, playerA, "Tremor"); // Sorcery {R} + // Prevent all damage that would be dealt to creatures you control by sources you control. + addCard(Zone.BATTLEFIELD, playerA, "Light of Sanction"); + addCard(Zone.BATTLEFIELD, playerA, "Metallic Sliver"); // 1/1 + addCard(Zone.BATTLEFIELD, playerA, "Dross Crocodile"); // 5/1 + + addCard(Zone.BATTLEFIELD, playerB, "Metallic Sliver"); // 1/1 + addCard(Zone.BATTLEFIELD, playerB, "Dross Crocodile"); // 5/1 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tremor"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Tremor", 1); + + assertPermanentCount(playerA, "Metallic Sliver", 1); + assertPermanentCount(playerA, "Dross Crocodile", 1); + assertGraveyardCount(playerB, "Metallic Sliver", 1); + assertGraveyardCount(playerB, "Dross Crocodile", 1); + + } +} diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index b03bf8bbff4..29b94e96762 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -493,7 +493,10 @@ public abstract class AbilityImpl implements Ability { for (VariableCost variableCost : this.costs.getVariableCosts()) { if (!(variableCost instanceof VariableManaCost)) { int xValue = variableCost.announceXValue(this, game); - costs.add(variableCost.getFixedCostsFromAnnouncedValue(xValue)); + Cost fixedCost = variableCost.getFixedCostsFromAnnouncedValue(xValue); + if (fixedCost != null) { + costs.add(fixedCost); + } // set the xcosts to paid variableCost.setAmount(xValue); ((Cost) variableCost).setPaid(); From 3f0ff01bbc00a42f2437c24963f786c3771a3106 Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Sun, 10 Jul 2016 18:47:32 -0400 Subject: [PATCH 2/8] Add files via upload --- commit1.patch | 798 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 798 insertions(+) create mode 100644 commit1.patch diff --git a/commit1.patch b/commit1.patch new file mode 100644 index 00000000000..1046956bf85 --- /dev/null +++ b/commit1.patch @@ -0,0 +1,798 @@ +From f9b0befac3548f209a73d4b658d60081f54458c3 Mon Sep 17 00:00:00 2001 +From: dilnu +Date: Jul 10, 2016 6:39:42 PM + +Implement several cards and tests for some of the more complex cards. + +This also makes a small change to AbilityImpl's handling of variable +costs which may not be needed. + +diff --git a/Mage.Sets/src/mage/sets/odyssey/LiquidFire.java b/Mage.Sets/src/mage/sets/odyssey/LiquidFire.java +new file mode 100644 +index 0000000..34e3893 +--- /dev/null ++++ b/Mage.Sets/src/mage/sets/odyssey/LiquidFire.java +@@ -0,0 +1,137 @@ ++/* ++ * 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.sets.odyssey; ++ ++import java.util.UUID; ++import mage.abilities.Ability; ++import mage.abilities.costs.Cost; ++import mage.abilities.costs.CostImpl; ++import mage.abilities.costs.VariableCostImpl; ++import mage.abilities.dynamicvalue.DynamicValue; ++import mage.abilities.dynamicvalue.common.GetXValue; ++import mage.abilities.effects.Effect; ++import mage.abilities.effects.OneShotEffect; ++import mage.cards.CardImpl; ++import mage.constants.CardType; ++import mage.constants.Outcome; ++import mage.constants.Rarity; ++import mage.game.Game; ++import mage.game.permanent.Permanent; ++import mage.players.Player; ++import mage.target.common.TargetCreaturePermanent; ++ ++/** ++ * ++ * @author Dilnu ++ */ ++public class LiquidFire extends CardImpl { ++ ++ public LiquidFire(UUID ownerId) { ++ super(ownerId, 201, "Liquid Fire", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{4}{R}{R}"); ++ this.expansionSetCode = "ODY"; ++ ++ // As an additional cost to cast Liquid Fire, choose a number between 0 and 5. ++ this.getSpellAbility().addCost(new LiquidFireCost()); ++ // Liquid Fire deals X damage to target creature and 5 minus X damage to that creature's controller, where X is the chosen number. ++ DynamicValue choiceValue = new GetXValue(); ++ this.getSpellAbility().addTarget(new TargetCreaturePermanent()); ++ this.getSpellAbility().addEffect(new LiquidFireEffect(choiceValue)); ++ ++ } ++ ++ public LiquidFire(final LiquidFire card) { ++ super(card); ++ } ++ ++ @Override ++ public LiquidFire copy() { ++ return new LiquidFire(this); ++ } ++ ++ private static class LiquidFireEffect extends OneShotEffect { ++ protected DynamicValue choiceValue; ++ ++ public LiquidFireEffect(DynamicValue choiceValue) { ++ super(Outcome.Damage); ++ this.staticText = "{this} deals X damage to target creature and 5 minus X damage to that creature's controller, where X is the chosen number."; ++ this.choiceValue = choiceValue; ++ } ++ ++ public LiquidFireEffect(LiquidFireEffect effect) { ++ super(effect); ++ this.choiceValue = effect.choiceValue; ++ } ++ ++ @Override ++ public boolean apply(Game game, Ability source) { ++ Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); ++ int creatureDamage = choiceValue.calculate(game, source, this); ++ int playerDamage = 5 - creatureDamage; ++ if (target != null) { ++ target.damage(creatureDamage, source.getSourceId(), game, false, true); ++ Player controller = game.getPlayer(target.getControllerId()); ++ if (controller != null) { ++ controller.damage(playerDamage, source.getSourceId(), game, false, true); ++ } ++ return true; ++ } ++ return false; ++ } ++ ++ @Override ++ public Effect copy() { ++ return new LiquidFireEffect(this); ++ } ++ } ++ ++ class LiquidFireCost extends VariableCostImpl { ++ public LiquidFireCost() { ++ super("Choose a Number"); ++ this.text = "As an additional cost to cast {source}, choose a number between 0 and 5"; ++ } ++ ++ public LiquidFireCost(final LiquidFireCost cost) { ++ super(cost); ++ } ++ ++ @Override ++ public Cost copy() { ++ return new LiquidFireCost(this); ++ } ++ ++ @Override ++ public Cost getFixedCostsFromAnnouncedValue(int xValue) { ++ return null; ++ } ++ ++ @Override ++ public int getMaxValue(Ability source, Game game) { ++ return 5; ++ } ++ } ++} +diff --git a/Mage.Sets/src/mage/sets/ravnica/BorosFuryShield.java b/Mage.Sets/src/mage/sets/ravnica/BorosFuryShield.java +new file mode 100644 +index 0000000..cd3867d +--- /dev/null ++++ b/Mage.Sets/src/mage/sets/ravnica/BorosFuryShield.java +@@ -0,0 +1,111 @@ ++/* ++ * 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.sets.ravnica; ++ ++import java.util.UUID; ++import mage.abilities.Ability; ++import mage.abilities.condition.common.ManaWasSpentCondition; ++import mage.abilities.decorator.ConditionalOneShotEffect; ++import mage.abilities.effects.Effect; ++import mage.abilities.effects.OneShotEffect; ++import mage.abilities.effects.common.PreventDamageByTargetEffect; ++import mage.abilities.effects.common.UntapAllControllerEffect; ++import mage.cards.CardImpl; ++import mage.constants.CardType; ++import mage.constants.ColoredManaSymbol; ++import mage.constants.Duration; ++import mage.constants.Outcome; ++import mage.constants.Rarity; ++import mage.filter.common.FilterAttackingOrBlockingCreature; ++import mage.filter.common.FilterControlledCreaturePermanent; ++import mage.game.Game; ++import mage.game.permanent.Permanent; ++import mage.players.Player; ++import mage.target.common.TargetCreaturePermanent; ++ ++/** ++ * ++ * @author Dilnu ++ */ ++public class BorosFuryShield extends CardImpl { ++ private static final FilterAttackingOrBlockingCreature filter = new FilterAttackingOrBlockingCreature(); ++ ++ public BorosFuryShield(UUID ownerId) { ++ super(ownerId, 5, "Boros Fury-Shield", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{2}{W}"); ++ this.expansionSetCode = "RAV"; ++ ++ // Prevent all combat damage that would be dealt by target attacking or blocking creature this turn. ++ this.getSpellAbility().addEffect(new PreventDamageByTargetEffect(Duration.EndOfTurn, true)); ++ this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter)); ++ ++ // If {R} was spent to cast Boros Fury-Shield, it deals damage to that creature's controller equal to the creature's power. ++ this.getSpellAbility().addEffect(new ConditionalOneShotEffect( ++ new BorosFuryShieldDamageEffect(), ++ new ManaWasSpentCondition(ColoredManaSymbol.R), "If {R} was spent to cast {this}, it deals damage to that creature's controller equal to the creature's power")); ++ } ++ ++ public BorosFuryShield(final BorosFuryShield card) { ++ super(card); ++ } ++ ++ @Override ++ public BorosFuryShield copy() { ++ return new BorosFuryShield(this); ++ } ++ ++ class BorosFuryShieldDamageEffect extends OneShotEffect { ++ BorosFuryShieldDamageEffect() { ++ super(Outcome.Damage); ++ staticText = "{this} deals damage to that creature's controller equal to the creature's power"; ++ } ++ ++ BorosFuryShieldDamageEffect(final BorosFuryShieldDamageEffect effect) { ++ super(effect); ++ } ++ ++ @Override ++ public boolean apply(Game game, Ability source) { ++ Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); ++ if (target != null) { ++ Player player = game.getPlayer(target.getControllerId()); ++ if (player != null) { ++ int power = target.getPower().getValue(); ++ player.damage(power, source.getId(), game, false, true); ++ } ++ ++ } ++ return false; ++ } ++ ++ @Override ++ public Effect copy() { ++ return new BorosFuryShieldDamageEffect(this); ++ } ++ ++ } ++} +diff --git a/Mage.Sets/src/mage/sets/ravnica/Brightflame.java b/Mage.Sets/src/mage/sets/ravnica/Brightflame.java +new file mode 100644 +index 0000000..6c600a0 +--- /dev/null ++++ b/Mage.Sets/src/mage/sets/ravnica/Brightflame.java +@@ -0,0 +1,120 @@ ++/* ++ * 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.sets.ravnica; ++ ++import java.util.UUID; ++import mage.ObjectColor; ++import mage.abilities.Ability; ++import mage.abilities.dynamicvalue.DynamicValue; ++import mage.abilities.dynamicvalue.common.ManacostVariableValue; ++import mage.abilities.effects.OneShotEffect; ++import mage.cards.CardImpl; ++import mage.constants.AbilityWord; ++import mage.constants.CardType; ++import mage.constants.Outcome; ++import mage.constants.Rarity; ++import mage.filter.FilterPermanent; ++import mage.filter.predicate.mageobject.CardTypePredicate; ++import mage.game.Game; ++import mage.game.permanent.Permanent; ++import mage.players.Player; ++import mage.target.common.TargetCreaturePermanent; ++ ++/** ++ * ++ * @author Dilnu ++ */ ++public class Brightflame extends CardImpl { ++ ++ public Brightflame(UUID ownerId) { ++ super(ownerId, 194, "Brightflame", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{X}{R}{R}{W}{W}"); ++ this.expansionSetCode = "RAV"; ++ ++ // Radiance - Brightflame deals X damage to target creature and each other creature that shares a color with it. You gain life equal to the damage dealt this way. ++ this.getSpellAbility().addEffect(new BrightflameEffect(new ManacostVariableValue())); ++ this.getSpellAbility().addTarget(new TargetCreaturePermanent()); ++ this.getSpellAbility().setAbilityWord(AbilityWord.RADIANCE); ++ } ++ ++ public Brightflame(final Brightflame card) { ++ super(card); ++ } ++ ++ @Override ++ public Brightflame copy() { ++ return new Brightflame(this); ++ } ++} ++ ++class BrightflameEffect extends OneShotEffect { ++ ++ static final FilterPermanent filter = new FilterPermanent("creature"); ++ protected DynamicValue amount; ++ ++ static { ++ filter.add(new CardTypePredicate(CardType.CREATURE)); ++ } ++ ++ BrightflameEffect(DynamicValue amount) { ++ super(Outcome.Damage); ++ this.amount = amount; ++ staticText = "{this} deals X damage to target creature and each other creature that shares a color with it. You gain life equal to the damage dealt this way."; ++ } ++ ++ BrightflameEffect(final BrightflameEffect effect) { ++ super(effect); ++ this.amount = effect.amount; ++ } ++ ++ @Override ++ public boolean apply(Game game, Ability source) { ++ Permanent target = game.getPermanent(targetPointer.getFirst(game, source)); ++ int damageDealt = 0; ++ if (target != null) { ++ ObjectColor color = target.getColor(game); ++ damageDealt += target.damage(amount.calculate(game, source, this), source.getSourceId(), game, false, true); ++ for (Permanent p : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) { ++ if (!target.getId().equals(p.getId()) && p.getColor(game).shares(color)) { ++ damageDealt += p.damage(amount.calculate(game, source, this), source.getSourceId(), game, false, true); ++ } ++ } ++ ++ Player you = game.getPlayer(source.getControllerId()); ++ if (you != null && damageDealt > 0) { ++ you.gainLife(damageDealt, game); ++ } ++ return true; ++ } ++ return false; ++ } ++ ++ @Override ++ public BrightflameEffect copy() { ++ return new BrightflameEffect(this); ++ } ++} +\ No newline at end of file +diff --git a/Mage.Sets/src/mage/sets/ravnica/LightOfSanction.java b/Mage.Sets/src/mage/sets/ravnica/LightOfSanction.java +new file mode 100644 +index 0000000..a416e99 +--- /dev/null ++++ b/Mage.Sets/src/mage/sets/ravnica/LightOfSanction.java +@@ -0,0 +1,103 @@ ++/* ++ * 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.sets.ravnica; ++ ++import java.util.UUID; ++import mage.MageObject; ++import mage.abilities.Ability; ++import mage.abilities.common.SimpleStaticAbility; ++import mage.abilities.effects.PreventionEffectImpl; ++import mage.cards.Card; ++import mage.cards.CardImpl; ++import mage.constants.CardType; ++import mage.constants.Duration; ++import mage.constants.Rarity; ++import mage.constants.Zone; ++import mage.game.Controllable; ++import mage.game.Game; ++import mage.game.events.GameEvent; ++import mage.game.permanent.Permanent; ++ ++/** ++ * ++ * @author Dilnu ++ */ ++public class LightOfSanction extends CardImpl { ++ ++ public LightOfSanction(UUID ownerId) { ++ super(ownerId, 24, "Light of Sanction", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}{W}"); ++ this.expansionSetCode = "RAV"; ++ ++ // Prevent all damage that would be dealt to creatures you control by sources you control. ++ this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new LightOfSanctionEffect())); ++ } ++ ++ public LightOfSanction(final LightOfSanction card) { ++ super(card); ++ } ++ ++ @Override ++ public LightOfSanction copy() { ++ return new LightOfSanction(this); ++ } ++} ++ ++class LightOfSanctionEffect extends PreventionEffectImpl { ++ ++ public LightOfSanctionEffect() { ++ super(Duration.EndOfGame); ++ this.staticText = "Prevent all damage that would be dealt to creatures you control by sources you control."; ++ consumable = false; ++ } ++ ++ public LightOfSanctionEffect(LightOfSanctionEffect effect) { ++ super(effect); ++ } ++ ++ @Override ++ public boolean applies(GameEvent event, Ability source, Game game) { ++ if (event.getType().equals(GameEvent.EventType.DAMAGE_CREATURE)) { ++ Permanent permanent = game.getPermanent(event.getTargetId()); ++ if (permanent != null && permanent.getControllerId().equals(source.getControllerId())) { ++ MageObject damageSource = game.getObject(event.getSourceId()); ++ if (damageSource instanceof Controllable) { ++ return ((Controllable) damageSource).getControllerId().equals(source.getControllerId()); ++ } ++ else if (damageSource instanceof Card) { ++ return ((Card) damageSource).getOwnerId().equals(source.getControllerId()); ++ } ++ } ++ } ++ return false; ++ } ++ ++ @Override ++ public LightOfSanctionEffect copy() { ++ return new LightOfSanctionEffect(this); ++ } ++} +\ No newline at end of file +diff --git a/Mage.Sets/src/mage/sets/weatherlight/DebtOfLoyalty.java b/Mage.Sets/src/mage/sets/weatherlight/DebtOfLoyalty.java +new file mode 100644 +index 0000000..d86d1d5 +--- /dev/null ++++ b/Mage.Sets/src/mage/sets/weatherlight/DebtOfLoyalty.java +@@ -0,0 +1,93 @@ ++/* ++ * 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.sets.weatherlight; ++ ++import java.util.UUID; ++import mage.abilities.Ability; ++import mage.abilities.effects.common.RegenerateTargetEffect; ++import mage.abilities.effects.common.continuous.GainControlTargetEffect; ++import mage.cards.CardImpl; ++import mage.constants.CardType; ++import mage.constants.Duration; ++import mage.constants.Rarity; ++import mage.game.Game; ++import mage.game.permanent.Permanent; ++import mage.target.common.TargetCreaturePermanent; ++ ++/** ++ * ++ * @author Dilnu ++ */ ++public class DebtOfLoyalty extends CardImpl { ++ ++ public DebtOfLoyalty(UUID ownerId) { ++ super(ownerId, 127, "Debt of Loyalty", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{1}{W}{W}"); ++ this.expansionSetCode = "WTH"; ++ ++ // Regenerate target creature. You gain control of that creature if it regenerates this way. ++ this.getSpellAbility().addTarget(new TargetCreaturePermanent()); ++ this.getSpellAbility().addEffect(new DebtOfLoyaltyEffect()); ++ } ++ ++ public DebtOfLoyalty(final DebtOfLoyalty card) { ++ super(card); ++ } ++ ++ @Override ++ public DebtOfLoyalty copy() { ++ return new DebtOfLoyalty(this); ++ } ++ ++ class DebtOfLoyaltyEffect extends RegenerateTargetEffect { ++ public DebtOfLoyaltyEffect ( ) { ++ super(); ++ this.staticText = "Regenerate target creature. You gain control of that creature if it regenerates this way."; ++ } ++ ++ public DebtOfLoyaltyEffect(final DebtOfLoyaltyEffect effect) { ++ super(effect); ++ } ++ ++ @Override ++ public DebtOfLoyaltyEffect copy() { ++ return new DebtOfLoyaltyEffect(this); ++ } ++ ++ @Override ++ public boolean apply(Game game, Ability source) { ++ Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source)); ++ if (super.apply(game, source) && permanent != null) { ++ GainControlTargetEffect effect = new GainControlTargetEffect(Duration.EndOfGame); ++ effect.setTargetPointer(targetPointer); ++ game.addEffect(effect, source); ++ return true; ++ } ++ return false; ++ } ++ } ++} +diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/DebtOfLoyaltyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/DebtOfLoyaltyTest.java +new file mode 100644 +index 0000000..9e1072e +--- /dev/null ++++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/DebtOfLoyaltyTest.java +@@ -0,0 +1,98 @@ ++/* ++ * 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 org.mage.test.cards.control; ++ ++import org.mage.test.cards.prevention.*; ++import mage.constants.PhaseStep; ++import mage.constants.Zone; ++import mage.game.permanent.Permanent; ++import org.junit.Assert; ++import org.junit.Test; ++import org.mage.test.serverside.base.CardTestPlayerBase; ++ ++/** ++ * ++ * @author LevelX2 ++ */ ++public class DebtOfLoyaltyTest extends CardTestPlayerBase { ++ ++ @Test ++ public void testDebtOfLoyaltyEffect_regen() { ++ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); ++ addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); ++ // Tremor deals 1 damage to each creature without flying. ++ addCard(Zone.HAND, playerA, "Tremor"); // Sorcery {R} ++ // Regenerate target creature. You gain control of that creature if it regenerates this way. ++ addCard(Zone.HAND, playerA, "Debt of Loyalty"); // Instant {1WW} ++ ++ addCard(Zone.BATTLEFIELD, playerB, "Metallic Sliver"); // 1/1 ++ ++ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Debt of Loyalty", "Metallic Sliver"); ++ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tremor"); ++ ++ setStopAt(1, PhaseStep.BEGIN_COMBAT); ++ execute(); ++ ++ assertGraveyardCount(playerA, "Tremor", 1); ++ ++ assertPermanentCount(playerB, "Metallic Sliver", 0); ++ assertGraveyardCount(playerB, "Metallic Sliver", 0); ++ assertPermanentCount(playerA, "Metallic Sliver", 1); ++ ++ Permanent sliver = getPermanent("Metallic Sliver", playerA.getId()); ++ Assert.assertNotNull(sliver); ++ ++ // regenerate causes to tap ++ Assert.assertTrue(sliver.isTapped()); ++ } ++ ++ @Test ++ public void testDebtOfLoyaltyEffect_noRegen() { ++ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); ++ addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); ++ // Tremor deals 1 damage to each creature without flying. ++ addCard(Zone.HAND, playerA, "Tremor"); // Sorcery {R} ++ // Regenerate target creature. You gain control of that creature if it regenerates this way. ++ addCard(Zone.HAND, playerA, "Debt of Loyalty"); // Instant {1WW} ++ ++ addCard(Zone.BATTLEFIELD, playerB, "Metallic Sliver"); // 1/1 ++ ++ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Debt of Loyalty", "Metallic Sliver"); ++ ++ setStopAt(1, PhaseStep.BEGIN_COMBAT); ++ execute(); ++ ++ assertPermanentCount(playerB, "Metallic Sliver", 1); ++ ++ Permanent sliver = getPermanent("Metallic Sliver", playerB.getId()); ++ Assert.assertNotNull(sliver); ++ ++ // No regeneration occured. ++ Assert.assertFalse(sliver.isTapped()); ++ } ++} +diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/prevention/LightOfSanctionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/prevention/LightOfSanctionTest.java +new file mode 100644 +index 0000000..ed4d44b +--- /dev/null ++++ b/Mage.Tests/src/test/java/org/mage/test/cards/prevention/LightOfSanctionTest.java +@@ -0,0 +1,67 @@ ++/* ++ * 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 org.mage.test.cards.prevention; ++ ++import mage.constants.PhaseStep; ++import mage.constants.Zone; ++import org.junit.Test; ++import org.mage.test.serverside.base.CardTestPlayerBase; ++ ++/** ++ * ++ * @author LevelX2 ++ */ ++public class LightOfSanctionTest extends CardTestPlayerBase { ++ ++ @Test ++ public void testLightOfSanctionEffect() { ++ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); ++ // Tremor deals 1 damage to each creature without flying. ++ addCard(Zone.HAND, playerA, "Tremor"); // Sorcery {R} ++ // Prevent all damage that would be dealt to creatures you control by sources you control. ++ addCard(Zone.BATTLEFIELD, playerA, "Light of Sanction"); ++ addCard(Zone.BATTLEFIELD, playerA, "Metallic Sliver"); // 1/1 ++ addCard(Zone.BATTLEFIELD, playerA, "Dross Crocodile"); // 5/1 ++ ++ addCard(Zone.BATTLEFIELD, playerB, "Metallic Sliver"); // 1/1 ++ addCard(Zone.BATTLEFIELD, playerB, "Dross Crocodile"); // 5/1 ++ ++ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tremor"); ++ ++ setStopAt(1, PhaseStep.BEGIN_COMBAT); ++ execute(); ++ ++ assertGraveyardCount(playerA, "Tremor", 1); ++ ++ assertPermanentCount(playerA, "Metallic Sliver", 1); ++ assertPermanentCount(playerA, "Dross Crocodile", 1); ++ assertGraveyardCount(playerB, "Metallic Sliver", 1); ++ assertGraveyardCount(playerB, "Dross Crocodile", 1); ++ ++ } ++} +diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java +index b03bf8b..29b94e9 100644 +--- a/Mage/src/main/java/mage/abilities/AbilityImpl.java ++++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java +@@ -493,7 +493,10 @@ + for (VariableCost variableCost : this.costs.getVariableCosts()) { + if (!(variableCost instanceof VariableManaCost)) { + int xValue = variableCost.announceXValue(this, game); +- costs.add(variableCost.getFixedCostsFromAnnouncedValue(xValue)); ++ Cost fixedCost = variableCost.getFixedCostsFromAnnouncedValue(xValue); ++ if (fixedCost != null) { ++ costs.add(fixedCost); ++ } + // set the xcosts to paid + variableCost.setAmount(xValue); + ((Cost) variableCost).setPaid(); From 8fe5bf4fbbb9e2f29292b7c3431871e476e27129 Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Sun, 10 Jul 2016 18:55:58 -0400 Subject: [PATCH 3/8] Update the uses of PreventAllDamageByAllPermanentsEffect to use the new name. --- Mage.Sets/src/mage/sets/gatecrash/Hindervines.java | 4 ++-- Mage.Sets/src/mage/sets/onslaught/LeeryFogbeast.java | 4 ++-- Mage.Sets/src/mage/sets/planeshift/RadiantKavu.java | 4 ++-- Mage.Sets/src/mage/sets/stronghold/ConstantMists.java | 4 ++-- Mage.Sets/src/mage/sets/timeshifted/Darkness.java | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Mage.Sets/src/mage/sets/gatecrash/Hindervines.java b/Mage.Sets/src/mage/sets/gatecrash/Hindervines.java index aace092ee12..3c31ca36337 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/Hindervines.java +++ b/Mage.Sets/src/mage/sets/gatecrash/Hindervines.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Rarity; -import mage.abilities.effects.common.PreventAllDamageByAllEffect; +import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect; import mage.cards.CardImpl; import mage.counters.CounterType; import mage.filter.common.FilterCreaturePermanent; @@ -56,7 +56,7 @@ public class Hindervines extends CardImpl { // Prevent all combat damage that would be dealt this turn by creatures with no +1/+1 counters on them. - this.getSpellAbility().addEffect(new PreventAllDamageByAllEffect(filter, Duration.EndOfTurn, true)); + this.getSpellAbility().addEffect(new PreventAllDamageByAllPermanentsEffect(filter, Duration.EndOfTurn, true)); } public Hindervines(final Hindervines card) { diff --git a/Mage.Sets/src/mage/sets/onslaught/LeeryFogbeast.java b/Mage.Sets/src/mage/sets/onslaught/LeeryFogbeast.java index 9e37a65723d..0ec4e26cb20 100644 --- a/Mage.Sets/src/mage/sets/onslaught/LeeryFogbeast.java +++ b/Mage.Sets/src/mage/sets/onslaught/LeeryFogbeast.java @@ -33,7 +33,7 @@ import mage.constants.CardType; import mage.constants.Rarity; import mage.MageInt; import mage.abilities.common.BecomesBlockedTriggeredAbility; -import mage.abilities.effects.common.PreventAllDamageByAllEffect; +import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect; import mage.cards.CardImpl; import mage.constants.Duration; @@ -52,7 +52,7 @@ public class LeeryFogbeast extends CardImpl { this.toughness = new MageInt(2); // Whenever Leery Fogbeast becomes blocked, prevent all combat damage that would be dealt this turn. - this.addAbility(new BecomesBlockedTriggeredAbility(new PreventAllDamageByAllEffect(Duration.EndOfTurn, true), false)); + this.addAbility(new BecomesBlockedTriggeredAbility(new PreventAllDamageByAllPermanentsEffect(Duration.EndOfTurn, true), false)); } public LeeryFogbeast(final LeeryFogbeast card) { diff --git a/Mage.Sets/src/mage/sets/planeshift/RadiantKavu.java b/Mage.Sets/src/mage/sets/planeshift/RadiantKavu.java index e29f6f24da0..c0c4143951a 100644 --- a/Mage.Sets/src/mage/sets/planeshift/RadiantKavu.java +++ b/Mage.Sets/src/mage/sets/planeshift/RadiantKavu.java @@ -32,7 +32,7 @@ import mage.MageInt; import mage.ObjectColor; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.common.PreventAllDamageByAllEffect; +import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; @@ -61,7 +61,7 @@ public class RadiantKavu extends CardImpl { this.toughness = new MageInt(3); // {R}{G}{W}: Prevent all combat damage blue creatures and black creatures would deal this turn. - this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, (new PreventAllDamageByAllEffect(filter, + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, (new PreventAllDamageByAllPermanentsEffect(filter, Duration.EndOfTurn, true)), new ManaCostsImpl("{R}{G}{W}"))); } diff --git a/Mage.Sets/src/mage/sets/stronghold/ConstantMists.java b/Mage.Sets/src/mage/sets/stronghold/ConstantMists.java index a35e1777b99..564cb47da1b 100644 --- a/Mage.Sets/src/mage/sets/stronghold/ConstantMists.java +++ b/Mage.Sets/src/mage/sets/stronghold/ConstantMists.java @@ -29,7 +29,7 @@ package mage.sets.stronghold; import java.util.UUID; import mage.abilities.costs.common.SacrificeTargetCost; -import mage.abilities.effects.common.PreventAllDamageByAllEffect; +import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect; import mage.abilities.keyword.BuybackAbility; import mage.cards.CardImpl; import mage.constants.CardType; @@ -52,7 +52,7 @@ public class ConstantMists extends CardImpl { this.addAbility(new BuybackAbility(new SacrificeTargetCost(new TargetControlledPermanent(new FilterControlledLandPermanent("a land"))))); // Prevent all combat damage that would be dealt this turn. - this.getSpellAbility().addEffect(new PreventAllDamageByAllEffect(Duration.EndOfTurn, true)); + this.getSpellAbility().addEffect(new PreventAllDamageByAllPermanentsEffect(Duration.EndOfTurn, true)); } public ConstantMists(final ConstantMists card) { diff --git a/Mage.Sets/src/mage/sets/timeshifted/Darkness.java b/Mage.Sets/src/mage/sets/timeshifted/Darkness.java index 66494f637a9..fbebfd7b95f 100644 --- a/Mage.Sets/src/mage/sets/timeshifted/Darkness.java +++ b/Mage.Sets/src/mage/sets/timeshifted/Darkness.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.constants.CardType; import mage.constants.Rarity; -import mage.abilities.effects.common.PreventAllDamageByAllEffect; +import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect; import mage.cards.CardImpl; import mage.constants.Duration; @@ -47,7 +47,7 @@ public class Darkness extends CardImpl { // Prevent all combat damage that would be dealt this turn. - this.getSpellAbility().addEffect(new PreventAllDamageByAllEffect(Duration.EndOfTurn, true)); + this.getSpellAbility().addEffect(new PreventAllDamageByAllPermanentsEffect(Duration.EndOfTurn, true)); } public Darkness(final Darkness card) { From c7cdd792d04a76fe1e0cc24a604af3967ab4b7fc Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Sun, 10 Jul 2016 19:05:41 -0400 Subject: [PATCH 4/8] Remove Merge conflict artifacts. --- Mage.Sets/src/mage/sets/gatecrash/Hindervines.java | 5 ----- Mage.Sets/src/mage/sets/timeshifted/Darkness.java | 5 ----- 2 files changed, 10 deletions(-) diff --git a/Mage.Sets/src/mage/sets/gatecrash/Hindervines.java b/Mage.Sets/src/mage/sets/gatecrash/Hindervines.java index 2486f81c8a6..bf4c2322db0 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/Hindervines.java +++ b/Mage.Sets/src/mage/sets/gatecrash/Hindervines.java @@ -33,11 +33,6 @@ import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Rarity; -<<<<<<< HEAD -import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect; -import mage.cards.CardImpl; -======= ->>>>>>> origin/master import mage.counters.CounterType; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.Predicates; diff --git a/Mage.Sets/src/mage/sets/timeshifted/Darkness.java b/Mage.Sets/src/mage/sets/timeshifted/Darkness.java index 4fd5121abcb..945668e01a9 100644 --- a/Mage.Sets/src/mage/sets/timeshifted/Darkness.java +++ b/Mage.Sets/src/mage/sets/timeshifted/Darkness.java @@ -28,12 +28,7 @@ package mage.sets.timeshifted; import java.util.UUID; -<<<<<<< HEAD -import mage.constants.CardType; -import mage.constants.Rarity; -======= ->>>>>>> origin/master import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect; import mage.cards.CardImpl; import mage.constants.CardType; From bf613572e903faa54dbab47ce2859fde7da04fd8 Mon Sep 17 00:00:00 2001 From: Samuel Sandeen Date: Sun, 10 Jul 2016 19:07:53 -0400 Subject: [PATCH 5/8] Fix another merge artifact. --- Mage.Sets/src/mage/sets/timeshifted/Darkness.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/timeshifted/Darkness.java b/Mage.Sets/src/mage/sets/timeshifted/Darkness.java index 945668e01a9..8a1d6131eda 100644 --- a/Mage.Sets/src/mage/sets/timeshifted/Darkness.java +++ b/Mage.Sets/src/mage/sets/timeshifted/Darkness.java @@ -28,7 +28,6 @@ package mage.sets.timeshifted; import java.util.UUID; - import mage.abilities.effects.common.PreventAllDamageByAllPermanentsEffect; import mage.cards.CardImpl; import mage.constants.CardType; From 3c52670d61f66c6984bb21032addc58aa3cc3fd2 Mon Sep 17 00:00:00 2001 From: CountAndromalius Date: Mon, 11 Jul 2016 02:27:38 -0300 Subject: [PATCH 6/8] Implementing Serum Tank (Mirrodin and Planechase) --- .../src/mage/sets/mirrodin/SerumTank.java | 117 ++++++++++++++++++ .../src/mage/sets/planechase/SerumTank.java | 53 ++++++++ 2 files changed, 170 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/mirrodin/SerumTank.java create mode 100644 Mage.Sets/src/mage/sets/planechase/SerumTank.java diff --git a/Mage.Sets/src/mage/sets/mirrodin/SerumTank.java b/Mage.Sets/src/mage/sets/mirrodin/SerumTank.java new file mode 100644 index 00000000000..20a855539bb --- /dev/null +++ b/Mage.Sets/src/mage/sets/mirrodin/SerumTank.java @@ -0,0 +1,117 @@ +/* + * 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.sets.mirrodin; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.costs.common.RemoveCountersSourceCost; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; +import mage.counters.CounterType; + +/** + * + * @author CountAndromalius + */ +public class SerumTank extends CardImpl { + + public SerumTank(UUID ownerId) { + super(ownerId, 240, "Serum Tank", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{3}"); + this.expansionSetCode = "MRD"; + + // Whenever {this} or another artifact comes into play, put a charge counter on {this}. + Effect effect = new AddCountersSourceEffect(CounterType.CHARGE.createInstance()); + etbEffect.setText("put a charge counter on {this}") + this.addAbility(new SerumTankTriggeredAbility(effect)); + + // {3}, {tap}, Remove a charge counter from {this}: Draw a card. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), new ManaCostsImpl("{3}")); + ability.addCost(new RemoveCountersSourceCost(CounterType.CHARGE.createInstance(1))); + ability.addCost(new TapSourceCost()); + this.addAbility(ability); + } + + public SerumTank(final SerumTank card) { + super(card); + } + + @java.lang.Override + public SerumTank copy() { + return new SerumTank(this); + } +} + +class SerumTankTriggeredAbility extends TriggeredAbilityImpl { + + SerumTankTriggeredAbility(Effect effect) { + super(Zone.BATTLEFIELD, effect, false); + } + + SerumTankTriggeredAbility(final SerumTankTriggeredAbility ability) { + super(ability); + } + + @Override + public SerumTankTriggeredAbility copy() { + return new SerumTankTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + UUID targetId = event.getTargetId(); + Permanent permanent = game.getPermanent(targetId); + if (permanent.getCardType().contains(CardType.ARTIFACT)) { + for (Effect effect : this.getEffects()) { + effect.setTargetPointer(new FixedTarget(this)); + } + return true; + } + return false; + } + + @Override + public String getRule() { + return "Whenever {this} or another artifact enters the battlefield, put a charge counter on {this}."; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/planechase/SerumTank.java b/Mage.Sets/src/mage/sets/planechase/SerumTank.java new file mode 100644 index 00000000000..3994d591433 --- /dev/null +++ b/Mage.Sets/src/mage/sets/planechase/SerumTank.java @@ -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.sets.planechase; + +import java.util.UUID; + +/** + * + * @author CountAndromalius + */ +public class SerumTank extends mage.sets.mirrodin.SerumTank { + + public SerumTank (UUID ownerId) { + super(ownerId); + this.cardNumber = 125; + this.expansionSetCode = "HOP"; + } + + public SerumTank (final SerumTank card) { + super(card); + } + + @Override + public SerumTank copy() { + return new SerumTank(this); + } +} From e1b3428a391daaf8e88e95ca549762dcffb6edec Mon Sep 17 00:00:00 2001 From: emerald000 Date: Mon, 11 Jul 2016 04:49:40 -0400 Subject: [PATCH 7/8] [EMN] Implemented basic Meld functionality. --- .../sets/blessedvscursed/EerieInterlude.java | 16 +- .../sets/eldritchmoon/ChitteringHost.java | 8 +- .../src/mage/sets/eldritchmoon/GrafRats.java | 15 +- .../mage/sets/eldritchmoon/LongRoadHome.java | 12 +- .../shadowsoverinnistrad/EssenceFlux.java | 71 ++-- .../condition/common/MeldCondition.java | 67 ++++ .../abilities/effects/common/MeldEffect.java | 111 ++++++ ...tlefieldUnderOwnerControlTargetEffect.java | 44 ++- ...ttlefieldUnderYourControlTargetEffect.java | 27 +- Mage/src/main/java/mage/cards/MeldCard.java | 373 ++++++++++++++++++ Mage/src/main/java/mage/game/Game.java | 7 +- Mage/src/main/java/mage/game/GameImpl.java | 18 +- .../mage/game/permanent/PermanentMeld.java | 177 +++++++++ .../main/java/mage/players/PlayerImpl.java | 243 ++++++++---- 14 files changed, 1053 insertions(+), 136 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/condition/common/MeldCondition.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java create mode 100644 Mage/src/main/java/mage/cards/MeldCard.java create mode 100644 Mage/src/main/java/mage/game/permanent/PermanentMeld.java diff --git a/Mage.Sets/src/mage/sets/blessedvscursed/EerieInterlude.java b/Mage.Sets/src/mage/sets/blessedvscursed/EerieInterlude.java index 9c51e48efac..c35d5a1b0d0 100644 --- a/Mage.Sets/src/mage/sets/blessedvscursed/EerieInterlude.java +++ b/Mage.Sets/src/mage/sets/blessedvscursed/EerieInterlude.java @@ -40,12 +40,15 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.Cards; import mage.cards.CardsImpl; +import mage.cards.MeldCard; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Rarity; import mage.filter.common.FilterControlledCreaturePermanent; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.permanent.PermanentCard; +import mage.game.permanent.PermanentMeld; import mage.players.Player; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.targetpointer.FixedTargets; @@ -105,7 +108,18 @@ class EerieInterludeEffect extends OneShotEffect { Cards cardsToReturn = new CardsImpl(); for (Card exiled : toExile) { - if (((Permanent) exiled).getZoneChangeCounter(game) == game.getState().getZoneChangeCounter(exiled.getId()) - 1) { + if (exiled instanceof PermanentMeld) { + MeldCard meldCard = (MeldCard) ((PermanentCard) exiled).getCard(); + Card topCard = meldCard.getTopHalfCard(); + Card bottomCard = meldCard.getBottomHalfCard(); + if (topCard.getZoneChangeCounter(game) == meldCard.getTopLastZoneChangeCounter()) { + cardsToReturn.add(topCard); + } + if (bottomCard.getZoneChangeCounter(game) == meldCard.getBottomLastZoneChangeCounter()) { + cardsToReturn.add(bottomCard); + } + } + else if (exiled.getZoneChangeCounter(game) == game.getState().getZoneChangeCounter(exiled.getId()) - 1) { cardsToReturn.add(exiled); } } diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/ChitteringHost.java b/Mage.Sets/src/mage/sets/eldritchmoon/ChitteringHost.java index deff83feda9..91568ac4327 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/ChitteringHost.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/ChitteringHost.java @@ -36,7 +36,7 @@ import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityAllEffect; import mage.abilities.keyword.HasteAbility; import mage.abilities.keyword.MenaceAbility; -import mage.cards.CardImpl; +import mage.cards.MeldCard; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Rarity; @@ -46,7 +46,7 @@ import mage.filter.common.FilterControlledCreaturePermanent; * * @author LevelX2 */ -public class ChitteringHost extends CardImpl { +public class ChitteringHost extends MeldCard { public ChitteringHost(UUID ownerId) { super(ownerId, 96, "Chittering Host", Rarity.COMMON, new CardType[]{CardType.CREATURE}, ""); @@ -57,10 +57,13 @@ public class ChitteringHost extends CardImpl { this.toughness = new MageInt(6); this.nightCard = true; // Meld card + // Haste this.addAbility(HasteAbility.getInstance()); + // Menace (This creature can't be blocked except by two or more creatures. this.addAbility(new MenaceAbility()); + // When Chittering Host enters the battlefield, other creatures you control get +1/+0 and gain menace until end of turn. Effect effect = new BoostControlledEffect(1, 0, Duration.EndOfTurn, true); effect.setText("other creatures you control get +1/+0"); @@ -68,7 +71,6 @@ public class ChitteringHost extends CardImpl { effect = new GainAbilityAllEffect(new MenaceAbility(), Duration.EndOfTurn, new FilterControlledCreaturePermanent("other creatures"), true); effect.setText("and gain menace until end of turn"); this.addAbility(ability); - } public ChitteringHost(final ChitteringHost card) { diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/GrafRats.java b/Mage.Sets/src/mage/sets/eldritchmoon/GrafRats.java index 2a3ebdd31eb..20036dcba9a 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/GrafRats.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/GrafRats.java @@ -29,16 +29,18 @@ package mage.sets.eldritchmoon; import java.util.UUID; import mage.MageInt; -import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.effects.common.InfoEffect; +import mage.abilities.common.BeginningOfCombatTriggeredAbility; +import mage.abilities.condition.common.MeldCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.MeldEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.constants.Zone; +import mage.constants.TargetController; /** * - * @author LevelX2 + * @author emerald000 */ public class GrafRats extends CardImpl { @@ -50,7 +52,10 @@ public class GrafRats extends CardImpl { this.toughness = new MageInt(1); // At the beginning of combat on your turn, if you both own and control Graf Rats and a creature named Midnight Scavengers, exile them, then meld them into Chittering Host. - this.addAbility(new SimpleStaticAbility(Zone.ALL, new InfoEffect("Meld ability not implemeted yet."))); + this.addAbility(new ConditionalTriggeredAbility( + new BeginningOfCombatTriggeredAbility(new MeldEffect("Midnight Scavengers", new ChitteringHost(ownerId)), TargetController.YOU, false), + new MeldCondition("Midnight Scavengers"), + "At the beginning of combat on your turn, if you both own and control {this} and a creature named Midnight Scavengers, exile them, then meld them into Chittering Host.")); } public GrafRats(final GrafRats card) { diff --git a/Mage.Sets/src/mage/sets/eldritchmoon/LongRoadHome.java b/Mage.Sets/src/mage/sets/eldritchmoon/LongRoadHome.java index 7e95f34d55a..03dfdf21e06 100644 --- a/Mage.Sets/src/mage/sets/eldritchmoon/LongRoadHome.java +++ b/Mage.Sets/src/mage/sets/eldritchmoon/LongRoadHome.java @@ -36,6 +36,7 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.ReplacementEffectImpl; import mage.cards.Card; import mage.cards.CardImpl; +import mage.cards.MeldCard; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; @@ -93,7 +94,7 @@ class LongRoadHomeEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Permanent permanent = game.getPermanent(source.getFirstTarget()); if (permanent != null) { - if (permanent.moveToExile(source.getSourceId(), "Otherworldly Journey", source.getSourceId(), game)) { + if (permanent.moveToExile(source.getSourceId(), "Long Road Home", source.getSourceId(), game)) { ExileZone exile = game.getExile().getExileZone(source.getSourceId()); // only if permanent is in exile (tokens would be stop to exist) if (exile != null && !exile.isEmpty()) { @@ -147,7 +148,14 @@ class LongRoadHomeReturnFromExileEffect extends OneShotEffect { if (card != null && objectToReturn.refersTo(card, game)) { Player owner = game.getPlayer(card.getOwnerId()); if (owner != null) { - game.addEffect(new LongRoadHomeEntersBattlefieldEffect(objectToReturn), source); + if (card instanceof MeldCard) { + MeldCard meldCard = (MeldCard) card; + game.addEffect(new LongRoadHomeEntersBattlefieldEffect(new MageObjectReference(meldCard.getTopHalfCard(), game)), source); + game.addEffect(new LongRoadHomeEntersBattlefieldEffect(new MageObjectReference(meldCard.getBottomHalfCard(), game)), source); + } + else { + game.addEffect(new LongRoadHomeEntersBattlefieldEffect(objectToReturn), source); + } owner.moveCards(card, Zone.BATTLEFIELD, source, game, false, false, true, null); } } diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EssenceFlux.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EssenceFlux.java index c74206dfb67..56f29a50c61 100644 --- a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EssenceFlux.java +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EssenceFlux.java @@ -30,17 +30,23 @@ package mage.sets.shadowsoverinnistrad; import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ExileTargetForSourceEffect; -import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.Card; import mage.cards.CardImpl; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.cards.MeldCard; import mage.constants.CardType; +import mage.constants.Outcome; import mage.constants.Rarity; +import mage.constants.Zone; import mage.counters.CounterType; import mage.game.ExileZone; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.players.Player; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; @@ -60,7 +66,7 @@ public class EssenceFlux extends CardImpl { Effect effect = new ExileTargetForSourceEffect(); effect.setApplyEffectsAfter(); this.getSpellAbility().addEffect(effect); - this.getSpellAbility().addEffect(new EssenceFluxEffect(false, true)); + this.getSpellAbility().addEffect(new EssenceFluxEffect()); } public EssenceFlux(final EssenceFlux card) { @@ -73,14 +79,14 @@ public class EssenceFlux extends CardImpl { } } -class EssenceFluxEffect extends ReturnToBattlefieldUnderOwnerControlTargetEffect { +class EssenceFluxEffect extends OneShotEffect { - public EssenceFluxEffect(boolean tapped, boolean fromExileZone) { - super(tapped, fromExileZone); - staticText = ", then return that card to the battlefield under its owner's control. If it's a Spirit, put a +1/+1 counter on it"; + EssenceFluxEffect() { + super(Outcome.Benefit); + staticText = "return that card to the battlefield under its owner's control"; } - public EssenceFluxEffect(final EssenceFluxEffect effect) { + EssenceFluxEffect(final EssenceFluxEffect effect) { super(effect); } @@ -91,22 +97,43 @@ class EssenceFluxEffect extends ReturnToBattlefieldUnderOwnerControlTargetEffect @Override public boolean apply(Game game, Ability source) { - Card card = null; - UUID exilZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - if (exilZoneId != null) { - ExileZone exileZone = game.getExile().getExileZone(exilZoneId); - if (exileZone != null && getTargetPointer().getFirst(game, source) != null) { - card = exileZone.get(getTargetPointer().getFirst(game, source), game); + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Cards cardsToBattlefield = new CardsImpl(); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + if (exileZoneId != null) { + ExileZone exileZone = game.getExile().getExileZone(exileZoneId); + if (exileZone != null) { + for (UUID targetId : this.getTargetPointer().getTargets(game, source)) { + if (exileZone.contains(targetId)) { + cardsToBattlefield.add(targetId); + } + else { + Card card = game.getCard(targetId); + if (card != null && card instanceof MeldCard) { + MeldCard meldCard = (MeldCard) card; + Card topCard = meldCard.getTopHalfCard(); + Card bottomCard = meldCard.getBottomHalfCard(); + if (topCard.getZoneChangeCounter(game) == meldCard.getTopLastZoneChangeCounter() && exileZone.contains(topCard.getId())) { + cardsToBattlefield.add(topCard); + } + if (bottomCard.getZoneChangeCounter(game) == meldCard.getBottomLastZoneChangeCounter() && exileZone.contains(bottomCard.getId())) { + cardsToBattlefield.add(bottomCard); + } + } + } + } + } } - } - if (super.apply(game, source)) { - if (card != null) { - - Permanent permanent = game.getPermanent(card.getId()); - if (permanent != null && permanent.getSubtype().contains("Spirit")) { - Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance()); - effect.setTargetPointer(new FixedTarget(permanent, game)); - return effect.apply(game, source); + if (!cardsToBattlefield.isEmpty()) { + controller.moveCards(cardsToBattlefield.getCards(game), Zone.BATTLEFIELD, source, game, false, false, true, null); + for (UUID cardId : cardsToBattlefield) { + Permanent permanent = game.getPermanent(cardId); + if (permanent != null && permanent.getSubtype().contains("Spirit")) { + Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance()); + effect.setTargetPointer(new FixedTarget(permanent, game)); + return effect.apply(game, source); + } } } return true; diff --git a/Mage/src/main/java/mage/abilities/condition/common/MeldCondition.java b/Mage/src/main/java/mage/abilities/condition/common/MeldCondition.java new file mode 100644 index 00000000000..c1ccc889b57 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/condition/common/MeldCondition.java @@ -0,0 +1,67 @@ +/* + * 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.MageObject; +import mage.abilities.Ability; +import mage.abilities.condition.Condition; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author emerald000 + */ + +public class MeldCondition implements Condition { + + private final String meldWithName; + + public MeldCondition(String meldWithName) { + this.meldWithName = meldWithName; + } + + @Override + public boolean apply(Game game, Ability source) { + MageObject sourceMageObject = source.getSourceObjectIfItStillExists(game); + if (sourceMageObject != null && sourceMageObject instanceof Permanent) { + Permanent sourcePermanent = (Permanent) sourceMageObject; + if (sourcePermanent.getControllerId().equals(source.getControllerId()) + && sourcePermanent.getOwnerId().equals(source.getControllerId())) { + FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(this.meldWithName); + for (Permanent meldWithPermanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { + if (meldWithPermanent.getOwnerId().equals(source.getControllerId())) { + return true; + } + } + } + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java b/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java new file mode 100644 index 00000000000..6f57fcaaa77 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/MeldEffect.java @@ -0,0 +1,111 @@ +/* + * 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.effects.common; + +import java.util.Set; +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; +import mage.cards.MeldCard; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author emerald000 + */ +public class MeldEffect extends OneShotEffect { + + private final String meldWithName; + private final MeldCard meldCard; + + public MeldEffect(String meldWithName, MeldCard meldCard) { + super(Outcome.Benefit); + this.meldWithName = meldWithName; + this.meldCard = meldCard; + } + + public MeldEffect(final MeldEffect effect) { + super(effect); + this.meldWithName = effect.meldWithName; + this.meldCard = effect.meldCard; + } + + @Override + public MeldEffect copy() { + return new MeldEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + // Find the two permanents to meld. + UUID sourceId = source.getSourceId(); + FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("creature named " + meldWithName); + filter.add(new NamePredicate(meldWithName)); + TargetPermanent target = new TargetControlledCreaturePermanent(filter); + Set meldWithList = target.possibleTargets(sourceId, source.getControllerId(), game); + UUID meldWithId; + if (meldWithList.size() == 1) { + meldWithId = meldWithList.iterator().next(); + } + else { + controller.choose(Outcome.BoostCreature, target, sourceId, game); + meldWithId = target.getFirstTarget(); + } + // Exile the two permanents to meld. + Permanent sourcePermanent = game.getPermanent(sourceId); + Permanent meldWithPermanent = game.getPermanent(meldWithId); + sourcePermanent.moveToExile(null, "", sourceId, game); + meldWithPermanent.moveToExile(null, "", sourceId, game); + // Create the meld card and move it to the battlefield. + Card sourceCard = game.getExile().getCard(sourceId, game); + Card meldWithCard = game.getExile().getCard(meldWithId, game); + if (!sourceCard.isCopy() && !meldWithCard.isCopy()) { + meldCard.setOwnerId(controller.getId()); + meldCard.setTopHalfCard(meldWithCard, game); + meldCard.setbottomHalfCard(sourceCard, game); + meldCard.setMelded(true); + game.addMeldCard(meldCard.getId(), meldCard); + game.getState().addCard(meldCard); + meldCard.moveToZone(Zone.BATTLEFIELD, sourceId, game, false); + } + return true; + } + return false; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java index d18f89f0422..1cf0c5b7d13 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderOwnerControlTargetEffect.java @@ -33,6 +33,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; +import mage.cards.MeldCard; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.ExileZone; @@ -79,33 +80,40 @@ public class ReturnToBattlefieldUnderOwnerControlTargetEffect extends OneShotEff public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Cards cardsToMove = new CardsImpl(); + Cards cardsToBattlefield = new CardsImpl(); if (fromExileZone) { - UUID exilZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - if (exilZoneId != null) { - ExileZone exileZone = game.getExile().getExileZone(exilZoneId); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + if (exileZoneId != null) { + ExileZone exileZone = game.getExile().getExileZone(exileZoneId); if (exileZone != null) { - for (UUID cardId : getTargetPointer().getTargets(game, source)) { - Card card = exileZone.get(cardId, game); - if (card != null) { - cardsToMove.add(card); + for (UUID targetId : this.getTargetPointer().getTargets(game, source)) { + if (exileZone.contains(targetId)) { + cardsToBattlefield.add(targetId); + } + else { + Card card = game.getCard(targetId); + if (card != null && card instanceof MeldCard) { + MeldCard meldCard = (MeldCard) card; + Card topCard = meldCard.getTopHalfCard(); + Card bottomCard = meldCard.getBottomHalfCard(); + if (topCard.getZoneChangeCounter(game) == meldCard.getTopLastZoneChangeCounter() && exileZone.contains(topCard.getId())) { + cardsToBattlefield.add(topCard); + } + if (bottomCard.getZoneChangeCounter(game) == meldCard.getBottomLastZoneChangeCounter() && exileZone.contains(bottomCard.getId())) { + cardsToBattlefield.add(bottomCard); + } + } } } } } } else { - for (UUID cardId : getTargetPointer().getTargets(game, source)) { - Card card = game.getCard(cardId); - if (card != null) { - cardsToMove.add(card); - } - } + cardsToBattlefield.addAll(getTargetPointer().getTargets(game, source)); } - if (!cardsToMove.isEmpty()) { - controller.moveCards(cardsToMove.getCards(game), - Zone.BATTLEFIELD, source, game, tapped, false, true, null); - return true; + if (!cardsToBattlefield.isEmpty()) { + controller.moveCards(cardsToBattlefield.getCards(game), Zone.BATTLEFIELD, source, game, tapped, false, true, null); } + return true; } return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderYourControlTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderYourControlTargetEffect.java index d7b3511965b..1245361caf7 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderYourControlTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToBattlefieldUnderYourControlTargetEffect.java @@ -33,6 +33,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; +import mage.cards.MeldCard; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.ExileZone; @@ -79,13 +80,27 @@ public class ReturnToBattlefieldUnderYourControlTargetEffect extends OneShotEffe if (controller != null) { Cards cardsToBattlefield = new CardsImpl(); if (fromExileZone) { - UUID exilZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); - if (exilZoneId != null) { - ExileZone exileZone = game.getExile().getExileZone(exilZoneId); + UUID exileZoneId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + if (exileZoneId != null) { + ExileZone exileZone = game.getExile().getExileZone(exileZoneId); if (exileZone != null) { - for (Card card : exileZone.getCards(game)) { - if (getTargetPointer().getTargets(game, source).contains(card.getId())) { - cardsToBattlefield.add(card); + for (UUID targetId : this.getTargetPointer().getTargets(game, source)) { + if (exileZone.contains(targetId)) { + cardsToBattlefield.add(targetId); + } + else { + Card card = game.getCard(targetId); + if (card != null && card instanceof MeldCard) { + MeldCard meldCard = (MeldCard) card; + Card topCard = meldCard.getTopHalfCard(); + Card bottomCard = meldCard.getBottomHalfCard(); + if (topCard.getZoneChangeCounter(game) == meldCard.getTopLastZoneChangeCounter() && exileZone.contains(topCard.getId())) { + cardsToBattlefield.add(topCard); + } + if (bottomCard.getZoneChangeCounter(game) == meldCard.getBottomLastZoneChangeCounter() && exileZone.contains(bottomCard.getId())) { + cardsToBattlefield.add(bottomCard); + } + } } } } diff --git a/Mage/src/main/java/mage/cards/MeldCard.java b/Mage/src/main/java/mage/cards/MeldCard.java new file mode 100644 index 00000000000..574e3a282d3 --- /dev/null +++ b/Mage/src/main/java/mage/cards/MeldCard.java @@ -0,0 +1,373 @@ +/* + * 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.cards; + +import java.util.ArrayList; +import java.util.UUID; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.Zone; +import static mage.constants.Zone.EXILED; +import static mage.constants.Zone.GRAVEYARD; +import static mage.constants.Zone.HAND; +import static mage.constants.Zone.LIBRARY; +import mage.counters.Counter; +import mage.game.Game; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.PermanentMeld; +import mage.players.Player; + +/** + * + * @author emerald000 + */ +public abstract class MeldCard extends CardImpl { + + protected Card topHalfCard; + protected Card bottomHalfCard; + protected int topLastZoneChangeCounter; + protected int bottomLastZoneChangeCounter; + protected boolean isMelded; + + public MeldCard(UUID ownerId, int cardNumber, String name, Rarity rarity, CardType[] cardTypes, String costs) { + super(ownerId, cardNumber, name, rarity, cardTypes, costs); + } + + public MeldCard(MeldCard card) { + super(card); + this.topHalfCard = card.topHalfCard; + this.bottomHalfCard = card.bottomHalfCard; + this.topLastZoneChangeCounter = card.topLastZoneChangeCounter; + this.bottomLastZoneChangeCounter = card.bottomLastZoneChangeCounter; + this.isMelded = card.isMelded; + } + + public void setMelded(boolean isMelded) { + this.isMelded = isMelded; + } + + public boolean isMelded() { + return isMelded; + } + + public Card getTopHalfCard() { + return topHalfCard; + } + + public void setTopHalfCard(Card topHalfCard, Game game) { + this.topHalfCard = topHalfCard; + this.topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); + } + + public int getTopLastZoneChangeCounter() { + return topLastZoneChangeCounter; + } + + public void setTopLastZoneChangeCounter(int topLastZoneChangeCounter) { + this.topLastZoneChangeCounter = topLastZoneChangeCounter; + } + + public Card getBottomHalfCard() { + return bottomHalfCard; + } + + public void setbottomHalfCard(Card bottomHalfCard, Game game) { + this.bottomHalfCard = bottomHalfCard; + this.bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); + } + + public int getBottomLastZoneChangeCounter() { + return bottomLastZoneChangeCounter; + } + + public void setBottomLastZoneChangeCounter(int bottomLastZoneChangeCounter) { + this.bottomLastZoneChangeCounter = bottomLastZoneChangeCounter; + } + + @Override + public void assignNewId() { + super.assignNewId(); + topHalfCard.assignNewId(); + bottomHalfCard.assignNewId(); + } + + @Override + public void setCopy(boolean isCopy) { + super.setCopy(isCopy); + topHalfCard.setCopy(isCopy); + bottomHalfCard.setCopy(isCopy); + } + + @Override + public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { + if (this.isMelded()) { + // Initial move to battlefield + if (toZone == Zone.BATTLEFIELD) { + return this.putOntoBattlefield(game, Zone.EXILED, sourceId, this.getOwnerId(), false, false, appliedEffects); + } + // Move when melded from the battlefield to elsewhere + else { + ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.BATTLEFIELD, toZone, appliedEffects); + if (!game.replaceEvent(event)) { + updateZoneChangeCounter(game); + switch (event.getToZone()) { + case GRAVEYARD: + game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game, true); + game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game, true); + break; + case HAND: + game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); + game.getPlayer(this.getOwnerId()).getHand().add(bottomHalfCard); + break; + case EXILED: + game.getExile().getPermanentExile().add(topHalfCard); + game.getExile().getPermanentExile().add(bottomHalfCard); + break; + case LIBRARY: + Player controller = game.getPlayer(this.getOwnerId()); + if (controller != null) { + CardsImpl cardsToMove = new CardsImpl(); + cardsToMove.add(topHalfCard); + cardsToMove.add(bottomHalfCard); + if (flag) { + controller.putCardsOnTopOfLibrary(cardsToMove, game, null, true); + } + else { + controller.putCardsOnBottomOfLibrary(cardsToMove, game, null, true); + } + } + break; + default: + return false; + } + this.setMelded(false); + game.setZone(topHalfCard.getId(), event.getToZone()); + game.setZone(bottomHalfCard.getId(), event.getToZone()); + this.topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); + this.bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); + game.addSimultaneousEvent(event); + return true; + } + else { + return false; + } + } + } + else { + // Try to move the former meld cards after it has already left the battlefield. + // If the meld parts didn't move from that zone, move them instead of the meld card. + // Reset the local zcc so the meld card lose track of them. + boolean returnValue = false; + if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { + topHalfCard.moveToZone(toZone, sourceId, game, flag, appliedEffects); + topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); + returnValue = true; + } + if (bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game)) { + bottomHalfCard.moveToZone(toZone, sourceId, game, flag, appliedEffects); + bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); + returnValue = true; + } + return returnValue; + } + } + + @Override + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { + if (this.isMelded()) { + // Move when melded from the battlefield to exile + ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.BATTLEFIELD, Zone.EXILED, appliedEffects); + if (!game.replaceEvent(event)) { + updateZoneChangeCounter(game); + switch (event.getToZone()) { + case GRAVEYARD: + game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game, true); + game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game, true); + break; + case HAND: + game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); + game.getPlayer(this.getOwnerId()).getHand().add(bottomHalfCard); + break; + case EXILED: + if (exileId == null) { + game.getExile().getPermanentExile().add(topHalfCard); + game.getExile().getPermanentExile().add(bottomHalfCard); + } + else { + game.getExile().createZone(exileId, name).add(topHalfCard); + game.getExile().getExileZone(exileId).add(bottomHalfCard); + } + break; + case LIBRARY: + Player controller = game.getPlayer(this.getOwnerId()); + if (controller != null) { + CardsImpl cardsToMove = new CardsImpl(); + cardsToMove.add(topHalfCard); + cardsToMove.add(bottomHalfCard); + if (event.getFlag()) { + controller.putCardsOnTopOfLibrary(cardsToMove, game, null, true); + } + else { + controller.putCardsOnBottomOfLibrary(cardsToMove, game, null, true); + } + } + break; + default: + return false; + } + this.setMelded(false); + game.setZone(topHalfCard.getId(), event.getToZone()); + game.setZone(bottomHalfCard.getId(), event.getToZone()); + this.topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); + this.bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); + game.addSimultaneousEvent(event); + return true; + } + else { + return false; + } + } + else { + // Try to move the former meld cards after it has already left the battlefield. + // If the meld parts didn't move from that zone, move them instead of the meld card. + // Reset the local zcc so the meld card lose track of them. + boolean returnValue = false; + if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { + topHalfCard.moveToExile(exileId, name, sourceId, game, appliedEffects); + topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); + returnValue = true; + } + if (bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game)) { + bottomHalfCard.moveToExile(exileId, name, sourceId, game, appliedEffects); + bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); + returnValue = true; + } + return returnValue; + } + } + + @Override + public boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId, boolean tapped, boolean facedown, ArrayList appliedEffects) { + // Initial move to battlefield + if (this.isMelded()) { + ZoneChangeEvent event = new ZoneChangeEvent(this.objectId, sourceId, controllerId, Zone.EXILED, Zone.BATTLEFIELD, appliedEffects); + if (!game.replaceEvent(event) && event.getToZone() == Zone.BATTLEFIELD) { + updateZoneChangeCounter(game); + PermanentMeld permanent = new PermanentMeld(this, event.getPlayerId(), game); // controller can be replaced (e.g. Gather Specimens) + game.addPermanent(permanent); + game.setZone(objectId, Zone.BATTLEFIELD); + game.setScopeRelevant(true); + game.applyEffects(); + boolean entered = permanent.entersBattlefield(sourceId, game, event.getFromZone(), true); + game.setScopeRelevant(false); + game.applyEffects(); + if (entered) { + if (event.getFlag()) { + permanent.setTapped(true); + } + event.setTarget(permanent); + } + else { + return false; + } + game.setZone(objectId, event.getToZone()); + game.addSimultaneousEvent(event); + game.getExile().removeCard(this.topHalfCard, game); + game.getExile().removeCard(this.bottomHalfCard, game); + return true; + } + else { + this.setMelded(false); + return false; + } + } + else { + // Try to move the former meld cards after it has already left the battlefield. + // If the meld parts didn't move from that zone, move them instead of the meld card. + // Reset the local zcc so the meld card lose track of them. + boolean returnValue = false; + if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { + topHalfCard.moveToZone(Zone.BATTLEFIELD, sourceId, game, tapped, appliedEffects); + topLastZoneChangeCounter = topHalfCard.getZoneChangeCounter(game); + returnValue = true; + } + if (bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game)) { + bottomHalfCard.moveToZone(Zone.BATTLEFIELD, sourceId, game, tapped, appliedEffects); + bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); + returnValue = true; + } + return returnValue; + } + } + + @Override + public void setOwnerId(UUID ownerId) { + super.setOwnerId(ownerId); + abilities.setControllerId(ownerId); + } + + @Override + public int getConvertedManaCost() { + if (this.isCopy()) { + return 0; + } + else { + return this.topHalfCard.getConvertedManaCost() + this.bottomHalfCard.getConvertedManaCost(); + } + } + + @Override + public void addCounters(Counter counter, Game game, ArrayList appliedEffects) { + if (this.isMelded()) { + super.addCounters(counter, game, appliedEffects); + } + else { + if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { + topHalfCard.addCounters(counter, game, appliedEffects); + } + if (bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game)) { + bottomHalfCard.addCounters(counter, game, appliedEffects); + } + } + } + + @Override + public void addCounters(String name, int amount, Game game, ArrayList appliedEffects) { + if (this.isMelded()) { + super.addCounters(name, amount, game, appliedEffects); + } + else { + if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { + topHalfCard.addCounters(name, amount, game, appliedEffects); + } + if (bottomLastZoneChangeCounter == bottomHalfCard.getZoneChangeCounter(game)) { + bottomHalfCard.addCounters(name, amount, game, appliedEffects); + } + } + } +} diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 700865fd279..28df98b888c 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -47,6 +47,7 @@ import mage.abilities.effects.PreventionEffectData; import mage.actions.impl.MageAction; import mage.cards.Card; import mage.cards.Cards; +import mage.cards.MeldCard; import mage.cards.decks.Deck; import mage.choices.Choice; import mage.constants.Duration; @@ -93,6 +94,10 @@ public interface Game extends MageItem, Serializable { Collection getCards(); + MeldCard getMeldCard(UUID meldId); + + void addMeldCard(UUID meldId, MeldCard meldCard); + Object getCustomData(); void setCustomData(Object data); @@ -348,7 +353,7 @@ public interface Game extends MageItem, Serializable { void setManaPaymentMode(UUID playerId, boolean autoPayment); void setManaPaymentModeRestricted(UUID playerId, boolean autoPaymentRestricted); - + void setUseFirstManaAbility(UUID playerId, boolean useFirstManaAbility); void undo(UUID playerId); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 2d6de38f9e4..6f0dcd552d8 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -67,6 +67,7 @@ import mage.actions.impl.MageAction; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; +import mage.cards.MeldCard; import mage.cards.SplitCard; import mage.cards.SplitCardHalf; import mage.cards.decks.Deck; @@ -169,6 +170,7 @@ public abstract class GameImpl implements Game, Serializable { protected transient PlayerQueryEventSource playerQueryEventSource = new PlayerQueryEventSource(); protected Map gameCards = new HashMap<>(); + protected Map meldCards = new HashMap<>(0); protected Map> lki = new EnumMap<>(Zone.class); protected Map> lkiExtended = new HashMap<>(); @@ -320,6 +322,16 @@ public abstract class GameImpl implements Game, Serializable { return gameCards.values(); } + @Override + public void addMeldCard(UUID meldId, MeldCard meldCard) { + meldCards.put(meldId, meldCard); + } + + @Override + public MeldCard getMeldCard(UUID meldId) { + return meldCards.get(meldId); + } + @Override public void addPlayer(Player player, Deck deck) throws GameException { player.useDeck(deck, this); @@ -521,7 +533,10 @@ public abstract class GameImpl implements Game, Serializable { } Card card = gameCards.get(cardId); if (card == null) { - return state.getCopiedCard(cardId); + card = state.getCopiedCard(cardId); + } + if (card == null) { + card = this.getMeldCard(cardId); } return card; } @@ -706,6 +721,7 @@ public abstract class GameImpl implements Game, Serializable { @Override public void cleanUp() { gameCards.clear(); + meldCards.clear(); } @Override diff --git a/Mage/src/main/java/mage/game/permanent/PermanentMeld.java b/Mage/src/main/java/mage/game/permanent/PermanentMeld.java new file mode 100644 index 00000000000..e933cfe62cf --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/PermanentMeld.java @@ -0,0 +1,177 @@ +/* + * 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.game.permanent; + +import java.util.ArrayList; +import java.util.UUID; +import mage.cards.Card; +import mage.cards.CardsImpl; +import mage.cards.MeldCard; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.ZoneChangeEvent; +import mage.players.Player; + +/** + * + * @author emerald000 + */ +public class PermanentMeld extends PermanentCard { + + public PermanentMeld(Card card, UUID controllerId, Game game) { + super(card, controllerId, game); + } + + @Override + public int getConvertedManaCost() { + if (this.isCopy()) { + return 0; + } + else { + return this.getCard().getConvertedManaCost(); + } + } + + @Override + public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, ArrayList appliedEffects) { + ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.BATTLEFIELD, toZone, appliedEffects); + if (!game.replaceEvent(event)) { + Player controller = game.getPlayer(this.getControllerId()); + if (controller != null) { + controller.removeFromBattlefield(this, game); + updateZoneChangeCounter(game); + MeldCard meldCard = (MeldCard) this.getCard(); + Card topHalfCard = meldCard.getTopHalfCard(); + Card bottomHalfCard = meldCard.getBottomHalfCard(); + switch (event.getToZone()) { + case GRAVEYARD: + game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game, true); + game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game, true); + break; + case HAND: + game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); + game.getPlayer(this.getOwnerId()).getHand().add(bottomHalfCard); + break; + case EXILED: + game.getExile().getPermanentExile().add(topHalfCard); + game.getExile().getPermanentExile().add(bottomHalfCard); + break; + case LIBRARY: + CardsImpl cardsToMove = new CardsImpl(); + cardsToMove.add(topHalfCard); + cardsToMove.add(bottomHalfCard); + if (flag) { + controller.putCardsOnTopOfLibrary(cardsToMove, game, null, true); + } + else { + controller.putCardsOnBottomOfLibrary(cardsToMove, game, null, true); + } + break; + default: + return false; + } + meldCard.setMelded(false); + game.setZone(topHalfCard.getId(), event.getToZone()); + game.setZone(bottomHalfCard.getId(), event.getToZone()); + meldCard.setTopLastZoneChangeCounter(topHalfCard.getZoneChangeCounter(game)); + meldCard.setBottomLastZoneChangeCounter(bottomHalfCard.getZoneChangeCounter(game)); + game.addSimultaneousEvent(event); + return true; + } + } + return false; + } + + @Override + public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, ArrayList appliedEffects) { + ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.BATTLEFIELD, Zone.EXILED, appliedEffects); + if (!game.replaceEvent(event)) { + Player controller = game.getPlayer(this.getControllerId()); + if (controller != null) { + controller.removeFromBattlefield(this, game); + updateZoneChangeCounter(game); + MeldCard meldCard = (MeldCard) this.getCard(); + Card topHalfCard = meldCard.getTopHalfCard(); + Card bottomHalfCard = meldCard.getBottomHalfCard(); + switch (event.getToZone()) { + case GRAVEYARD: + game.getPlayer(this.getOwnerId()).putInGraveyard(topHalfCard, game, true); + game.getPlayer(this.getOwnerId()).putInGraveyard(bottomHalfCard, game, true); + break; + case HAND: + game.getPlayer(this.getOwnerId()).getHand().add(topHalfCard); + game.getPlayer(this.getOwnerId()).getHand().add(bottomHalfCard); + break; + case EXILED: + if (exileId == null) { + game.getExile().getPermanentExile().add(topHalfCard); + game.getExile().getPermanentExile().add(bottomHalfCard); + } + else { + game.getExile().createZone(exileId, name).add(topHalfCard); + game.getExile().getExileZone(exileId).add(bottomHalfCard); + } + break; + case LIBRARY: + CardsImpl cardsToMove = new CardsImpl(); + cardsToMove.add(topHalfCard); + cardsToMove.add(bottomHalfCard); + if (event.getFlag()) { + controller.putCardsOnTopOfLibrary(cardsToMove, game, null, true); + } + else { + controller.putCardsOnBottomOfLibrary(cardsToMove, game, null, true); + } + break; + default: + return false; + } + meldCard.setMelded(false); + game.setZone(topHalfCard.getId(), event.getToZone()); + game.setZone(bottomHalfCard.getId(), event.getToZone()); + meldCard.setTopLastZoneChangeCounter(topHalfCard.getZoneChangeCounter(game)); + meldCard.setBottomLastZoneChangeCounter(bottomHalfCard.getZoneChangeCounter(game)); + game.addSimultaneousEvent(event); + return true; + } + } + return false; + } + + @Override + public void addCounters(String name, int amount, Game game, ArrayList appliedEffects) { + MeldCard meldCard = (MeldCard) this.getCard(); + if (meldCard.isMelded()) { + super.addCounters(name, amount, game, appliedEffects); + } + else { + meldCard.getTopHalfCard().addCounters(name, amount, game, appliedEffects); + meldCard.getBottomHalfCard().addCounters(name, amount, game, appliedEffects); + } + } +} diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index eaee1467ca0..eaf096d010d 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -83,6 +83,7 @@ import mage.actions.MageDrawAction; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; +import mage.cards.MeldCard; import mage.cards.SplitCard; import mage.cards.decks.Deck; import mage.constants.AbilityType; @@ -120,6 +121,7 @@ import mage.game.events.ZoneChangeEvent; import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; +import mage.game.permanent.PermanentMeld; import mage.game.stack.Spell; import mage.game.stack.StackAbility; import mage.game.stack.StackObject; @@ -177,9 +179,7 @@ public abstract class PlayerImpl implements Player, Serializable { protected Date dateLastAddedToStack; // F10 protected boolean skippedAtLeastOnce; // used to track if passed started in specific phase /** - * This indicates that player passed all turns until his own turn starts - * (F9). Note! This differs from passedTurn as it doesn't care about spells - * and abilities in the stack and will pass them as well. + * This indicates that player passed all turns until his own turn starts (F9). Note! This differs from passedTurn as it doesn't care about spells and abilities in the stack and will pass them as well. */ protected boolean passedAllTurns; // F9 protected AbilityType justActivatedType; // used to check if priority can be passed automatically @@ -524,13 +524,15 @@ public abstract class PlayerImpl implements Player, Serializable { inRange.add(player.getId()); } } - } else if ((range.getRange() * 2) + 1 >= game.getPlayers().size()) { + } + else if ((range.getRange() * 2) + 1 >= game.getPlayers().size()) { for (Player player : game.getPlayers().values()) { if (!player.hasLeft()) { inRange.add(player.getId()); } } - } else { + } + else { inRange.add(playerId); PlayerList players = game.getState().getPlayerList(playerId); for (int i = 0; i < range.getRange(); i++) { @@ -593,8 +595,7 @@ public abstract class PlayerImpl implements Player, Serializable { } /** - * returns true if the player has the control itself - false if the player - * is controlled by another player + * returns true if the player has the control itself - false if the player is controlled by another player * * @return */ @@ -678,7 +679,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (card.getOwnerId().equals(playerId)) { card.setZone(Zone.HAND, game); this.hand.add(card); - } else { + } + else { return game.getPlayer(card.getOwnerId()).putInHand(card, game); } return true; @@ -736,7 +738,8 @@ public abstract class PlayerImpl implements Player, Serializable { discard(card, source, game); } } - } else { + } + else { int possibleAmount = Math.min(getHand().size(), amount); TargetDiscard target = new TargetDiscard(possibleAmount, possibleAmount, new FilterCard(CardUtil.numberToText(possibleAmount, "a") + " card" + (possibleAmount > 1 ? "s" : "")), playerId); choose(Outcome.Discard, target, source == null ? null : source.getSourceId(), game); @@ -830,7 +833,8 @@ public abstract class PlayerImpl implements Player, Serializable { Permanent attachedTo = game.getPermanent(permanent.getAttachedTo()); if (attachedTo != null) { attachedTo.removeAttachment(permanent.getId(), game); - } else { + } + else { Player attachedToPlayer = game.getPlayer(permanent.getAttachedTo()); if (attachedToPlayer != null) { attachedToPlayer.removeAttachment(permanent, game); @@ -851,7 +855,8 @@ public abstract class PlayerImpl implements Player, Serializable { public boolean putInGraveyard(Card card, Game game, boolean fromBattlefield) { if (card.getOwnerId().equals(playerId)) { this.graveyard.add(card); - } else { + } + else { return game.getPlayer(card.getOwnerId()).putInGraveyard(card, game, fromBattlefield); } return true; @@ -870,7 +875,8 @@ public abstract class PlayerImpl implements Player, Serializable { for (UUID objectId : cards) { moveObjectToLibrary(objectId, source == null ? null : source.getSourceId(), game, false, false); } - } else { + } + else { TargetCard target = new TargetCard(Zone.ALL, new FilterCard("card to put on the bottom of your library (last one chosen will be bottommost)")); target.setRequired(true); while (isInGame() && cards.size() > 1) { @@ -906,7 +912,8 @@ public abstract class PlayerImpl implements Player, Serializable { for (UUID cardId : cards) { moveObjectToLibrary(cardId, sourceId, game, true, false); } - } else { + } + else { TargetCard target = new TargetCard(Zone.LIBRARY, new FilterCard("card to put on the top of your library (last one chosen will be topmost)")); target.setRequired(true); while (isInGame() && cards.size() > 1) { @@ -930,7 +937,8 @@ public abstract class PlayerImpl implements Player, Serializable { Zone fromZone = game.getState().getZone(objectId); if ((mageObject instanceof Permanent)) { return this.moveCardToLibraryWithInfo((Permanent) mageObject, sourceId, game, fromZone, toTop, withName); - } else if (mageObject instanceof Card) { + } + else if (mageObject instanceof Card) { return this.moveCardToLibraryWithInfo((Card) mageObject, sourceId, game, fromZone, toTop, withName); } } @@ -972,7 +980,8 @@ public abstract class PlayerImpl implements Player, Serializable { boolean result; if (card.getCardType().contains(CardType.LAND)) { result = playLand(card, game, ignoreTiming); - } else { + } + else { result = cast(card.getSpellAbility(), game, noMana); } if (result == false) { @@ -1004,7 +1013,8 @@ public abstract class PlayerImpl implements Player, Serializable { Costs costs = getCastSourceIdCosts(); if (alternateCosts == null) { noMana = true; - } else { + } + else { spellAbility.getManaCosts().clear(); spellAbility.getManaCostsToPay().clear(); spellAbility.getManaCosts().add(alternateCosts.copy()); @@ -1098,7 +1108,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (storedBookmark == -1 || storedBookmark > bookmark) { // e.g. usefull for undo Nykthos, Shrine to Nyx setStoredBookmark(bookmark); } - } else { + } + else { resetStoredBookmark(game); } return true; @@ -1127,7 +1138,8 @@ public abstract class PlayerImpl implements Player, Serializable { } restoreState(bookmark, ability.getRule(), game); } - } else { + } + else { int bookmark = game.bookmarkState(); if (ability.activate(game, false)) { ability.resolve(game); @@ -1177,7 +1189,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (ability instanceof PlayLandAbility) { Card card = game.getCard(ability.getSourceId()); result = playLand(card, game, false); - } else { + } + else { if (!ability.canActivate(this.playerId, game)) { return false; } @@ -1192,7 +1205,8 @@ public abstract class PlayerImpl implements Player, Serializable { case SPELL: if (ability instanceof FlashbackAbility) { result = playAbility(ability.copy(), game); - } else { + } + else { result = cast((SpellAbility) ability, game, false); } break; @@ -1238,7 +1252,8 @@ public abstract class PlayerImpl implements Player, Serializable { } if (!ability.isUsesStack()) { ability.resolve(game); - } else { + } + else { game.fireEvent(new GameEvent(EventType.TRIGGERED_ABILITY, ability.getId(), ability.getSourceId(), ability.getControllerId())); } game.removeBookmark(bookmark); @@ -1259,7 +1274,8 @@ public abstract class PlayerImpl implements Player, Serializable { useable.clear(); useable.put(ability.getId(), (SpellAbility) ability); return useable; - } else { + } + else { // Fuse only allowed from hand continue; } @@ -1288,10 +1304,12 @@ public abstract class PlayerImpl implements Player, Serializable { if (((ActivatedAbility) ability).canActivate(playerId, game)) { useable.put(ability.getId(), (ActivatedAbility) ability); } - } else if (canPlay(((ActivatedAbility) ability), availableMana, object, game)) { + } + else if (canPlay(((ActivatedAbility) ability), availableMana, object, game)) { useable.put(ability.getId(), (ActivatedAbility) ability); } - } else if (ability instanceof AlternativeSourceCosts) { + } + else if (ability instanceof AlternativeSourceCosts) { if (object.getCardType().contains(CardType.LAND)) { for (Ability ability2 : object.getAbilities().copy()) { if (ability2 instanceof PlayLandAbility) { @@ -1349,7 +1367,8 @@ public abstract class PlayerImpl implements Player, Serializable { // Left Half if (card.getCardType().contains(CardType.INSTANT)) { flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(), TimingRule.INSTANT); - } else { + } + else { flashbackAbility = new FlashbackAbility(((SplitCard) card).getLeftHalfCard().getManaCost(), TimingRule.SORCERY); } flashbackAbility.setSourceId(card.getId()); @@ -1362,7 +1381,8 @@ public abstract class PlayerImpl implements Player, Serializable { // Right Half if (card.getCardType().contains(CardType.INSTANT)) { flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(), TimingRule.INSTANT); - } else { + } + else { flashbackAbility = new FlashbackAbility(((SplitCard) card).getRightHalfCard().getManaCost(), TimingRule.SORCERY); } flashbackAbility.setSourceId(card.getId()); @@ -1373,7 +1393,8 @@ public abstract class PlayerImpl implements Player, Serializable { useable.put(flashbackAbility.getId(), flashbackAbility); } - } else { + } + else { useable.put(ability.getId(), ability); } } @@ -1429,7 +1450,8 @@ public abstract class PlayerImpl implements Player, Serializable { public void revealCards(String name, Cards cards, Game game, boolean postToLog) { if (postToLog) { game.getState().getRevealed().add(name, cards); - } else { + } + else { game.getState().getRevealed().update(name, cards); } if (postToLog && !game.isSimulation()) { @@ -1561,7 +1583,8 @@ public abstract class PlayerImpl implements Player, Serializable { } } - } else { + } + else { // player selected an permanent that is restricted by another effect, disallow it (so AI can select another one) filter.add(Predicates.not(new PermanentIdPredicate(selectedPermanent.getId()))); if (this.isHuman() && !game.isSimulation()) { @@ -1599,7 +1622,8 @@ public abstract class PlayerImpl implements Player, Serializable { } - } else { + } + else { //20091005 - 502.2 for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) { boolean untap = true; @@ -1694,7 +1718,8 @@ public abstract class PlayerImpl implements Player, Serializable { // rule 118.5 if (life > this.life) { gainLife(life - this.life, game); - } else if (life < this.life) { + } + else if (life < this.life) { loseLife(this.life - life, game); } } @@ -1794,26 +1819,31 @@ public abstract class PlayerImpl implements Player, Serializable { StackObject stackObject = game.getStack().getStackObject(sourceId); if (stackObject != null) { source = stackObject.getStackAbility().getSourceObject(game); - } else { + } + else { source = game.getObject(sourceId); } if (source instanceof Spell) { sourceAbilities = ((Spell) source).getAbilities(game); sourceControllerId = ((Spell) source).getControllerId(); - } else if (source instanceof Card) { + } + else if (source instanceof Card) { sourceAbilities = ((Card) source).getAbilities(game); sourceControllerId = ((Card) source).getOwnerId(); - } else if (source instanceof CommandObject) { + } + else if (source instanceof CommandObject) { sourceControllerId = ((CommandObject) source).getControllerId(); sourceAbilities = ((CommandObject) source).getAbilities(); } - } else { + } + else { sourceAbilities = ((Permanent) source).getAbilities(game); sourceControllerId = ((Permanent) source).getControllerId(); } if (sourceAbilities != null && sourceAbilities.containsKey(InfectAbility.getInstance().getId())) { addCounters(CounterType.POISON.createInstance(actualDamage), game); - } else { + } + else { GameEvent damageToLifeLossEvent = new GameEvent(EventType.DAMAGE_CAUSES_LIFE_LOSS, playerId, sourceId, playerId, actualDamage, combatDamage); if (!game.replaceEvent(damageToLifeLossEvent)) { this.loseLife(damageToLifeLossEvent.getAmount(), game); @@ -2130,7 +2160,8 @@ public abstract class PlayerImpl implements Player, Serializable { this.loses = true; game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LOST, null, null, playerId)); game.informPlayers(this.getLogName() + " has lost the game."); - } else { + } + else { logger.debug(this.getName() + " has already won - stop lost"); } // for draw - first all players that have lost have to be set to lost @@ -2185,7 +2216,8 @@ public abstract class PlayerImpl implements Player, Serializable { this.wins = true; game.end(); } - } else { + } + else { logger.debug("player won -> but already lost before: " + this.getName()); } } @@ -2210,7 +2242,8 @@ public abstract class PlayerImpl implements Player, Serializable { public boolean hasWon() { if (!this.loses) { return this.wins; - } else { + } + else { return false; } } @@ -2238,7 +2271,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (blocker != null && group != null && group.canBlock(blocker, game)) { group.addBlocker(blockerId, playerId, game); game.getCombat().addBlockingGroup(blockerId, attackerId, playerId, game); - } else if (this.isHuman() && !game.isSimulation()) { + } + else if (this.isHuman() && !game.isSimulation()) { game.informPlayer(this, "You can't block this creature."); } } @@ -2256,7 +2290,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (targetPlayerId.equals(playerId)) { searchInfo = getLogName() + " searches his or her library"; searchedLibrary = library; - } else { + } + else { Player targetPlayer = game.getPlayer(targetPlayerId); if (targetPlayer != null) { searchInfo = getLogName() + " searches the library of " + targetPlayer.getLogName(); @@ -2276,7 +2311,8 @@ public abstract class PlayerImpl implements Player, Serializable { int librarySearchLimit = event.getAmount(); if (librarySearchLimit == Integer.MAX_VALUE) { count = searchedLibrary.count(target.getFilter(), game); - } else { + } + else { newTarget.setCardLimit(librarySearchLimit); count = Math.min(searchedLibrary.count(target.getFilter(), game), librarySearchLimit); } @@ -2369,7 +2405,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (canAdd) { if (withCost) { sourceWithCosts.add(manaAbilities); - } else { + } + else { sourceWithoutManaCosts.add(manaAbilities); } } @@ -2473,7 +2510,8 @@ public abstract class PlayerImpl implements Player, Serializable { ManaOptions abilityOptions = copy.getManaCostsToPay().getOptions(); if (abilityOptions.isEmpty()) { return true; - } else { + } + else { if (available == null) { return true; } @@ -2521,7 +2559,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (manaCosts.size() == 0) { return true; - } else { + } + else { if (available == null) { return true; } @@ -2552,7 +2591,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (manaCosts.size() == 0) { return true; - } else { + } + else { for (Mana mana : manaCosts.getOptions()) { for (Mana avail : available) { if (mana.enough(avail)) { @@ -2589,7 +2629,8 @@ public abstract class PlayerImpl implements Player, Serializable { if (manaCosts.size() == 0) { return true; - } else { + } + else { for (Mana mana : manaCosts.getOptions()) { for (Mana avail : available) { if (mana.enough(avail)) { @@ -2626,12 +2667,14 @@ public abstract class PlayerImpl implements Player, Serializable { playable.add(ability); } } - } else if (ability instanceof AlternativeSourceCosts) { + } + else if (ability instanceof AlternativeSourceCosts) { if (card.getCardType().contains(CardType.LAND)) { if (canLandPlayAlternateSourceCostsAbility(card, availableMana, ability, game)) { // e.g. Land with Morph playable.add(ability); } - } else if (card.getCardType().contains(CardType.CREATURE)) { // e.g. makes a card available for play by Morph if the card may not be cast normally + } + else if (card.getCardType().contains(CardType.CREATURE)) { // e.g. makes a card available for play by Morph if the card may not be cast normally if (!playable.contains(card.getSpellAbility())) { if (((AlternativeSourceCosts) ability).isAvailable(card.getSpellAbility(), game)) { playable.add(card.getSpellAbility()); @@ -2649,7 +2692,8 @@ public abstract class PlayerImpl implements Player, Serializable { boolean possible = false; if (ability.getZone().match(Zone.GRAVEYARD)) { possible = true; - } else if (ability.getZone().match(Zone.HAND) && (ability instanceof SpellAbility || ability instanceof PlayLandAbility)) { + } + else if (ability.getZone().match(Zone.HAND) && (ability instanceof SpellAbility || ability instanceof PlayLandAbility)) { if (asThoughtCast || canPlayCardsFromGraveyard()) { possible = true; } @@ -2800,8 +2844,7 @@ public abstract class PlayerImpl implements Player, Serializable { } /** - * Skip "silent" phase step when players are not allowed to cast anything. - * E.g. players can't play or cast anything during declaring attackers. + * Skip "silent" phase step when players are not allowed to cast anything. E.g. players can't play or cast anything during declaring attackers. * * @param game * @return @@ -2833,14 +2876,17 @@ public abstract class PlayerImpl implements Player, Serializable { if (ability.isModal()) { addModeOptions(options, ability, game); - } else if (ability.getTargets().getUnchosen().size() > 0) { + } + else if (ability.getTargets().getUnchosen().size() > 0) { // TODO: Handle other variable costs than mana costs if (ability.getManaCosts().getVariableCosts().size() > 0) { addVariableXOptions(options, ability, 0, game); - } else { + } + else { addTargetOptions(options, ability, 0, game); } - } else if (ability.getCosts().getTargets().getUnchosen().size() > 0) { + } + else if (ability.getCosts().getTargets().getUnchosen().size() > 0) { addCostTargetOptions(options, ability, 0, game); } @@ -2857,12 +2903,15 @@ public abstract class PlayerImpl implements Player, Serializable { if (newOption.getTargets().getUnchosen().size() > 0) { if (newOption.getManaCosts().getVariableCosts().size() > 0) { addVariableXOptions(options, newOption, 0, game); - } else { + } + else { addTargetOptions(options, newOption, 0, game); } - } else if (newOption.getCosts().getTargets().getUnchosen().size() > 0) { + } + else if (newOption.getCosts().getTargets().getUnchosen().size() > 0) { addCostTargetOptions(options, newOption, 0, game); - } else { + } + else { options.add(newOption); } } @@ -2880,16 +2929,19 @@ public abstract class PlayerImpl implements Player, Serializable { int amount = target.getTargetAmount(targetId); newOption.getTargets().get(targetNum).addTarget(targetId, amount, newOption, game, true); } - } else { + } + else { for (UUID targetId : target.getTargets()) { newOption.getTargets().get(targetNum).addTarget(targetId, newOption, game, true); } } if (targetNum < option.getTargets().size() - 2) { addTargetOptions(options, newOption, targetNum + 1, game); - } else if (option.getCosts().getTargets().size() > 0) { + } + else if (option.getCosts().getTargets().size() > 0) { addCostTargetOptions(options, newOption, 0, game); - } else { + } + else { options.add(newOption); } } @@ -2901,7 +2953,8 @@ public abstract class PlayerImpl implements Player, Serializable { newOption.getCosts().getTargets().get(targetNum).addTarget(targetId, option, game, true); if (targetNum < option.getCosts().getTargets().size() - 1) { addCostTargetOptions(options, newOption, targetNum + 1, game); - } else { + } + else { options.add(newOption); } } @@ -3109,14 +3162,16 @@ public abstract class PlayerImpl implements Player, Serializable { if (permanent != null) { cardList.add(permanent); } - } else { + } + else { Card card = game.getCard(cardId); if (card == null) { Spell spell = game.getState().getStack().getSpell(cardId); if (spell != null) { if (!spell.isCopy()) { card = spell.getCard(); - } else { + } + else { // If a spell is returned to its owner's hand, it's removed from the stack and thus will not resolve game.getStack().remove(spell); game.informPlayers(spell.getLogName() + " was removed from the stack"); @@ -3184,6 +3239,24 @@ public abstract class PlayerImpl implements Player, Serializable { case BATTLEFIELD: // new logic that does not yet add the permanents to battlefield while replacement effects are handled List permanents = new ArrayList<>(); List permanentsEntered = new ArrayList<>(); + // Move meld pieces instead of the meld card if unmelded + Set meldPiecesToAdd = new HashSet<>(0); + Set meldCardsRemoved = new HashSet<>(0); + for (Iterator it = cards.iterator(); it.hasNext();) { + Card card = it.next(); + if (card instanceof MeldCard && !((MeldCard) card).isMelded()) { + MeldCard meldCard = (MeldCard) card; + if (meldCard.getTopLastZoneChangeCounter() == meldCard.getTopHalfCard().getZoneChangeCounter(game)) { + meldPiecesToAdd.add(meldCard.getTopHalfCard()); + } + if (meldCard.getBottomLastZoneChangeCounter() == meldCard.getBottomHalfCard().getZoneChangeCounter(game)) { + meldPiecesToAdd.add(meldCard.getBottomHalfCard()); + } + meldCardsRemoved.add(meldCard); + it.remove(); + } + } + cards.addAll(meldPiecesToAdd); for (Card card : cards) { UUID controllingPlayerId = byOwner ? card.getOwnerId() : getId(); fromZone = game.getState().getZone(card.getId()); @@ -3193,7 +3266,13 @@ public abstract class PlayerImpl implements Player, Serializable { ZoneChangeEvent event = new ZoneChangeEvent(card.getId(), source.getSourceId(), controllingPlayerId, fromZone, Zone.BATTLEFIELD, appliedEffects, tapped); if (!game.replaceEvent(event)) { // get permanent - Permanent permanent = new PermanentCard(card, event.getPlayerId(), game);// controlling player can be replaced so use event player now + Permanent permanent; + if (card instanceof MeldCard) { + permanent = new PermanentMeld(card, event.getPlayerId(), game);// controlling player can be replaced so use event player now + } + else { + permanent = new PermanentCard(card, event.getPlayerId(), game);// controlling player can be replaced so use event player now + } permanents.add(permanent); game.getPermanentsEntering().put(permanent.getId(), permanent); card.checkForCountersToAdd(permanent, game); @@ -3211,7 +3290,8 @@ public abstract class PlayerImpl implements Player, Serializable { game.getContinuousEffects().setController(permanent.getId(), permanent.getControllerId()); if (permanent.entersBattlefield(source.getSourceId(), game, fromZone, true)) { permanentsEntered.add(permanent); - } else { + } + else { // revert controller to owner if permanent does not enter game.getContinuousEffects().setController(permanent.getId(), permanent.getOwnerId()); game.getPermanentsEntering().remove(permanent.getId()); @@ -3231,10 +3311,16 @@ public abstract class PlayerImpl implements Player, Serializable { game.informPlayers(this.getLogName() + " puts " + (faceDown ? "a card face down " : permanent.getLogName()) + " from " + fromZone.toString().toLowerCase(Locale.ENGLISH) + " onto the Battlefield"); } - } else { + } + else { game.getPermanentsEntering().remove(permanent.getId()); } } + // Update the lastZoneChangeCounter of meld pieces that were moved + for (MeldCard meldCard : meldCardsRemoved) { + meldCard.setTopLastZoneChangeCounter(meldCard.getTopHalfCard().getZoneChangeCounter(game)); + meldCard.setBottomLastZoneChangeCounter(meldCard.getBottomHalfCard().getZoneChangeCounter(game)); + } game.applyEffects(); break; case HAND: @@ -3260,7 +3346,8 @@ public abstract class PlayerImpl implements Player, Serializable { for (Card card : cards) { if (card instanceof Spell) { fromZone = game.getState().getZone(((Spell) card).getSourceId()); - } else { + } + else { fromZone = game.getState().getZone(card.getId()); } boolean hideCard = fromZone.equals(Zone.HAND) || fromZone.equals(Zone.LIBRARY); @@ -3308,7 +3395,7 @@ public abstract class PlayerImpl implements Player, Serializable { card = game.getPermanent(card.getId()); } if (card.moveToZone(Zone.HAND, sourceId, game, false)) { - if (card instanceof PermanentCard) { + if (card instanceof PermanentCard && game.getCard(card.getId()) != null) { card = game.getCard(card.getId()); } if (!game.isSimulation()) { @@ -3378,7 +3465,8 @@ public abstract class PlayerImpl implements Player, Serializable { movedCards.add(card); } } - } else { + } + else { for (Card card : cards.getCards(game)) { if (choosingPlayer.moveCardToGraveyardWithInfo(card, sourceId, game, fromZone)) { movedCards.add(card); @@ -3396,7 +3484,7 @@ public abstract class PlayerImpl implements Player, Serializable { // Zone fromZone = game.getState().getZone(card.getId()); if (card.moveToZone(Zone.GRAVEYARD, sourceId, game, fromZone != null ? fromZone.equals(Zone.BATTLEFIELD) : false)) { if (!game.isSimulation()) { - if (card instanceof PermanentCard) { + if (card instanceof PermanentCard && game.getCard(card.getId()) != null) { card = game.getCard(card.getId()); } StringBuilder sb = new StringBuilder(this.getLogName()) @@ -3404,7 +3492,8 @@ public abstract class PlayerImpl implements Player, Serializable { .append(fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH) + " " : ""); if (card.getOwnerId().equals(getId())) { sb.append("into his or her graveyard"); - } else { + } + else { sb.append("it into its owner's graveyard"); } game.informPlayers(sb.toString()); @@ -3419,7 +3508,7 @@ public abstract class PlayerImpl implements Player, Serializable { boolean result = false; if (card.moveToZone(Zone.LIBRARY, sourceId, game, toTop)) { if (!game.isSimulation()) { - if (card instanceof PermanentCard) { + if (card instanceof PermanentCard && game.getCard(card.getId()) != null) { card = game.getCard(card.getId()); } StringBuilder sb = new StringBuilder(this.getLogName()) @@ -3430,7 +3519,8 @@ public abstract class PlayerImpl implements Player, Serializable { sb.append("to the ").append(toTop ? "top" : "bottom"); if (card.getOwnerId().equals(getId())) { sb.append(" of his or her library"); - } else { + } + else { Player player = game.getPlayer(card.getOwnerId()); if (player != null) { sb.append(" of ").append(player.getLogName()).append("'s library"); @@ -3453,8 +3543,6 @@ public abstract class PlayerImpl implements Player, Serializable { Card basicCard = game.getCard(card.getId()); if (basicCard != null) { card = basicCard; - } else { - logger.error("Couldn't get the card object for the PermanenCard named " + card.getName()); } } game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName() : "a card face down") + " " @@ -3564,7 +3652,8 @@ public abstract class PlayerImpl implements Player, Serializable { String text; if (cards.size() == 1) { text = "card if you want to put it to the bottom of your library (Scry)"; - } else { + } + else { text = "cards you want to put on the bottom of your library (Scry)"; } TargetCard target = new TargetCard(0, cards.size(), Zone.LIBRARY, new FilterCard(text)); From 54ad8a6ec0e931f33c6b3abf31e49b44a10c4e12 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Mon, 11 Jul 2016 17:10:14 +0200 Subject: [PATCH 8/8] MeldCard prevented possible NPE during test project build. --- Mage/src/main/java/mage/cards/MeldCard.java | 45 ++++++++------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/Mage/src/main/java/mage/cards/MeldCard.java b/Mage/src/main/java/mage/cards/MeldCard.java index 574e3a282d3..de50f932334 100644 --- a/Mage/src/main/java/mage/cards/MeldCard.java +++ b/Mage/src/main/java/mage/cards/MeldCard.java @@ -129,8 +129,7 @@ public abstract class MeldCard extends CardImpl { // Initial move to battlefield if (toZone == Zone.BATTLEFIELD) { return this.putOntoBattlefield(game, Zone.EXILED, sourceId, this.getOwnerId(), false, false, appliedEffects); - } - // Move when melded from the battlefield to elsewhere + } // Move when melded from the battlefield to elsewhere else { ZoneChangeEvent event = new ZoneChangeEvent(this.getId(), sourceId, this.getOwnerId(), Zone.BATTLEFIELD, toZone, appliedEffects); if (!game.replaceEvent(event)) { @@ -156,8 +155,7 @@ public abstract class MeldCard extends CardImpl { cardsToMove.add(bottomHalfCard); if (flag) { controller.putCardsOnTopOfLibrary(cardsToMove, game, null, true); - } - else { + } else { controller.putCardsOnBottomOfLibrary(cardsToMove, game, null, true); } } @@ -172,13 +170,11 @@ public abstract class MeldCard extends CardImpl { this.bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); game.addSimultaneousEvent(event); return true; - } - else { + } else { return false; } } - } - else { + } else { // Try to move the former meld cards after it has already left the battlefield. // If the meld parts didn't move from that zone, move them instead of the meld card. // Reset the local zcc so the meld card lose track of them. @@ -217,8 +213,7 @@ public abstract class MeldCard extends CardImpl { if (exileId == null) { game.getExile().getPermanentExile().add(topHalfCard); game.getExile().getPermanentExile().add(bottomHalfCard); - } - else { + } else { game.getExile().createZone(exileId, name).add(topHalfCard); game.getExile().getExileZone(exileId).add(bottomHalfCard); } @@ -231,8 +226,7 @@ public abstract class MeldCard extends CardImpl { cardsToMove.add(bottomHalfCard); if (event.getFlag()) { controller.putCardsOnTopOfLibrary(cardsToMove, game, null, true); - } - else { + } else { controller.putCardsOnBottomOfLibrary(cardsToMove, game, null, true); } } @@ -247,12 +241,10 @@ public abstract class MeldCard extends CardImpl { this.bottomLastZoneChangeCounter = bottomHalfCard.getZoneChangeCounter(game); game.addSimultaneousEvent(event); return true; - } - else { + } else { return false; } - } - else { + } else { // Try to move the former meld cards after it has already left the battlefield. // If the meld parts didn't move from that zone, move them instead of the meld card. // Reset the local zcc so the meld card lose track of them. @@ -291,8 +283,7 @@ public abstract class MeldCard extends CardImpl { permanent.setTapped(true); } event.setTarget(permanent); - } - else { + } else { return false; } game.setZone(objectId, event.getToZone()); @@ -300,13 +291,11 @@ public abstract class MeldCard extends CardImpl { game.getExile().removeCard(this.topHalfCard, game); game.getExile().removeCard(this.bottomHalfCard, game); return true; - } - else { + } else { this.setMelded(false); return false; } - } - else { + } else { // Try to move the former meld cards after it has already left the battlefield. // If the meld parts didn't move from that zone, move them instead of the meld card. // Reset the local zcc so the meld card lose track of them. @@ -335,9 +324,9 @@ public abstract class MeldCard extends CardImpl { public int getConvertedManaCost() { if (this.isCopy()) { return 0; - } - else { - return this.topHalfCard.getConvertedManaCost() + this.bottomHalfCard.getConvertedManaCost(); + } else { + return (this.topHalfCard != null ? this.topHalfCard.getConvertedManaCost() : 0) + + (this.bottomHalfCard != null ? this.bottomHalfCard.getConvertedManaCost() : 0); } } @@ -345,8 +334,7 @@ public abstract class MeldCard extends CardImpl { public void addCounters(Counter counter, Game game, ArrayList appliedEffects) { if (this.isMelded()) { super.addCounters(counter, game, appliedEffects); - } - else { + } else { if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { topHalfCard.addCounters(counter, game, appliedEffects); } @@ -360,8 +348,7 @@ public abstract class MeldCard extends CardImpl { public void addCounters(String name, int amount, Game game, ArrayList appliedEffects) { if (this.isMelded()) { super.addCounters(name, amount, game, appliedEffects); - } - else { + } else { if (topLastZoneChangeCounter == topHalfCard.getZoneChangeCounter(game)) { topHalfCard.addCounters(name, amount, game, appliedEffects); }