* Some rework/clean up of the PlayFromNotOwnHandZone effects (fixes #6580). Some added tests.

This commit is contained in:
LevelX2 2020-06-18 01:19:23 +02:00
parent 8e4d966ff3
commit 85709c0a16
29 changed files with 465 additions and 797 deletions

View file

@ -105,7 +105,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
return ActivationStatus.getFalse();
}
}
if (costs.canPay(this, sourceId, controllerId, game)) {
if (costs.canPay(this, sourceId, playerId, game)) {
if (getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
SplitCard splitCard = (SplitCard) game.getCard(getSourceId());
if (splitCard != null) {

View file

@ -7,6 +7,9 @@ import mage.constants.*;
import mage.game.Game;
import java.util.UUID;
import mage.cards.SplitCard;
import mage.cards.SplitCardHalf;
import mage.players.Player;
/**
* @author BetaSteward_at_googlemail.com
@ -42,7 +45,14 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
}
/**
* Helper to check that affectedAbility is compatible for alternative cast modifications by setCastSourceIdWithAlternateMana
* Helper to check that affectedAbility is compatible for alternative cast
* modifications by setCastSourceIdWithAlternateMana
*
* @param cardToCheck
* @param affectedAbilityToCheck
* @param playerToCheck
* @param source
* @return
*/
public boolean isAbilityAppliedForAlternateCast(Card cardToCheck, Ability affectedAbilityToCheck, UUID playerToCheck, Ability source) {
return cardToCheck != null
@ -52,4 +62,35 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
&& (affectedAbilityToCheck.getAbilityType() == AbilityType.SPELL
|| affectedAbilityToCheck.getAbilityType() == AbilityType.PLAY_LAND);
}
/**
* Internal method to do the neccessary to allow the card from objectId to be cast or played (if it's a land) without paying any mana.
* Additional costs (like sacrificing or discarding) have still to be payed.
* Checks if the card is of the correct type or in the correct zone have to be done before.
*
* @param objectId sourceId of the card to play
* @param source source ability that allows this effect
* @param affectedControllerId player allowed to play the card
* @param game
* @return
*/
protected boolean allowCardToPlayWithoutMana(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
Player player = game.getPlayer(affectedControllerId);
Card card = game.getCard(objectId);
if (card == null || player == null) {
return false;
}
if (!card.isLand()) {
if (card instanceof SplitCard) {
SplitCardHalf leftCard = ((SplitCard) card).getLeftHalfCard();
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts());
SplitCardHalf rightCard = ((SplitCard) card).getRightHalfCard();
player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts());
} else {
player.setCastSourceIdWithAlternateMana(objectId, null, card.getSpellAbility().getCosts());
}
}
return true;
}
}

View file

@ -1,15 +1,23 @@
package mage.abilities.effects.common.asthought;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.ContinuousEffect;
import mage.cards.Card;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
import mage.target.targetpointer.FixedTargets;
import mage.util.CardUtil;
/**
*
@ -19,6 +27,7 @@ public class PlayFromNotOwnHandZoneTargetEffect extends AsThoughEffectImpl {
private final Zone fromZone;
private final TargetController allowedCaster;
private final boolean withoutMana;
public PlayFromNotOwnHandZoneTargetEffect() {
this(Duration.EndOfTurn);
@ -33,15 +42,21 @@ public class PlayFromNotOwnHandZoneTargetEffect extends AsThoughEffectImpl {
}
public PlayFromNotOwnHandZoneTargetEffect(Zone fromZone, TargetController allowedCaster, Duration duration) {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, duration, Outcome.Benefit);
this(fromZone, allowedCaster, duration, false);
}
public PlayFromNotOwnHandZoneTargetEffect(Zone fromZone, TargetController allowedCaster, Duration duration, boolean withoutMana) {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, duration, withoutMana ? Outcome.PlayForFree : Outcome.PutCardInPlay);
this.fromZone = fromZone;
this.allowedCaster = allowedCaster;
this.withoutMana = withoutMana;
}
public PlayFromNotOwnHandZoneTargetEffect(final PlayFromNotOwnHandZoneTargetEffect effect) {
super(effect);
this.fromZone = effect.fromZone;
this.allowedCaster = effect.allowedCaster;
this.withoutMana = effect.withoutMana;
}
@Override
@ -56,27 +71,91 @@ public class PlayFromNotOwnHandZoneTargetEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
return applies(objectId, null, source, game, affectedControllerId);
}
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
List<UUID> targets = getTargetPointer().getTargets(game, source);
if (targets.isEmpty()) {
this.discard();
return false;
}
switch (allowedCaster) {
case YOU:
if (affectedControllerId != source.getControllerId()) {
if (playerId != source.getControllerId()) {
return false;
}
break;
case OPPONENT:
if (!game.getOpponents(source.getControllerId()).contains(affectedControllerId)) {
if (!game.getOpponents(source.getControllerId()).contains(playerId)) {
return false;
}
break;
case ANY:
break;
}
List<UUID> targets = getTargetPointer().getTargets(game, source);
if (targets.isEmpty()) {
this.discard();
UUID objectIdToCast = CardUtil.getMainCardId(game, objectId);
if (targets.contains(objectIdToCast)
&& playerId.equals(source.getControllerId())
&& game.getState().getZone(objectId).match(fromZone)) {
if (withoutMana) {
if (affectedAbility != null) {
objectIdToCast = affectedAbility.getSourceId();
}
return allowCardToPlayWithoutMana(objectIdToCast, source, playerId, game);
}
return true;
}
return false;
}
public static boolean exileAndPlayFromExile(Game game, Ability source, Card card, TargetController allowedCaster, Duration duration, boolean withoutMana) {
if (card == null) {
return true;
}
Set<Card> cards = new HashSet<>();
cards.add(card);
return exileAndPlayFromExile(game, source, cards, allowedCaster, duration, withoutMana);
}
/**
* Exiles the cards and let the allowed player play them from exile for the given duration
*
* @param game
* @param source
* @param cards
* @param allowedCaster
* @param duration
* @param withoutMana
* @return
*/
public static boolean exileAndPlayFromExile(Game game, Ability source, Set<Card> cards, TargetController allowedCaster, Duration duration, boolean withoutMana) {
if (cards == null || cards.isEmpty()) {
return true;
}
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = source.getSourceObject(game);
if (controller == null || sourceObject == null) {
return false;
}
return targets.contains(objectId)
&& affectedControllerId.equals(source.getControllerId())
&& game.getState().getZone(objectId).match(fromZone);
UUID exileId = CardUtil.getExileZoneId(
controller.getId().toString()
+ "-" + game.getState().getTurnNum()
+ "-" + sourceObject.getIdName(), game
);
String exileName = sourceObject.getIdName() + " free play"
+ (Duration.EndOfTurn.equals(duration) ? " on turn " + game.getState().getTurnNum():"")
+ " for " + controller.getName();
if (Duration.EndOfTurn.equals(duration)) {
game.getExile().createZone(exileId, exileName).setCleanupOnEndTurn(true);
}
if (!controller.moveCardsToExile(cards, source, game, true, exileId, exileName)) {
return false;
}
ContinuousEffect effect = new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, allowedCaster, duration, withoutMana);
effect.setTargetPointer(new FixedTargets(cards, game));
game.addEffect(effect, source);
return true;
}
}

View file

@ -29,6 +29,8 @@ public interface Card extends MageObject {
/**
* For cards: return all basic and dynamic abilities
* For permanents: return all basic and dynamic abilities
* @param game
* @return
*/
Abilities<Ability> getAbilities(Game game);

View file

@ -27,6 +27,12 @@ public class FixedTarget implements TargetPointer {
this(mor.getSourceId(), mor.getZoneChangeCounter());
}
/**
* Target counter is immediatly initialised with current zoneChangeCounter value from the GameState
* Sets fixed the currect zone chnage counter
* @param card used to get the objectId
* @param game
*/
public FixedTarget(Card card, Game game) {
this.targetId = card.getId();
this.zoneChangeCounter = card.getZoneChangeCounter(game);