* Soulbond - Reworked to two triggerd abilities (fixes #1882).

This commit is contained in:
LevelX2 2016-04-16 02:43:52 +02:00
parent 3b98d0714d
commit 1dbba3f7a9
32 changed files with 374 additions and 285 deletions

View file

@ -24,16 +24,15 @@
* 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.continuous;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
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;
@ -68,7 +67,7 @@ public class BoostPairedEffect extends ContinuousEffectImpl {
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());
Permanent paired = permanent.getPairedCard().getPermanent(game);
if (paired != null) {
permanent.addPower(power);
permanent.addToughness(toughness);

View file

@ -1,16 +1,16 @@
/*
* 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
@ -20,20 +20,20 @@
* 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.continuous;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
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;
@ -66,11 +66,15 @@ public class GainAbilityPairedEffect extends ContinuousEffectImpl {
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.addAbility(ability, game);
Permanent paired = permanent.getPairedCard().getPermanent(game);
if (paired != null && paired.getPairedCard() != null && paired.getPairedCard().equals(new MageObjectReference(permanent, game))) {
permanent.addAbility(ability, source.getSourceId(), game);
paired.addAbility(ability, source.getSourceId(), game, false);
return true;
} else {
// No longer the same cards as orininally paired.
permanent.setPairedCard(null);
}
}
return false;

View file

@ -24,32 +24,71 @@
* 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 java.io.ObjectStreamException;
import mage.abilities.MageSingleton;
import mage.abilities.StaticAbility;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAllTriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SetTargetPointer;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.permanent.AnotherPredicate;
import mage.filter.predicate.permanent.ControllerPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetControlledPermanent;
/**
* @author noxx
* 702.94. Soulbond
*
* 702.94a Soulbond is a keyword that represents two triggered abilities.
* Soulbond means When this creature enters the battlefield, if you control
* both this creature and another creature and both are unpaired, you may pair
* this creature with another unpaired creature you control for as long as both
* remain creatures on the battlefield under your control and Whenever another
* creature enters the battlefield under your control, if you control both that
* creature and this one and both are unpaired, you may pair that creature with
* this creature for as long as both remain creatures on the battlefield under
* your control.
*
* 702.94b A creature becomes paired with another as the result of a soulbond
* ability. Abilities may refer to a paired creature, the creature another
* creature is paired with, or whether a creature is paired. An unpaired
* creature is one that is not paired.
*
* 702.94c When the soulbond ability resolves, if either object that would be
* paired is no longer a creature, no longer on the battlefield, or no longer
* under the control of the player who controls the soulbond ability, neither
* object becomes paired.
*
* 702.94d A creature can be paired with only one other creature.
*
* 702.94e A paired creature becomes unpaired if any of the following occur:
* another player gains control of it or the creature its paired with; it or
* the creature its paired with stops being a creature; or it or the creature
* its paired with leaves the battlefield.
*
* @author LevelX2
*/
public class SoulbondAbility extends StaticAbility implements MageSingleton {
public class SoulbondAbility extends EntersBattlefieldTriggeredAbility {
private static final SoulbondAbility fINSTANCE = new SoulbondAbility();
private Object readResolve() throws ObjectStreamException {
return fINSTANCE;
public SoulbondAbility() {
super(new SoulboundEntersSelfEffect(), true);
this.addSubAbility(new SoulbondEntersOtherAbility());
}
public static SoulbondAbility getInstance() {
return fINSTANCE;
}
private SoulbondAbility() {
super(Zone.BATTLEFIELD, null);
public SoulbondAbility(SoulbondAbility ability) {
super(ability);
}
@Override
@ -57,9 +96,200 @@ public class SoulbondAbility extends StaticAbility implements MageSingleton {
return "Soulbond <i>(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.)</i>";
}
@Override
public boolean checkInterveningIfClause(Game game) {
// if you control both this creature and another creature and both are unpaired
boolean self = false;
boolean other = false;
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(getControllerId())) {
if (permanent.getCardType().contains(CardType.CREATURE)) {
if (permanent.getId().equals(getSourceId())) {
if (permanent.getControllerId().equals(getControllerId())) {
self = true;
if (other) {
return true;
}
} else {
return false;
}
} else if (permanent.getPairedCard() == null) {
other = true;
if (self) {
return true;
}
}
}
}
return false;
}
@Override
public SoulbondAbility copy() {
return fINSTANCE;
return new SoulbondAbility(this);
}
}
// When this creature enters the battlefield, if you control both this creature and another creature and both are unpaired, you may pair
// this creature with another unpaired creature you control for as long as both remain creatures on the battlefield under your control
class SoulboundEntersSelfEffect extends OneShotEffect {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another not paired creature you control");
static {
filter.add(new AnotherPredicate());
filter.add(Predicates.not(new PairedPredicate()));
}
public SoulboundEntersSelfEffect() {
super(Outcome.Benefit);
}
public SoulboundEntersSelfEffect(final SoulboundEntersSelfEffect effect) {
super(effect);
}
@Override
public SoulboundEntersSelfEffect copy() {
return new SoulboundEntersSelfEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null && permanent.getCardType().contains(CardType.CREATURE)) {
Player controller = game.getPlayer(permanent.getControllerId());
if (controller != null) {
TargetControlledPermanent target = new TargetControlledPermanent(filter);
target.setNotTarget(true);
if (target.canChoose(permanent.getId(), controller.getId(), game)) {
if (controller.choose(Outcome.Benefit, target, permanent.getId(), game)) {
Permanent chosen = game.getPermanent(target.getFirstTarget());
if (chosen != null) {
chosen.setPairedCard(new MageObjectReference(permanent, game));
permanent.setPairedCard(new MageObjectReference(chosen, game));
if (!game.isSimulation()) {
game.informPlayers(controller.getLogName() + " soulbonds " + permanent.getLogName() + " with " + chosen.getLogName());
}
}
}
}
}
return true;
}
return false;
}
}
/**
* Whenever another creature enters the battlefield under your control, if you
* control both that creature and this one and both are unpaired, you may pair
* that creature with this creature for as long as both remain creatures on the
* battlefield under your control.
*
*/
class SoulbondEntersOtherAbility extends EntersBattlefieldAllTriggeredAbility {
private final static FilterCreaturePermanent soulbondFilter = new FilterCreaturePermanent();
static {
soulbondFilter.add(Predicates.not(new PairedPredicate()));
soulbondFilter.add(new ControllerPredicate(TargetController.YOU));
soulbondFilter.add(new AnotherPredicate());
}
public SoulbondEntersOtherAbility() {
super(Zone.BATTLEFIELD, new SoulboundEntersOtherEffect(), soulbondFilter, true, SetTargetPointer.PERMANENT, "");
setRuleVisible(false);
}
public SoulbondEntersOtherAbility(SoulbondEntersOtherAbility ability) {
super(ability);
}
@Override
public String getRule() {
return "Soulbond <i>(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.)</i>";
}
@Override
public boolean checkInterveningIfClause(Game game) {
// if you control both this creature and another creature and both are unpaired
if (game.getBattlefield().countAll(filter, getControllerId(), game) > 0) {
Permanent sourcePermanent = game.getPermanent(getSourceId());
if (sourcePermanent != null && sourcePermanent.getControllerId().equals(getControllerId()) && sourcePermanent.getPairedCard() == null) {
return true;
}
}
return false;
}
@Override
public SoulbondEntersOtherAbility copy() {
return new SoulbondEntersOtherAbility(this);
}
}
class SoulboundEntersOtherEffect extends OneShotEffect {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another not paired creature you control");
static {
filter.add(new AnotherPredicate());
filter.add(Predicates.not(new PairedPredicate()));
}
public SoulboundEntersOtherEffect() {
super(Outcome.Benefit);
}
public SoulboundEntersOtherEffect(final SoulboundEntersOtherEffect effect) {
super(effect);
}
@Override
public SoulboundEntersOtherEffect copy() {
return new SoulboundEntersOtherEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null && permanent.getPairedCard() == null
&& permanent.getCardType().contains(CardType.CREATURE)) {
Player controller = game.getPlayer(permanent.getControllerId());
if (controller != null) {
Permanent enteringPermanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (enteringPermanent != null && enteringPermanent.getCardType().contains(CardType.CREATURE) && enteringPermanent.getPairedCard() == null) {
enteringPermanent.setPairedCard(new MageObjectReference(permanent, game));
permanent.setPairedCard(new MageObjectReference(enteringPermanent, game));
if (!game.isSimulation()) {
game.informPlayers(controller.getLogName() + " soulbonds " + permanent.getLogName() + " with " + enteringPermanent.getLogName());
}
}
}
return true;
}
return false;
}
}
class PairedPredicate implements Predicate<Permanent> {
@Override
public boolean apply(Permanent input, Game game) {
return input.getPairedCard() != null;
}
@Override
public String toString() {
return "Paired";
}
}

View file

@ -131,7 +131,6 @@ import mage.watchers.common.DamageDoneWatcher;
import mage.watchers.common.MorbidWatcher;
import mage.watchers.common.PlayerDamagedBySourceWatcher;
import mage.watchers.common.PlayerLostLifeWatcher;
import mage.watchers.common.SoulbondWatcher;
import org.apache.log4j.Logger;
public abstract class GameImpl implements Game, Serializable {
@ -1007,7 +1006,6 @@ public abstract class GameImpl implements Game, Serializable {
}
watchers.add(new MorbidWatcher());
watchers.add(new CastSpellLastTurnWatcher());
watchers.add(new SoulbondWatcher());
watchers.add(new PlayerLostLifeWatcher());
watchers.add(new BlockedAttackerWatcher());
watchers.add(new DamageDoneWatcher());
@ -1687,7 +1685,7 @@ public abstract class GameImpl implements Game, Serializable {
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());
Permanent paired = perm.getPairedCard().getPermanent(this);
if (paired == null || !perm.getControllerId().equals(paired.getControllerId()) || paired.getPairedCard() == null) {
perm.setPairedCard(null);
if (paired != null) {
@ -1698,7 +1696,7 @@ public abstract class GameImpl implements Game, Serializable {
}
} else if (perm.getPairedCard() != null) {
//702.93e.: ...stops being a creature
Permanent paired = getPermanent(perm.getPairedCard());
Permanent paired = perm.getPairedCard().getPermanent(this);
perm.setPairedCard(null);
if (paired != null) {
paired.setPairedCard(null);

View file

@ -327,14 +327,14 @@ public interface Permanent extends Card, Controllable {
*
* @param pairedCard
*/
void setPairedCard(UUID pairedCard);
void setPairedCard(MageObjectReference pairedCard);
/**
* Gets paired card. Can return null.
*
* @return
*/
UUID getPairedCard();
MageObjectReference getPairedCard();
/**
* Makes permanent paired with no other permanent.

View file

@ -116,7 +116,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected HashSet<MageObjectReference> dealtDamageByThisTurn;
protected UUID attachedTo;
protected int attachedToZoneChangeCounter;
protected UUID pairedCard;
protected MageObjectReference pairedPermanent;
protected Counters counters;
protected List<Counter> markedDamage;
protected int timesLoyaltyUsed = 0;
@ -179,7 +179,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.transformed = permanent.transformed;
this.monstrous = permanent.monstrous;
this.renowned = permanent.renowned;
this.pairedCard = permanent.pairedCard;
this.pairedPermanent = permanent.pairedPermanent;
this.timesLoyaltyUsed = permanent.timesLoyaltyUsed;
this.morphed = permanent.morphed;
@ -1318,18 +1318,18 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
}
@Override
public void setPairedCard(UUID pairedCard) {
this.pairedCard = pairedCard;
public void setPairedCard(MageObjectReference pairedCard) {
this.pairedPermanent = pairedCard;
}
@Override
public UUID getPairedCard() {
return pairedCard;
public MageObjectReference getPairedCard() {
return pairedPermanent;
}
@Override
public void clearPairedCard() {
this.pairedCard = null;
this.pairedPermanent = null;
}
@Override

View file

@ -839,7 +839,7 @@ public abstract class PlayerImpl implements Player, Serializable {
}
if (permanent.getPairedCard() != null) {
Permanent pairedCard = game.getPermanent(permanent.getPairedCard());
Permanent pairedCard = permanent.getPairedCard().getPermanent(game);
if (pairedCard != null) {
pairedCard.clearPairedCard();
}

View file

@ -1,145 +0,0 @@
/*
* 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.abilities.keyword.SoulbondAbility;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.WatcherScope;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.permanent.AnotherPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetControlledPermanent;
import mage.watchers.Watcher;
/**
* Reacts on various events to pair or unpair creatures on the battlefield.
*
* @author noxx
*/
public class SoulbondWatcher extends Watcher {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("another not paired creature you control");
static {
filter.add(new AnotherPredicate());
filter.add(Predicates.not(new PairedPredicate()));
}
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.ENTERS_THE_BATTLEFIELD) {
Permanent permanent = game.getPermanent(event.getTargetId());
if (permanent != null && permanent.getCardType().contains(CardType.CREATURE)) {
if (permanent.getAbilities().contains(SoulbondAbility.getInstance())) {
Player controller = game.getPlayer(permanent.getControllerId());
if (controller != null) {
Cards cards = new CardsImpl();
cards.add(permanent);
controller.lookAtCards("Soulbond", cards, game);
if (controller.chooseUse(Outcome.Benefit, "Use Soulbond?", null, game)) {
TargetControlledPermanent target = new TargetControlledPermanent(filter);
target.setNotTarget(true);
if (target.canChoose(permanent.getId(), controller.getId(), game)) {
if (controller.choose(Outcome.Benefit, target, permanent.getId(), game)) {
Permanent chosen = game.getPermanent(target.getFirstTarget());
if (chosen != null) {
chosen.setPairedCard(permanent.getId());
permanent.setPairedCard(chosen.getId());
if (!game.isSimulation()) {
game.informPlayers(new StringBuilder(controller.getLogName()).append(" souldbonds ").append(permanent.getLogName()).append(" with ").append(chosen.getName()).toString());
}
}
}
}
}
}
}
// 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();
cards.add(chosen);
controller.lookAtCards("Soulbond", cards, game);
if (controller.chooseUse(Outcome.Benefit, "Use Soulbond for recent " + permanent.getLogName() + "?", SoulbondAbility.getInstance(), game)) {
chosen.setPairedCard(permanent.getId());
permanent.setPairedCard(chosen.getId());
if (!game.isSimulation()) {
game.informPlayers(new StringBuilder(controller.getLogName()).append(" souldbonds ").append(permanent.getLogName()).append(" with ").append(chosen.getName()).toString());
}
break;
}
}
}
}
}
}
}
}
@Override
public SoulbondWatcher copy() {
return new SoulbondWatcher(this);
}
}
class PairedPredicate implements Predicate<Permanent> {
@Override
public boolean apply(Permanent input, Game game) {
return input.getPairedCard() != null;
}
@Override
public String toString() {
return "Paired";
}
}