From 3822e0d09be4811490e328d632b9283e2a428eef Mon Sep 17 00:00:00 2001 From: magenoxx Date: Thu, 17 May 2012 09:51:08 +0400 Subject: [PATCH] Soulbond keyword --- .../java/mage/player/ai/ComputerPlayer.java | 44 +++--- .../avacynrestored/DemonlordOfAshmouth.java | 4 +- .../sets/avacynrestored/TrustedForcemage.java | 18 ++- .../keywords/SoulbondKeywordTest.java | 53 ++++++-- Mage/src/mage/Constants.java | 8 +- .../common/continious/BoostPairedEffect.java | 83 ++++++++++++ .../abilities/keyword/SoulbondAbility.java | 65 +++++++++ .../FilterControlledCreaturePermanent.java | 2 +- ...rNotPairedControlledCreaturePermanent.java | 81 +++++++++++ Mage/src/mage/game/GameImpl.java | 26 +++- Mage/src/mage/game/permanent/Permanent.java | 25 +++- .../mage/game/permanent/PermanentImpl.java | 25 +++- .../mage/watchers/common/SoulbondWatcher.java | 126 ++++++++++++++++++ 13 files changed, 503 insertions(+), 57 deletions(-) create mode 100644 Mage/src/mage/abilities/effects/common/continious/BoostPairedEffect.java create mode 100644 Mage/src/mage/abilities/keyword/SoulbondAbility.java create mode 100644 Mage/src/mage/filter/common/FilterNotPairedControlledCreaturePermanent.java create mode 100644 Mage/src/mage/watchers/common/SoulbondWatcher.java diff --git a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java index 4e35a867f9e..e436b2be1d6 100644 --- a/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/ComputerPlayer.java @@ -57,8 +57,12 @@ import mage.filter.common.*; import mage.game.Game; import mage.game.combat.CombatGroup; import mage.game.draft.Draft; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; import mage.game.match.Match; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.game.tournament.Tournament; import mage.player.ai.simulators.CombatGroupSimulator; import mage.player.ai.simulators.CombatSimulator; @@ -79,10 +83,6 @@ import java.io.IOException; import java.io.Serializable; import java.util.*; import java.util.Map.Entry; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.stack.Spell; -import mage.game.stack.StackObject; /** * @@ -184,7 +184,7 @@ public class ComputerPlayer> extends PlayerImpl i } if (target instanceof TargetControlledPermanent) { List targets; - targets = threats(playerId, ((TargetControlledPermanent)target).getFilter(), game, target.getTargets()); + targets = threats(playerId, sourceId, ((TargetControlledPermanent)target).getFilter(), game, target.getTargets()); if (!outcome.isGood()) Collections.reverse(targets); for (Permanent permanent: targets) { @@ -197,13 +197,13 @@ public class ComputerPlayer> extends PlayerImpl i if (target instanceof TargetPermanent) { List targets; if (outcome.isCanTargetAll()) { - targets = threats(null, ((TargetPermanent)target).getFilter(), game, target.getTargets()); + targets = threats(null, sourceId, ((TargetPermanent)target).getFilter(), game, target.getTargets()); } else { if (outcome.isGood()) { - targets = threats(playerId, ((TargetPermanent)target).getFilter(), game, target.getTargets()); + targets = threats(playerId, sourceId, ((TargetPermanent)target).getFilter(), game, target.getTargets()); } else { - targets = threats(opponentId, ((TargetPermanent)target).getFilter(), game, target.getTargets()); + targets = threats(opponentId, sourceId, ((TargetPermanent)target).getFilter(), game, target.getTargets()); } } for (Permanent permanent: targets) { @@ -226,10 +226,10 @@ public class ComputerPlayer> extends PlayerImpl i List targets; TargetCreatureOrPlayer t = ((TargetCreatureOrPlayer)target); if (outcome.isGood()) { - targets = threats(playerId, ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(playerId, sourceId, ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets()); } else { - targets = threats(opponentId, ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(opponentId, sourceId, ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets()); } for (Permanent permanent : targets) { List alreadyTargetted = target.getTargets(); @@ -259,10 +259,10 @@ public class ComputerPlayer> extends PlayerImpl i List targets; TargetPermanentOrPlayer t = ((TargetPermanentOrPlayer)target); if (outcome.isGood()) { - targets = threats(playerId, ((FilterPermanentOrPlayer)t.getFilter()).getPermanentFilter(), game, target.getTargets()); + targets = threats(playerId, sourceId, ((FilterPermanentOrPlayer)t.getFilter()).getPermanentFilter(), game, target.getTargets()); } else { - targets = threats(opponentId, ((FilterPermanentOrPlayer)t.getFilter()).getPermanentFilter(), game, target.getTargets()); + targets = threats(opponentId, sourceId, ((FilterPermanentOrPlayer)t.getFilter()).getPermanentFilter(), game, target.getTargets()); } for (Permanent permanent : targets) { List alreadyTargetted = target.getTargets(); @@ -356,7 +356,7 @@ public class ComputerPlayer> extends PlayerImpl i } if (target instanceof TargetControlledPermanent) { List targets; - targets = threats(playerId, ((TargetControlledPermanent)target).getFilter(), game, target.getTargets()); + targets = threats(playerId, source.getSourceId(), ((TargetControlledPermanent)target).getFilter(), game, target.getTargets()); if (!outcome.isGood()) Collections.reverse(targets); for (Permanent permanent: targets) { @@ -370,10 +370,10 @@ public class ComputerPlayer> extends PlayerImpl i if (target instanceof TargetPermanent) { List targets; if (outcome.isGood()) { - targets = threats(playerId, ((TargetPermanent)target).getFilter(), game, target.getTargets()); + targets = threats(playerId, source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets()); } else { - targets = threats(opponentId, ((TargetPermanent)target).getFilter(), game, target.getTargets()); + targets = threats(opponentId, source.getSourceId(), ((TargetPermanent)target).getFilter(), game, target.getTargets()); } if (targets.isEmpty() && target.isRequired()) { targets = game.getBattlefield().getActivePermanents(((TargetPermanent)target).getFilter(), playerId, game); @@ -390,10 +390,10 @@ public class ComputerPlayer> extends PlayerImpl i List targets; TargetCreatureOrPlayer t = ((TargetCreatureOrPlayer)target); if (outcome.isGood()) { - targets = threats(playerId, ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(playerId, source.getSourceId(), ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets()); } else { - targets = threats(opponentId, ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets()); + targets = threats(opponentId, source.getSourceId(), ((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), game, target.getTargets()); } if (targets.isEmpty() && target.isRequired()) { targets = game.getBattlefield().getActivePermanents(((FilterCreatureOrPlayer)t.getFilter()).getCreatureFilter(), playerId, game); @@ -503,10 +503,10 @@ public class ComputerPlayer> extends PlayerImpl i } List targets; if (outcome.isGood()) { - targets = threats(playerId, new FilterCreaturePermanent(), game, target.getTargets()); + targets = threats(playerId, source.getSourceId(), new FilterCreaturePermanent(), game, target.getTargets()); } else { - targets = threats(opponentId, new FilterCreaturePermanent(), game, target.getTargets()); + targets = threats(opponentId, source.getSourceId(), new FilterCreaturePermanent(), game, target.getTargets()); } for (Permanent permanent: targets) { if (target.canTarget(permanent.getId(), source, game)) { @@ -1495,13 +1495,13 @@ public class ComputerPlayer> extends PlayerImpl i return worst; } - protected List threats(UUID playerId, FilterPermanent filter, Game game, List targets) { + protected List threats(UUID playerId, UUID sourceId, FilterPermanent filter, Game game, List targets) { List threats = playerId == null ? game.getBattlefield().getAllActivePermanents(filter) : - game.getBattlefield().getAllActivePermanents(filter, playerId); + game.getBattlefield().getActivePermanents(filter, playerId, sourceId, game); Iterator it = threats.iterator(); - while (it.hasNext()) { // remove permanents already targetted + while (it.hasNext()) { // remove permanents already targeted Permanent test = it.next(); if (targets.contains(test.getId())) it.remove(); diff --git a/Mage.Sets/src/mage/sets/avacynrestored/DemonlordOfAshmouth.java b/Mage.Sets/src/mage/sets/avacynrestored/DemonlordOfAshmouth.java index 7fdb7ecdda9..9e46372ab50 100644 --- a/Mage.Sets/src/mage/sets/avacynrestored/DemonlordOfAshmouth.java +++ b/Mage.Sets/src/mage/sets/avacynrestored/DemonlordOfAshmouth.java @@ -36,7 +36,7 @@ import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.UndyingAbility; import mage.cards.CardImpl; -import mage.filter.common.FilterControlledPermanent; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.target.common.TargetControlledPermanent; import java.util.UUID; @@ -46,7 +46,7 @@ import java.util.UUID; */ public class DemonlordOfAshmouth extends CardImpl { - private static FilterControlledPermanent filter = new FilterControlledPermanent(" another creature"); + private static FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(" another creature"); static { filter.setAnother(true); diff --git a/Mage.Sets/src/mage/sets/avacynrestored/TrustedForcemage.java b/Mage.Sets/src/mage/sets/avacynrestored/TrustedForcemage.java index dc99fa99384..39ad76fd001 100644 --- a/Mage.Sets/src/mage/sets/avacynrestored/TrustedForcemage.java +++ b/Mage.Sets/src/mage/sets/avacynrestored/TrustedForcemage.java @@ -27,19 +27,24 @@ */ package mage.sets.avacynrestored; -import java.util.UUID; +import mage.Constants; import mage.Constants.CardType; import mage.Constants.Rarity; import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.continious.BoostPairedEffect; +import mage.abilities.keyword.SoulbondAbility; import mage.cards.CardImpl; -/** - * - * @author noxx +import java.util.UUID; +/** + * @author noxx */ public class TrustedForcemage extends CardImpl { + private static final String ruleText = "As long as Trusted Forcemage is paired with another creature, each of those creatures gets +1/+1"; + public TrustedForcemage(UUID ownerId) { super(ownerId, 199, "Trusted Forcemage", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.expansionSetCode = "AVR"; @@ -51,7 +56,10 @@ public class TrustedForcemage extends CardImpl { this.toughness = new MageInt(2); // Soulbond + this.addAbility(SoulbondAbility.getInstance()); + // As long as Trusted Forcemage is paired with another creature, each of those creatures gets +1/+1. + this.addAbility(new SimpleStaticAbility(Constants.Zone.BATTLEFIELD, new BoostPairedEffect(1, 1, ruleText))); } public TrustedForcemage(final TrustedForcemage card) { @@ -62,4 +70,4 @@ public class TrustedForcemage extends CardImpl { public TrustedForcemage copy() { return new TrustedForcemage(this); } -} +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SoulbondKeywordTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SoulbondKeywordTest.java index 82a3bfe6cc7..bf6c434c36c 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SoulbondKeywordTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SoulbondKeywordTest.java @@ -50,7 +50,7 @@ public class SoulbondKeywordTest extends CardTestPlayerBase { */ @Test public void testTwoSoulbondCreaturesOnBattlefield() { - addCard(Constants.Zone.BATTLEFIELD, playerA, "Trusted Forcemage", 2); + addCard(Constants.Zone.HAND, playerA, "Trusted Forcemage", 2); addCard(Constants.Zone.BATTLEFIELD, playerA, "Forest", 6); castSpell(1, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Trusted Forcemage"); @@ -59,8 +59,8 @@ public class SoulbondKeywordTest extends CardTestPlayerBase { setStopAt(1, Constants.PhaseStep.BEGIN_COMBAT); execute(); - assertPermanentCount(playerA, "Trusted Forcemage", 1); - assertPowerToughness(playerA, "Trusted Forcemage", 3, 3, Filter.ComparisonScope.All); + assertPermanentCount(playerA, "Trusted Forcemage", 2); + assertPowerToughness(playerA, "Trusted Forcemage", 4, 4, Filter.ComparisonScope.All); } /** @@ -168,8 +168,8 @@ public class SoulbondKeywordTest extends CardTestPlayerBase { addCard(Constants.Zone.HAND, playerB, "Act of Treason"); addCard(Constants.Zone.BATTLEFIELD, playerB, "Mountain", 3); - addCard(Constants.Zone.BATTLEFIELD, playerB, "Elite Vanguard"); - addCard(Constants.Zone.BATTLEFIELD, playerB, "Plains", 1); + addCard(Constants.Zone.HAND, playerB, "Elite Vanguard"); + addCard(Constants.Zone.BATTLEFIELD, playerB, "Plains", 3); castSpell(1, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Trusted Forcemage"); castSpell(2, Constants.PhaseStep.PRECOMBAT_MAIN, playerB, "Act of Treason", "Trusted Forcemage"); @@ -196,8 +196,8 @@ public class SoulbondKeywordTest extends CardTestPlayerBase { addCard(Constants.Zone.HAND, playerB, "Act of Treason"); addCard(Constants.Zone.BATTLEFIELD, playerB, "Mountain", 3); - addCard(Constants.Zone.BATTLEFIELD, playerB, "Elite Vanguard"); - addCard(Constants.Zone.BATTLEFIELD, playerB, "Plains", 1); + addCard(Constants.Zone.HAND, playerB, "Elite Vanguard"); + addCard(Constants.Zone.BATTLEFIELD, playerB, "Plains", 3); castSpell(1, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Trusted Forcemage"); castSpell(2, Constants.PhaseStep.PRECOMBAT_MAIN, playerB, "Act of Treason", "Trusted Forcemage"); @@ -208,7 +208,7 @@ public class SoulbondKeywordTest extends CardTestPlayerBase { // returned back with no boost assertPermanentCount(playerA, "Trusted Forcemage", 1); - assertPowerToughness(playerB, "Trusted Forcemage", 2, 2); + assertPowerToughness(playerA, "Trusted Forcemage", 2, 2); // no boost on next turn (gets unpaired) assertPowerToughness(playerB, "Elite Vanguard", 2, 1); @@ -222,7 +222,7 @@ public class SoulbondKeywordTest extends CardTestPlayerBase { addCard(Constants.Zone.BATTLEFIELD, playerA, "Elite Vanguard"); addCard(Constants.Zone.HAND, playerA, "Trusted Forcemage"); addCard(Constants.Zone.BATTLEFIELD, playerA, "Forest", 3); - addCard(Constants.Zone.BATTLEFIELD, playerA, "Island", 1); + addCard(Constants.Zone.BATTLEFIELD, playerA, "Island", 3); addCard(Constants.Zone.HAND, playerA, "Unsummon", 1); castSpell(1, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Trusted Forcemage"); @@ -231,8 +231,8 @@ public class SoulbondKeywordTest extends CardTestPlayerBase { setStopAt(1, Constants.PhaseStep.BEGIN_COMBAT); execute(); - assertPowerToughness(playerA, "Trusted Forcemage", 2, 2); assertPermanentCount(playerA, "Elite Vanguard", 0); + assertPowerToughness(playerA, "Trusted Forcemage", 2, 2); } /** @@ -265,9 +265,9 @@ public class SoulbondKeywordTest extends CardTestPlayerBase { addCard(Constants.Zone.BATTLEFIELD, playerA, "Blinkmoth Nexus", 1); castSpell(1, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Trusted Forcemage"); - activateAbility(1, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "{1}: "); + activateAbility(1, Constants.PhaseStep.BEGIN_COMBAT, playerA, "{1}: "); - setStopAt(1, Constants.PhaseStep.BEGIN_COMBAT); + setStopAt(1, Constants.PhaseStep.DECLARE_ATTACKERS); execute(); // no effect on later animation @@ -293,4 +293,33 @@ public class SoulbondKeywordTest extends CardTestPlayerBase { // test boost loss assertPowerToughness(playerA, "Trusted Forcemage", 2, 2); } + + /** + * Tests that after loosing first pair it is possible to pair creature with another one + */ + @Test + public void testRebondOnNextCreature() { + addCard(Constants.Zone.BATTLEFIELD, playerA, "Elite Vanguard"); + addCard(Constants.Zone.HAND, playerA, "Phantasmal Bear"); + addCard(Constants.Zone.HAND, playerA, "Trusted Forcemage"); + addCard(Constants.Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Constants.Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Constants.Zone.BATTLEFIELD, playerA, "Plains", 3); + addCard(Constants.Zone.BATTLEFIELD, playerA, "Island", 3); + + addCard(Constants.Zone.HAND, playerA, "Lightning Bolt", 1); + + castSpell(1, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Trusted Forcemage"); + castSpell(1, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Elite Vanguard"); + castSpell(1, Constants.PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Bear"); + + setStopAt(1, Constants.PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Elite Vanguard", 0); + assertPermanentCount(playerA, "Phantasmal Bear", 1); + + assertPowerToughness(playerA, "Trusted Forcemage", 3, 3); + assertPowerToughness(playerA, "Phantasmal Bear", 3, 3); + } } diff --git a/Mage/src/mage/Constants.java b/Mage/src/mage/Constants.java index 4891e19c8b2..8d93574ff1e 100644 --- a/Mage/src/mage/Constants.java +++ b/Mage/src/mage/Constants.java @@ -28,9 +28,6 @@ package mage; -import java.util.ArrayList; -import java.util.List; - public final class Constants { public enum ColoredManaSymbol { @@ -445,12 +442,13 @@ public final class Constants { CARD } - public static final List PlaneswalkerTypes = new ArrayList() +/* public static final List PlaneswalkerTypes = new ArrayList() {{add("Ajani"); add("Bolas"); add("Chandra"); add("Elspeth"); add("Garruk"); add("Jace"); add("Liliana"); add("Nissa"); add("Sarkhan"); add("Sorin"); add("Tezzeret"); add("Karn"); add("Venser"); add("Gideon"); add("Koth");}}; - +*/ + private Constants() { throw new AssertionError(); } diff --git a/Mage/src/mage/abilities/effects/common/continious/BoostPairedEffect.java b/Mage/src/mage/abilities/effects/common/continious/BoostPairedEffect.java new file mode 100644 index 00000000000..1332d3264a7 --- /dev/null +++ b/Mage/src/mage/abilities/effects/common/continious/BoostPairedEffect.java @@ -0,0 +1,83 @@ +/* +* 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.continious; + +import mage.Constants.Duration; +import mage.Constants.Layer; +import mage.Constants.Outcome; +import mage.Constants.SubLayer; +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author noxx + */ +public class BoostPairedEffect extends ContinuousEffectImpl { + + private int power; + private int toughness; + + public BoostPairedEffect(int power, int toughness, String rule) { + super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.ModifyPT_7c, Outcome.BoostCreature); + this.power = power; + this.toughness = toughness; + staticText = rule; + } + + public BoostPairedEffect(final BoostPairedEffect effect) { + super(effect); + power = effect.power; + toughness = effect.toughness; + } + + @Override + public BoostPairedEffect copy() { + return new BoostPairedEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null && permanent.getPairedCard() != null) { + Permanent paired = game.getPermanent(permanent.getPairedCard()); + if (paired != null) { + permanent.addPower(1); + permanent.addToughness(1); + paired.addPower(1); + paired.addToughness(1); + return true; + } + } + return false; + } + +} diff --git a/Mage/src/mage/abilities/keyword/SoulbondAbility.java b/Mage/src/mage/abilities/keyword/SoulbondAbility.java new file mode 100644 index 00000000000..dc5068add76 --- /dev/null +++ b/Mage/src/mage/abilities/keyword/SoulbondAbility.java @@ -0,0 +1,65 @@ +/* +* 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.keyword; + +import mage.Constants.Zone; +import mage.abilities.StaticAbility; + +import java.io.ObjectStreamException; + +/** + * @author noxx + */ +public class SoulbondAbility extends StaticAbility { + + private static final SoulbondAbility fINSTANCE = new SoulbondAbility(); + + private Object readResolve() throws ObjectStreamException { + return fINSTANCE; + } + + public static SoulbondAbility getInstance() { + return fINSTANCE; + } + + private SoulbondAbility() { + super(Zone.BATTLEFIELD, null); + } + + @Override + public String getRule() { + return "Soulbond (You may pair this creature with another unpaired creature when either enters the battlefield. They remain paired for as long as you control both of them.)"; + } + + @Override + public SoulbondAbility copy() { + return fINSTANCE; + } + +} diff --git a/Mage/src/mage/filter/common/FilterControlledCreaturePermanent.java b/Mage/src/mage/filter/common/FilterControlledCreaturePermanent.java index 2ac2b284833..0dd07d892d0 100644 --- a/Mage/src/mage/filter/common/FilterControlledCreaturePermanent.java +++ b/Mage/src/mage/filter/common/FilterControlledCreaturePermanent.java @@ -34,7 +34,7 @@ import mage.Constants.CardType; * * @author BetaSteward_at_googlemail.com */ -public class FilterControlledCreaturePermanent extends FilterControlledPermanent { +public class FilterControlledCreaturePermanent extends FilterControlledPermanent> { public FilterControlledCreaturePermanent() { this("creature you control"); diff --git a/Mage/src/mage/filter/common/FilterNotPairedControlledCreaturePermanent.java b/Mage/src/mage/filter/common/FilterNotPairedControlledCreaturePermanent.java new file mode 100644 index 00000000000..bbcf3ef5d8e --- /dev/null +++ b/Mage/src/mage/filter/common/FilterNotPairedControlledCreaturePermanent.java @@ -0,0 +1,81 @@ +/* + * 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.filter.common; + +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * + * @author noxx + */ +public class FilterNotPairedControlledCreaturePermanent extends FilterControlledCreaturePermanent { + + public FilterNotPairedControlledCreaturePermanent() { + this("not paired creature you control"); + } + + public FilterNotPairedControlledCreaturePermanent(String name) { + super(name); + } + + public FilterNotPairedControlledCreaturePermanent(final FilterNotPairedControlledCreaturePermanent filter) { + super(filter); + } + + @Override + public boolean match(Permanent permanent) { + if (!super.match(permanent)) + return notFilter; + + if (permanent.getPairedCard() != null) + return notFilter; + + return !notFilter; + } + + @Override + public boolean match(Permanent permanent, UUID sourceId, UUID playerId, Game game) { + if (!super.match(permanent, sourceId, playerId, game)) + return notFilter; + + if (permanent.getPairedCard() != null) + return notFilter; + + return !notFilter; + } + + @Override + public FilterNotPairedControlledCreaturePermanent copy() { + return new FilterNotPairedControlledCreaturePermanent(this); + } + +} diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index b3d4ba7e49b..5c9e189d5cf 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -67,10 +67,7 @@ import mage.players.Players; import mage.target.Target; import mage.target.TargetPermanent; import mage.target.TargetPlayer; -import mage.watchers.common.CastSpellLastTurnWatcher; -import mage.watchers.common.MiracleWatcher; -import mage.watchers.common.MorbidWatcher; -import mage.watchers.common.PlayerDamagedBySourceWatcher; +import mage.watchers.common.*; import org.apache.log4j.Logger; import java.io.IOException; @@ -505,6 +502,7 @@ public abstract class GameImpl> implements Game, Serializa state.getWatchers().add(new MorbidWatcher()); state.getWatchers().add(new CastSpellLastTurnWatcher()); state.getWatchers().add(new MiracleWatcher()); + state.getWatchers().add(new SoulbondWatcher()); //20100716 - 103.5 for (UUID playerId: state.getPlayerList(startingPlayerId)) { @@ -810,6 +808,26 @@ public abstract class GameImpl> implements Game, Serializa continue; } } + if (perm.getPairedCard() != null) { + //702.93e.: ...another player gains control + // ...or the creature it's paired with leaves the battlefield. + Permanent paired = getPermanent(perm.getPairedCard()); + if (paired == null || !perm.getControllerId().equals(paired.getControllerId())) { + perm.setPairedCard(null); + if (paired != null) { + paired.setPairedCard(null); + } + somethingHappened = true; + } + } + } else if (perm.getPairedCard() != null) { + //702.93e.: ...stops being a creature + Permanent paired = getPermanent(perm.getPairedCard()); + perm.setPairedCard(null); + if (paired != null) { + paired.setPairedCard(null); + } + somethingHappened = true; } if (perm.getCardType().contains(CardType.PLANESWALKER)) { //20091005 - 704.5i diff --git a/Mage/src/mage/game/permanent/Permanent.java b/Mage/src/mage/game/permanent/Permanent.java index d81391f878b..eaaafd028e8 100644 --- a/Mage/src/mage/game/permanent/Permanent.java +++ b/Mage/src/mage/game/permanent/Permanent.java @@ -28,15 +28,15 @@ package mage.game.permanent; -import java.util.List; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.cards.Card; import mage.counters.Counter; import mage.counters.Counters; import mage.game.Game; -import mage.game.events.GameEvent; + +import java.util.List; +import java.util.UUID; public interface Permanent extends Card { @@ -177,6 +177,25 @@ public interface Permanent extends Card { */ public void clearConnectedCards(); + /** + * Sets paired card. + * + * @param pairedCard + */ + public void setPairedCard(UUID pairedCard); + + /** + * Gets paired card. Can return null. + * + * @return + */ + public UUID getPairedCard(); + + /** + * Makes permanent paired with no other permanent. + */ + public void clearPairedCard(); + @Override public Permanent copy(); diff --git a/Mage/src/mage/game/permanent/PermanentImpl.java b/Mage/src/mage/game/permanent/PermanentImpl.java index d00520a97a5..ffe84b5e58b 100644 --- a/Mage/src/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/mage/game/permanent/PermanentImpl.java @@ -28,6 +28,7 @@ package mage.game.permanent; +import mage.Constants.AsThoughEffectType; import mage.Constants.CardType; import mage.Constants.Zone; import mage.MageObject; @@ -43,9 +44,10 @@ import mage.game.events.*; import mage.game.events.GameEvent.EventType; import mage.players.Player; -import java.util.*; - -import mage.Constants.AsThoughEffectType; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; /** * @author BetaSteward_at_googlemail.com @@ -76,6 +78,7 @@ public abstract class PermanentImpl> extends CardImpl protected List connectedCards = new ArrayList(); protected List dealtDamageByThisTurn; protected UUID attachedTo; + protected UUID pairedCard; protected List markedDamage; private static final List emptyList = Collections.unmodifiableList(new ArrayList()); @@ -126,6 +129,7 @@ public abstract class PermanentImpl> extends CardImpl this.attachedTo = permanent.attachedTo; this.minBlockedBy = permanent.minBlockedBy; this.transformed = permanent.transformed; + this.pairedCard = permanent.pairedCard; } @Override @@ -824,4 +828,19 @@ public abstract class PermanentImpl> extends CardImpl public void setTransformed(boolean value) { this.transformed = value; } + + @Override + public void setPairedCard(UUID pairedCard) { + this.pairedCard = pairedCard; + } + + @Override + public UUID getPairedCard() { + return pairedCard; + } + + @Override + public void clearPairedCard() { + this.pairedCard = null; + } } diff --git a/Mage/src/mage/watchers/common/SoulbondWatcher.java b/Mage/src/mage/watchers/common/SoulbondWatcher.java new file mode 100644 index 00000000000..16297b7b2ea --- /dev/null +++ b/Mage/src/mage/watchers/common/SoulbondWatcher.java @@ -0,0 +1,126 @@ +/* + * 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.watchers.common; + +import mage.Constants; +import mage.Constants.WatcherScope; +import mage.abilities.keyword.SoulbondAbility; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.filter.common.FilterNotPairedControlledCreaturePermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; +import mage.watchers.WatcherImpl; + +/** + * Reacts on various events to pair or unpair creatures on the battlefield. + * + * @author noxx + */ +public class SoulbondWatcher extends WatcherImpl { + + private static FilterNotPairedControlledCreaturePermanent filter = new FilterNotPairedControlledCreaturePermanent("another not paired creature you control"); + + static { + filter.setAnother(true); + } + + public SoulbondWatcher() { + super("SoulbondWatcher", WatcherScope.GAME); + } + + public SoulbondWatcher(final SoulbondWatcher watcher) { + super(watcher); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.ZONE_CHANGE) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (zEvent.getToZone() == Constants.Zone.BATTLEFIELD) { + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null && permanent.getCardType().contains(Constants.CardType.CREATURE)) { + if (permanent.getAbilities().contains(SoulbondAbility.getInstance())) { + Player controller = game.getPlayer(permanent.getControllerId()); + if (controller != null) { + Cards cards = new CardsImpl(Constants.Zone.PICK); + cards.add(permanent); + controller.lookAtCards("Soulbond", cards, game); + if (controller.chooseUse(Constants.Outcome.Benefit, "Use Soulbond?", game)) { + TargetControlledPermanent target = new TargetControlledPermanent(filter); + target.setNotTarget(true); + if (target.canChoose(permanent.getId(), controller.getId(), game)) { + if (controller.choose(Constants.Outcome.Benefit, target, permanent.getId(), game)) { + Permanent chosen = game.getPermanent(target.getFirstTarget()); + if (chosen != null) { + chosen.setPairedCard(permanent.getId()); + permanent.setPairedCard(chosen.getId()); + } + } + } + } + } + } + + // if still unpaired + if (permanent.getPairedCard() == null) { + // try to find creature with Soulbond and unpaired + Player controller = null; + for (Permanent chosen : game.getBattlefield().getActivePermanents(filter, permanent.getControllerId(), permanent.getId(), game)) { + if (!chosen.getId().equals(permanent.getId()) && chosen.getAbilities().contains(SoulbondAbility.getInstance()) && chosen.getPairedCard() == null) { + if (controller == null) { + controller = game.getPlayer(permanent.getControllerId()); + } + if (controller != null) { + Cards cards = new CardsImpl(Constants.Zone.PICK); + cards.add(chosen); + controller.lookAtCards("Soulbond", cards, game); + if (controller.chooseUse(Constants.Outcome.Benefit, "Use Soulbond for recent " + permanent.getName() + "?", game)) { + chosen.setPairedCard(permanent.getId()); + permanent.setPairedCard(chosen.getId()); + break; + } + } + } + } + } + } + } + } + } + + @Override + public SoulbondWatcher copy() { + return new SoulbondWatcher(this); + } +}