Merge branch 'master' into case-of-the-pilfered-proof

This commit is contained in:
xenohedron 2024-03-18 01:24:26 -04:00 committed by GitHub
commit ca45c06f71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1595 changed files with 24451 additions and 9637 deletions

View file

@ -34,8 +34,10 @@ public enum MageIdentifier {
SerraParagonWatcher,
OneWithTheMultiverseWatcher("Without paying manacost"),
JohannApprenticeSorcererWatcher,
AssembleThePlayersWatcher,
KaghaShadowArchdruidWatcher,
CourtOfLocthwainWatcher("Without paying manacost"),
LaraCroftTombRaiderWatcher,
// ----------------------------//
// alternate casts //
@ -68,7 +70,9 @@ public enum MageIdentifier {
SqueeDubiousMonarchAlternateCast,
WorldheartPhoenixAlternateCast,
XandersPactAlternateCast,
TheTombOfAclazotzWatcher;
TheTombOfAclazotzWatcher,
MeTheImmortalAlternateCast;
/**
* Additional text if there is need to differentiate two very similar effects

View file

@ -1,16 +1,14 @@
package mage;
import java.io.Serializable;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
@FunctionalInterface
public interface MageItem extends Serializable {
UUID getId();
}

View file

@ -34,6 +34,15 @@ public interface MageObject extends MageItem, Serializable, Copyable<MageObject>
void setImageNumber(Integer imageNumber);
/**
* Get image file name
* - empty for default name from a card
* - non-empty for face down objects like Morph (GUI show empty name, but image must show some image)
*/
String getImageFileName();
void setImageFileName(String imageFile);
String getName();
/**

View file

@ -36,6 +36,7 @@ public abstract class MageObjectImpl implements MageObject {
private String expansionSetCode = "";
private String cardNumber = "";
private String imageFileName = "";
private int imageNumber = 0;
protected List<SuperType> supertype = new ArrayList<>();
@ -77,6 +78,7 @@ public abstract class MageObjectImpl implements MageObject {
frameStyle = object.frameStyle;
expansionSetCode = object.expansionSetCode;
cardNumber = object.cardNumber;
imageFileName = object.imageFileName;
imageNumber = object.imageNumber;
power = object.power.copy();
toughness = object.toughness.copy();
@ -266,6 +268,16 @@ public abstract class MageObjectImpl implements MageObject {
this.cardNumber = cardNumber;
}
@Override
public String getImageFileName() {
return imageFileName;
}
@Override
public void setImageFileName(String imageFileName) {
this.imageFileName = imageFileName;
}
@Override
public Integer getImageNumber() {
return imageNumber;

View file

@ -35,15 +35,6 @@ import java.util.UUID;
*/
public interface Ability extends Controllable, Serializable {
/**
* Gets the globally unique id of the ability contained within the game.
*
* @return A {@link java.util.UUID} which the game will use to store and
* retrieve the exact instance of this ability.
*/
@Override
UUID getId();
/**
* Assigns a new {@link java.util.UUID}
*
@ -71,14 +62,6 @@ public interface Ability extends Controllable, Serializable {
*/
AbilityType getAbilityType();
/**
* Gets the id of the player in control of this ability.
*
* @return The {@link java.util.UUID} of the controlling player.
*/
@Override
UUID getControllerId();
/**
* Sets the id of the controller of this ability.
*
@ -228,6 +211,8 @@ public interface Ability extends Controllable, Serializable {
* Retrieves the {@link Target} located at the 0th index in the
* {@link Targets}. A call to the method is equivalent to
* {@link #getTargets()}.get(0).getFirstTarget().
* <p>
* Warning, if you effect uses target pointers then it must search getTargetPointer too
*
* @return The {@link java.util.UUID} of the first target within the targets
* list.

View file

@ -428,6 +428,7 @@ public abstract class AbilityImpl implements Ability {
case MORE_THAN_MEETS_THE_EYE:
case BESTOW:
case MORPH:
case DISGUISE:
// from Snapcaster Mage:
// If you cast a spell from a graveyard using its flashback ability, you can't pay other alternative costs
// (such as that of Foil). (2018-12-07)
@ -649,6 +650,11 @@ public abstract class AbilityImpl implements Ability {
return controllerId;
}
@Override
public UUID getControllerOrOwnerId() {
return getControllerId();
}
@Override
public void setControllerId(UUID controllerId) {
this.controllerId = controllerId;

View file

@ -148,7 +148,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
if (!approvingObjects.isEmpty()) {
Card card = game.getCard(sourceId);
Zone zone = game.getState().getZone(sourceId);
if(card != null && card.isOwnedBy(playerId) && Zone.HAND.match(zone)) {
if (card != null && card.isOwnedBy(playerId) && Zone.HAND.match(zone)) {
// Regular casting, to be an alternative to the AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE from hand (e.g. One with the Multiverse):
approvingObjects.add(new ApprovingObject(this, game));
}
@ -160,8 +160,8 @@ public class SpellAbility extends ActivatedAbilityImpl {
Player player = game.getPlayer(playerId);
if (player != null
&& player.getCastSourceIdWithAlternateMana()
.getOrDefault(getSourceId(), Collections.emptySet())
.contains(MageIdentifier.Default)
.getOrDefault(getSourceId(), Collections.emptySet())
.contains(MageIdentifier.Default)
) {
return ActivationStatus.getFalse();
}
@ -181,11 +181,10 @@ public class SpellAbility extends ActivatedAbilityImpl {
}
return ActivationStatus.getFalse();
} else {
if(canChooseTarget(game, playerId)) {
if(approvingObjects == null || approvingObjects.isEmpty()) {
if (canChooseTarget(game, playerId)) {
if (approvingObjects == null || approvingObjects.isEmpty()) {
return ActivationStatus.withoutApprovingObject(true);
}
else {
} else {
return new ActivationStatus(approvingObjects);
}
}
@ -308,22 +307,27 @@ public class SpellAbility extends ActivatedAbilityImpl {
}
/**
* Returns a card object with the spell characteristics like color, types,
* Returns combined card object with the spell characteristics like color, types,
* subtypes etc. E.g. if you cast a Bestow card as enchantment, the
* characteristics don't include the creature type.
* <p>
* Warning, it's not a real card - use it as a blueprint or characteristics searching
*
* @param game
* @return card object with the spell characteristics
*/
public Card getCharacteristics(Game game) {
Card spellCharacteristics = game.getSpell(this.getId());
if (spellCharacteristics == null) {
// playable check (without put to stack)
spellCharacteristics = game.getCard(this.getSourceId());
}
if (spellCharacteristics != null) {
if (getSpellAbilityCastMode() != SpellAbilityCastMode.NORMAL) {
spellCharacteristics = getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(spellCharacteristics, this);
// transform characteristics (morph, transform, bestow, etc)
spellCharacteristics = getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(spellCharacteristics, this, game);
}
spellCharacteristics = spellCharacteristics.copy();
}
return spellCharacteristics;
}

View file

@ -0,0 +1,40 @@
package mage.abilities.abilityword;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.common.TapTargetCost;
import mage.abilities.effects.Effect;
import mage.constants.AbilityWord;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.permanent.TappedPredicate;
import mage.target.common.TargetControlledPermanent;
/**
* @author xenohedron
*/
public class CohortAbility extends SimpleActivatedAbility {
private static final FilterControlledPermanent filter = new FilterControlledPermanent(SubType.ALLY, "an untapped Ally you control");
static {
filter.add(TappedPredicate.UNTAPPED);
}
public CohortAbility(Effect effect) {
super(Zone.BATTLEFIELD, effect, new TapSourceCost());
this.addCost(new TapTargetCost(new TargetControlledPermanent(filter)));
this.setAbilityWord(AbilityWord.COHORT);
}
protected CohortAbility(final CohortAbility ability) {
super(ability);
}
@Override
public CohortAbility copy() {
return new CohortAbility(this);
}
}

View file

@ -13,6 +13,7 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/**
* @author LevelX2
@ -42,7 +43,7 @@ public class AttackedByCreatureTriggeredAbility extends TriggeredAbilityImpl {
super(zone, effect, optional);
this.setTargetPointer = setTargetPointer;
this.filter = filter;
setTriggerPhrase("Whenever " + filter.getMessage() + " attacks you, ");
setTriggerPhrase("Whenever " + CardUtil.addArticle(filter.getMessage()) + " attacks you, ");
}
protected AttackedByCreatureTriggeredAbility(final AttackedByCreatureTriggeredAbility ability) {

View file

@ -11,6 +11,7 @@ import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTargets;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@ -91,7 +92,7 @@ public class AttacksWithCreaturesTriggeredAbility extends TriggeredAbilityImpl {
}
getEffects().setValue(VALUEKEY_NUMBER_ATTACKERS, attackers.size());
if (setTargetPointer) {
getEffects().setTargetPointer(new FixedTargets(attackers, game));
getEffects().setTargetPointer(new FixedTargets(new ArrayList<>(attackers), game));
}
return true;
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
@ -14,20 +13,29 @@ import mage.game.events.GameEvent;
public class CounterRemovedFromSourceWhileExiledTriggeredAbility extends TriggeredAbilityImpl {
private final CounterType counterType;
private final boolean onlyController;
public CounterRemovedFromSourceWhileExiledTriggeredAbility(CounterType counterType, Effect effect) {
this(counterType, effect, false);
}
public CounterRemovedFromSourceWhileExiledTriggeredAbility(CounterType counterType, Effect effect, boolean optional) {
this(counterType, effect, optional, false);
}
public CounterRemovedFromSourceWhileExiledTriggeredAbility(CounterType counterType, Effect effect, boolean optional, boolean onlyController) {
super(Zone.EXILED, effect, optional);
this.counterType = counterType;
setTriggerPhrase("Whenever a " + counterType.getName() + " counter is removed from {this} while it's exiled, ");
this.onlyController = onlyController;
setTriggerPhrase("Whenever " + (
onlyController ? ("you remove a " + counterType.getName() + " counter") : ("a " + counterType.getName() + " counter is removed")
) + " from {this} while it's exiled, ");
}
private CounterRemovedFromSourceWhileExiledTriggeredAbility(final CounterRemovedFromSourceWhileExiledTriggeredAbility ability) {
super(ability);
this.counterType = ability.counterType;
this.onlyController = ability.onlyController;
}
@Override
@ -42,6 +50,8 @@ public class CounterRemovedFromSourceWhileExiledTriggeredAbility extends Trigger
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return event.getData().equals(counterType.getName()) && event.getTargetId().equals(this.getSourceId());
return event.getData().equals(counterType.getName())
&& event.getTargetId().equals(this.getSourceId())
&& (!onlyController || event.getPlayerId().equals(getControllerId()));
}
}

View file

@ -1,4 +1,3 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
@ -12,6 +11,7 @@ import mage.game.events.DamagedPlayerEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/**
* @author LevelX2
@ -43,7 +43,7 @@ public class DealsDamageToAPlayerAllTriggeredAbility extends TriggeredAbilityImp
this.onlyCombat = onlyCombat;
this.affectsDefendingPlayer = affectsDefendingPlayer;
this.targetController = targetController;
setTriggerPhrase("Whenever " + filter.getMessage() + " deals " + (onlyCombat ? "combat " : "") + "damage to "
setTriggerPhrase("Whenever " + CardUtil.addArticle(filter.getMessage()) + " deals " + (onlyCombat ? "combat " : "") + "damage to "
+ (targetController == TargetController.OPPONENT ? "an opponent" : "a player") + ", ");
}

View file

@ -15,7 +15,11 @@ public class DrawCardOpponentTriggeredAbility extends TriggeredAbilityImpl {
private final boolean setTargetPointer;
public DrawCardOpponentTriggeredAbility(Effect effect, boolean optional, boolean setTargetPointer) {
super(Zone.BATTLEFIELD, effect, optional);
this(Zone.BATTLEFIELD, effect, optional, setTargetPointer);
}
public DrawCardOpponentTriggeredAbility(Zone zone, Effect effect, boolean optional, boolean setTargetPointer) {
super(zone, effect, optional);
this.setTargetPointer = setTargetPointer;
setTriggerPhrase("Whenever an opponent draws a card, ");
}

View file

@ -6,15 +6,12 @@ import mage.abilities.effects.Effect;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.constants.TargetController;
import mage.constants.WatcherScope;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.util.CardUtil;
import mage.watchers.Watcher;
import java.util.*;
import mage.watchers.common.CardsDrawnThisTurnWatcher;
/**
* @author TheElk801
@ -37,7 +34,6 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl {
public DrawNthCardTriggeredAbility(Zone zone, Effect effect, boolean optional, TargetController targetController, int cardNumber) {
super(zone, effect, optional);
this.addWatcher(new DrawCardWatcher());
this.targetController = targetController;
this.cardNumber = cardNumber;
this.addHint(hint);
@ -77,7 +73,8 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl {
default:
throw new IllegalArgumentException("TargetController " + targetController + " not supported");
}
return DrawCardWatcher.checkEvent(event.getPlayerId(), event, game, cardNumber);
CardsDrawnThisTurnWatcher watcher = game.getState().getWatcher(CardsDrawnThisTurnWatcher.class);
return watcher != null && watcher.getCardsDrawnThisTurn(event.getPlayerId()) == cardNumber;
}
public String generateTriggerPhrase() {
@ -98,35 +95,3 @@ public class DrawNthCardTriggeredAbility extends TriggeredAbilityImpl {
return new DrawNthCardTriggeredAbility(this);
}
}
class DrawCardWatcher extends Watcher {
private final Map<UUID, List<UUID>> drawMap = new HashMap<>();
DrawCardWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() != GameEvent.EventType.DREW_CARD) {
return;
}
if (!drawMap.containsKey(event.getPlayerId())) {
drawMap.putIfAbsent(event.getPlayerId(), new ArrayList<>());
}
drawMap.get(event.getPlayerId()).add(event.getId());
}
@Override
public void reset() {
super.reset();
drawMap.clear();
}
static boolean checkEvent(UUID playerId, GameEvent event, Game game, int cardNumber) {
Map<UUID, List<UUID>> drawMap = game.getState().getWatcher(DrawCardWatcher.class).drawMap;
return drawMap.containsKey(playerId) && Objects.equals(drawMap.get(playerId).size(), cardNumber) && event.getId().equals(drawMap.get(playerId).get(cardNumber - 1));
}
}

View file

@ -25,7 +25,7 @@ public class OneOrMoreCountersAddedTriggeredAbility extends TriggeredAbilityImpl
setTriggerPhrase("Whenever one or more " + counterType.getName() + " counters are put on {this}, ");
}
private OneOrMoreCountersAddedTriggeredAbility(final OneOrMoreCountersAddedTriggeredAbility ability) {
protected OneOrMoreCountersAddedTriggeredAbility(final OneOrMoreCountersAddedTriggeredAbility ability) {
super(ability);
this.counterType = ability.counterType;
}

View file

@ -20,7 +20,7 @@ public class ScryTriggeredAbility extends TriggeredAbilityImpl {
}
public ScryTriggeredAbility(Zone zone, Effect effect, boolean optional) {
super(zone, effect, false);
super(zone, effect, optional);
setTriggerPhrase("Whenever you scry, ");
}

View file

@ -0,0 +1,83 @@
package mage.abilities.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
/**
* @author PurpleCrowbar
*/
public class SkipExtraTurnsAbility extends SimpleStaticAbility {
private final boolean onlyOpponents;
public SkipExtraTurnsAbility() {
this(false);
}
public SkipExtraTurnsAbility(boolean onlyOpponents) {
super(Zone.BATTLEFIELD, new SkipExtraTurnsEffect(onlyOpponents));
this.onlyOpponents = onlyOpponents;
}
private SkipExtraTurnsAbility(final SkipExtraTurnsAbility ability) {
super(ability);
this.onlyOpponents = ability.onlyOpponents;
}
@Override
public SkipExtraTurnsAbility copy() {
return new SkipExtraTurnsAbility(this);
}
@Override
public String getRule() {
return "If a" + (onlyOpponents ? "n opponent" : " player") + " would begin an extra turn, that player skips that turn instead.";
}
}
class SkipExtraTurnsEffect extends ReplacementEffectImpl {
private final boolean onlyOpponents;
SkipExtraTurnsEffect(boolean onlyOpponents) {
super(Duration.WhileOnBattlefield, Outcome.Detriment);
this.onlyOpponents = onlyOpponents;
}
private SkipExtraTurnsEffect(final SkipExtraTurnsEffect effect) {
super(effect);
this.onlyOpponents = effect.onlyOpponents;
}
@Override
public SkipExtraTurnsEffect copy() {
return new SkipExtraTurnsEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Player player = game.getPlayer(event.getPlayerId());
MageObject sourceObject = game.getObject(source);
if (player != null && sourceObject != null) {
game.informPlayers(sourceObject.getLogName() + ": Extra turn of " + player.getLogName() + " skipped");
}
return true;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.EXTRA_TURN;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return !onlyOpponents || game.getPlayer(source.getControllerId()).hasOpponent(event.getPlayerId(), game);
}
}

View file

@ -0,0 +1,45 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
* @author PurpleCrowbar
*/
public class SurveilTriggeredAbility extends TriggeredAbilityImpl {
public SurveilTriggeredAbility(Effect effect) {
this(Zone.BATTLEFIELD, effect);
}
public SurveilTriggeredAbility(Zone zone, Effect effect) {
super(zone, effect);
setTriggerPhrase("Whenever you surveil, " + (zone == Zone.GRAVEYARD ? "if {this} is in your graveyard, " : ""));
}
private SurveilTriggeredAbility(final SurveilTriggeredAbility ability) {
super(ability);
}
@Override
public SurveilTriggeredAbility copy() {
return new SurveilTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.SURVEILED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (isControlledBy(event.getPlayerId())) {
this.getEffects().setValue("amount", event.getAmount());
return true;
}
return false;
}
}

View file

@ -29,8 +29,8 @@ public class TurnFaceUpAbility extends SpecialAction {
this.addCost(costs);
this.usesStack = false;
this.abilityType = AbilityType.SPECIAL_ACTION;
this.setRuleVisible(false); // will be made visible only to controller in CardView
this.setWorksFaceDown(true);
this.setRuleVisible(false); // hide in face up, but show in face down view (it will be enabled as default ability)
}
protected TurnFaceUpAbility(final TurnFaceUpAbility ability) {

View file

@ -1,11 +1,12 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.counters.CounterType;
import mage.game.Game;
import mage.watchers.common.PlayerAttackedStepWatcher;
import mage.game.combat.CombatGroup;
import java.util.Objects;
/**
* A condition which checks whether any players being attacked are poisoned
@ -14,17 +15,23 @@ import mage.watchers.common.PlayerAttackedStepWatcher;
* @author alexander-novo
*/
public enum AttackedPlayersPoisonedCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
return game.getCombat().getDefenders().stream().map(defender -> game.getPlayer(defender))
.anyMatch(player -> player != null && player.getCounters().containsKey(CounterType.POISON));
return game.getCombat()
.getGroups()
.stream()
.map(CombatGroup::getDefenderId)
.filter(Objects::nonNull)
.distinct()
.map(game::getPlayer)
.filter(Objects::nonNull)
.anyMatch(player -> player.getCounters().containsKey(CounterType.POISON));
}
@Override
public String toString() {
return "one or more players being attacked are poisoned";
}
}
}

View file

@ -7,6 +7,10 @@ import mage.abilities.hint.Hint;
import mage.game.Game;
import mage.watchers.common.PermanentsSacrificedWatcher;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
/**
* @author TheElk801
*/
@ -20,11 +24,12 @@ public enum SacrificedArtifactThisTurnCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
return game
.getState()
.getWatcher(PermanentsSacrificedWatcher.class)
.getThisTurnSacrificedPermanents(source.getControllerId())
.stream()
return Optional.ofNullable(game
.getState()
.getWatcher(PermanentsSacrificedWatcher.class)
.getThisTurnSacrificedPermanents(source.getControllerId()))
.map(List::stream)
.orElseGet(Stream::empty)
.anyMatch(permanent -> permanent.isArtifact(game));
}

View file

@ -14,6 +14,7 @@ import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInYourGraveyard;
import mage.util.CardUtil;
import java.awt.*;
import java.util.Objects;
@ -25,16 +26,23 @@ import java.util.UUID;
public class CollectEvidenceCost extends CostImpl {
private final int amount;
private final boolean withSource;
public CollectEvidenceCost(int amount) {
this(amount, false);
}
public CollectEvidenceCost(int amount, boolean withSource) {
super();
this.amount = amount;
this.withSource = withSource;
this.text = "collect evidence " + amount;
}
private CollectEvidenceCost(final CollectEvidenceCost cost) {
super(cost);
this.amount = cost.amount;
this.withSource = cost.withSource;
}
@Override
@ -83,7 +91,11 @@ public class CollectEvidenceCost extends CostImpl {
.mapToInt(MageObject::getManaValue)
.sum() >= amount;
if (paid) {
player.moveCards(cards, Zone.EXILED, source, game);
if (withSource) {
player.moveCardsToExile(cards.getCards(game), source, game, true, CardUtil.getExileZoneId(game, source), CardUtil.getSourceName(game, source));
} else {
player.moveCards(cards, Zone.EXILED, source, game);
}
game.fireEvent(GameEvent.getEvent(
GameEvent.EventType.EVIDENCE_COLLECTED,
source.getSourceId(), source, source.getControllerId(), amount

View file

@ -18,6 +18,7 @@ import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author nantuko
@ -95,7 +96,7 @@ public class ExileFromGraveCost extends CostImpl {
CardUtil.getSourceName(game, source)
);
if (setTargetPointer) {
source.getEffects().setTargetPointer(new FixedTargets(cardsToExile, game));
source.getEffects().setTargetPointer(new FixedTargets(cardsToExile.getCards(game), game));
}
paid = true;
}

View file

@ -50,10 +50,10 @@ public class ConditionalAsThoughEffect extends AsThoughEffectImpl {
public boolean apply(Game game, Ability source) {
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.apply(game, source);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.apply(game, source);
}
if (!conditionState && effect.getDuration() == Duration.OneUse) {
@ -69,10 +69,10 @@ public class ConditionalAsThoughEffect extends AsThoughEffectImpl {
public boolean applies(UUID sourceId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.applies(sourceId, affectedAbility, source, game, playerId);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.applies(sourceId, affectedAbility, source, game, playerId);
}
return false;
@ -82,10 +82,10 @@ public class ConditionalAsThoughEffect extends AsThoughEffectImpl {
public boolean applies(UUID sourceId, Ability source, UUID affectedControllerId, Game game) {
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.applies(sourceId, source, affectedControllerId, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.applies(sourceId, source, affectedControllerId, game);
}
return false;

View file

@ -81,10 +81,10 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl {
} else {
condition = baseCondition;
}
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
effect.init(source, game);
if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
otherwiseEffect.init(source, game);
}
initDone = true;
@ -122,10 +122,10 @@ public class ConditionalContinuousEffect extends ContinuousEffectImpl {
}
boolean conditionState = condition != null && condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.apply(game, source);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.apply(game, source);
}
switch (effect.getDuration()) {

View file

@ -52,10 +52,10 @@ public class ConditionalContinuousRuleModifyingEffect extends ContinuousRuleModi
} else {
condition = baseCondition;
}
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
effect.init(source, game);
if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
otherwiseEffect.init(source, game);
}
initDone = true;
@ -82,10 +82,10 @@ public class ConditionalContinuousRuleModifyingEffect extends ContinuousRuleModi
init(source, game);
}
if (condition.apply(game, source)) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.applies(event, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.applies(event, source, game);
}
return false;

View file

@ -51,10 +51,10 @@ public class ConditionalCostModificationEffect extends CostModificationEffectImp
public boolean apply(Game game, Ability source, Ability abilityToModify) {
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.apply(game, source, abilityToModify);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.apply(game, source, abilityToModify);
}
if (!conditionState && effect.getDuration() == Duration.OneUse) {
@ -70,10 +70,10 @@ public class ConditionalCostModificationEffect extends CostModificationEffectImp
public boolean applies(Ability abilityToModify, Ability source, Game game) {
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.applies(abilityToModify, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.applies(abilityToModify, source, game);
}
return false;

View file

@ -53,7 +53,7 @@ public class ConditionalOneShotEffect extends OneShotEffect {
if (toApply.isEmpty()) {
return true;
}
toApply.setTargetPointer(this.targetPointer);
toApply.setTargetPointer(this.getTargetPointer().copy());
toApply.stream().forEach(effect -> effect.apply(game, source));
return true;
}

View file

@ -68,10 +68,10 @@ public class ConditionalPreventionEffect extends PreventionEffectImpl {
} else {
condition = baseCondition;
}
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
effect.init(source, game);
if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
otherwiseEffect.init(source, game);
}
initDone = true;
@ -80,10 +80,10 @@ public class ConditionalPreventionEffect extends PreventionEffectImpl {
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.replaceEvent(event, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.replaceEvent(event, source, game);
}
@ -110,10 +110,10 @@ public class ConditionalPreventionEffect extends PreventionEffectImpl {
}
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.applies(event, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.applies(event, source, game);
}
return false;

View file

@ -60,10 +60,10 @@ public class ConditionalReplacementEffect extends ReplacementEffectImpl {
} else {
condition = baseCondition;
}
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
effect.init(source, game);
if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
otherwiseEffect.init(source, game);
}
initDone = true;
@ -72,10 +72,10 @@ public class ConditionalReplacementEffect extends ReplacementEffectImpl {
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.replaceEvent(event, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.replaceEvent(event, source, game);
}
if (!conditionState && effect.getDuration() == Duration.OneUse) {
@ -100,10 +100,10 @@ public class ConditionalReplacementEffect extends ReplacementEffectImpl {
}
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.applies(event, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.applies(event, source, game);
}
return false;

View file

@ -64,10 +64,10 @@ public class ConditionalRequirementEffect extends RequirementEffect {
} else {
condition = baseCondition;
}
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
effect.init(source, game);
if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
otherwiseEffect.init(source, game);
}
initDone = true;
@ -80,10 +80,10 @@ public class ConditionalRequirementEffect extends RequirementEffect {
}
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.applies(permanent, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.applies(permanent, source, game);
}
if (!conditionState && effect.getDuration() == Duration.OneUse) {

View file

@ -63,10 +63,10 @@ public class ConditionalRestrictionEffect extends RestrictionEffect {
} else {
condition = baseCondition;
}
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
effect.init(source, game);
if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
otherwiseEffect.init(source, game);
}
initDone = true;
@ -79,10 +79,10 @@ public class ConditionalRestrictionEffect extends RestrictionEffect {
}
conditionState = condition.apply(game, source);
if (conditionState) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
return effect.applies(permanent, source, game);
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
return otherwiseEffect.applies(permanent, source, game);
}
if (effect.getDuration() == Duration.OneUse) {

View file

@ -36,7 +36,7 @@ public class IntPlusDynamicValue implements DynamicValue {
@Override
public String toString() {
StringBuilder sb = new StringBuilder(baseValue);
StringBuilder sb = new StringBuilder();
sb.append(baseValue).append(" plus ");
return sb.append(value.toString()).toString();
}

View file

@ -4,7 +4,6 @@ package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -16,12 +15,9 @@ public enum TargetPermanentPowerCount implements DynamicValue {
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
Permanent sourcePermanent = game.getPermanent(sourceAbility.getFirstTarget());
if (sourcePermanent == null) {
sourcePermanent = (Permanent) game.getLastKnownInformation(sourceAbility.getFirstTarget(), Zone.BATTLEFIELD);
}
if (sourcePermanent != null) {
return sourcePermanent.getPower().getValue();
Permanent targetPermanent = effect.getTargetPointer().getFirstTargetPermanentOrLKI(game, sourceAbility);
if (targetPermanent != null) {
return targetPermanent.getPower().getValue();
}
return 0;

View file

@ -39,6 +39,11 @@ public interface ContinuousEffect extends Effect {
boolean isInactive(Ability source, Game game);
/**
* Init ability data like ZCC or targets on first check in game cycle (ApplyEffects)
* <p>
* Warning, if you setup target pointer in init then must call super.init at the end (after all choices)
*/
void init(Ability source, Game game);
void init(Ability source, Game game, UUID activePlayerId);

View file

@ -3,7 +3,6 @@ package mage.abilities.effects;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.CompoundAbility;
import mage.abilities.MageSingleton;
import mage.abilities.keyword.ChangelingAbility;
import mage.constants.*;
import mage.filter.Filter;
@ -154,6 +153,11 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
this.discarded = true;
}
@Override
public final void initNewTargetPointer() {
// continuous effect uses init code, so do nothing here
}
@Override
public void init(Ability source, Game game) {
init(source, game, game.getActivePlayerId());
@ -161,7 +165,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
@Override
public void init(Ability source, Game game, UUID activePlayerId) {
targetPointer.init(game, source);
getTargetPointer().init(game, source);
// 20210112 - 611.2c
// 611.2c If a continuous effect generated by the resolution of a spell or ability modifies the
// characteristics or changes the controller of any objects, the set of objects it affects is

View file

@ -21,7 +21,8 @@ public abstract class EffectImpl implements Effect {
protected EffectType effectType;
// read related docs about static and dynamic targets in ContinuousEffectImpl.affectedObjectsSet
protected TargetPointer targetPointer = new FirstTargetPointer();
// warning, do not change it directly, use setTargetPointer instead
private TargetPointer targetPointer = new FirstTargetPointer();
protected String staticText = "";
protected Map<String, Object> values;
@ -30,6 +31,8 @@ public abstract class EffectImpl implements Effect {
public EffectImpl(Outcome outcome) {
this.id = UUID.randomUUID();
this.outcome = outcome;
initNewTargetPointer();
}
protected EffectImpl(final EffectImpl effect) {
@ -48,6 +51,11 @@ public abstract class EffectImpl implements Effect {
}
}
/**
* Init target pointer by default (see TargetPointer for details)
*/
abstract public void initNewTargetPointer();
@Override
public UUID getId() {
return id;
@ -81,7 +89,13 @@ public abstract class EffectImpl implements Effect {
@Override
public Effect setTargetPointer(TargetPointer targetPointer) {
if (targetPointer == null) {
// first target pointer is default
throw new IllegalArgumentException("Wrong code usage: target pointer can't be set to null: " + this);
}
this.targetPointer = targetPointer;
initNewTargetPointer();
return this;
}

View file

@ -4,6 +4,7 @@ package mage.abilities.effects;
import mage.constants.EffectType;
import mage.constants.Outcome;
import mage.target.targetpointer.TargetPointer;
/**
* @author BetaSteward_at_googlemail.com
@ -15,6 +16,12 @@ public abstract class OneShotEffect extends EffectImpl {
this.effectType = EffectType.ONESHOT;
}
@Override
public final void initNewTargetPointer() {
// one short effects don't use init logic
this.getTargetPointer().setInitialized();
}
protected OneShotEffect(final OneShotEffect effect) {
super(effect);
}
@ -24,4 +31,10 @@ public abstract class OneShotEffect extends EffectImpl {
super.setText(staticText);
return this;
}
@Override
public Effect setTargetPointer(TargetPointer targetPointer) {
super.setTargetPointer(targetPointer);
return this;
}
}

View file

@ -35,7 +35,7 @@ public class BecomeBlockedTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Set<MageObjectReference> morSet = new HashSet<>();
for (UUID targetId : targetPointer.getTargets(game, source)) {
for (UUID targetId : getTargetPointer().getTargets(game, source)) {
Permanent permanent = game.getPermanent(targetId);
if (permanent == null) {
continue;

View file

@ -65,7 +65,7 @@ public class ConjureCardEffect extends OneShotEffect {
}
Set<Card> cards = new HashSet<>();
for (int i = 0; i < amount; i++) {
Card card = cardInfo.getCard();
Card card = cardInfo.createCard();
cards.add(card);
}
game.loadCards(cards, source.getControllerId());

View file

@ -79,12 +79,12 @@ public class CopyTargetSpellEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Spell spell;
if (useLKI) {
spell = game.getSpellOrLKIStack(targetPointer.getFirst(game, source));
spell = game.getSpellOrLKIStack(getTargetPointer().getFirst(game, source));
} else {
spell = game.getStack().getSpell(targetPointer.getFirst(game, source));
spell = game.getStack().getSpell(getTargetPointer().getFirst(game, source));
}
if (spell == null) {
spell = (Spell) game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.STACK);
spell = (Spell) game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.STACK);
}
if (spell != null) {
spell.createCopyOnStack(game, source, useController ? spell.getControllerId() : source.getControllerId(),

View file

@ -22,7 +22,7 @@ public class CopyTargetStackAbilityEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(targetPointer.getFirst(game, source));
StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(getTargetPointer().getFirst(game, source));
if (stackAbility == null) {
return false;
}

View file

@ -60,7 +60,7 @@ public class CounterUnlessPaysEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
StackObject spell = game.getStack().getStackObject(targetPointer.getFirst(game, source));
StackObject spell = game.getStack().getStackObject(getTargetPointer().getFirst(game, source));
if (spell == null) {
return false;
}

View file

@ -49,7 +49,7 @@ public class CreateDelayedTriggeredAbilityEffect extends OneShotEffect {
DelayedTriggeredAbility delayedAbility = ability.copy();
if (this.copyTargets) {
if (source.getTargets().isEmpty()) {
delayedAbility.getEffects().setTargetPointer(targetPointer);
delayedAbility.getEffects().setTargetPointer(this.getTargetPointer().copy());
} else {
delayedAbility.getTargets().addAll(source.getTargets());
for (Effect effect : delayedAbility.getEffects()) {

View file

@ -25,6 +25,7 @@ import mage.util.functions.CopyTokenFunction;
import mage.util.functions.EmptyCopyApplier;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author LevelX2
@ -273,9 +274,8 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
Permanent tokenPermanent = game.getPermanent(tokenId);
if (tokenPermanent != null) {
addedTokenPermanents.add(tokenPermanent);
// add counters if necessary ie Ochre Jelly
if (counter != null
&& numberOfCounters > 0) {
// TODO: Workaround to add counters to all created tokens, necessary for correct interactions with cards like Chatterfang, Squirrel General and Ochre Jelly / Printlifter Ooze. See #10786
if (counter != null && numberOfCounters > 0) {
tokenPermanent.addCounters(counter.createInstance(numberOfCounters), source.getControllerId(), source, game);
}
}
@ -418,7 +418,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
} else {
effect = new SacrificeTargetEffect("sacrifice the token copies", source.getControllerId());
}
effect.setTargetPointer(new FixedTargets(addedTokenPermanents, game));
effect.setTargetPointer(new FixedTargets(new ArrayList<>(addedTokenPermanents), game));
DelayedTriggeredAbility exileAbility;

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
@ -9,6 +8,7 @@ import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.Token;
@ -30,6 +30,8 @@ public class CreateTokenEffect extends OneShotEffect {
private final boolean attacking;
private String additionalRules;
private List<UUID> lastAddedTokenIds = new ArrayList<>();
private CounterType counterType;
private DynamicValue numberOfCounters;
public CreateTokenEffect(Token token) {
this(token, StaticValue.get(1));
@ -67,9 +69,17 @@ public class CreateTokenEffect extends OneShotEffect {
this.tapped = effect.tapped;
this.attacking = effect.attacking;
this.lastAddedTokenIds.addAll(effect.lastAddedTokenIds);
this.counterType = effect.counterType;
this.numberOfCounters = effect.numberOfCounters;
this.additionalRules = effect.additionalRules;
}
public CreateTokenEffect entersWithCounters(CounterType counterType, DynamicValue numberOfCounters) {
this.counterType = counterType;
this.numberOfCounters = numberOfCounters;
return this;
}
@Override
public CreateTokenEffect copy() {
return new CreateTokenEffect(this);
@ -80,6 +90,15 @@ public class CreateTokenEffect extends OneShotEffect {
int value = amount.calculate(game, source, this);
token.putOntoBattlefield(value, game, source, source.getControllerId(), tapped, attacking);
this.lastAddedTokenIds = token.getLastAddedTokenIds();
// TODO: Workaround to add counters to all created tokens, necessary for correct interactions with cards like Chatterfang, Squirrel General and Ochre Jelly / Printlifter Ooze. See #10786
if (counterType != null) {
for (UUID tokenId : lastAddedTokenIds) {
Permanent tokenPermanent = game.getPermanent(tokenId);
if (tokenPermanent != null) {
tokenPermanent.addCounters(counterType.createInstance(numberOfCounters.calculate(game, source, this)), source.getControllerId(), source, game);
}
}
}
return true;
}

View file

@ -63,7 +63,7 @@ public class CreateTokenTargetEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
int value = amount.calculate(game, source, this);
if (value > 0) {
return token.putOntoBattlefield(value, game, source, targetPointer.getFirst(game, source), tapped, attacking, (UUID) getValue("playerToAttack"));
return token.putOntoBattlefield(value, game, source, getTargetPointer().getFirst(game, source), tapped, attacking, (UUID) getValue("playerToAttack"));
}
return true;
}

View file

@ -47,7 +47,7 @@ public class DamageAllControlledTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayerOrPlaneswalkerController(targetPointer.getFirst(game, source));
Player player = game.getPlayerOrPlaneswalkerController(getTargetPointer().getFirst(game, source));
if (player == null) {
return false;
}

View file

@ -41,7 +41,7 @@ public class DamageWithExcessEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source));
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
MageObject sourceObject = source.getSourceObject(game);
if (permanent == null || sourceObject == null) {
return false;

View file

@ -51,7 +51,7 @@ public class DestroyTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
int affectedTargets = 0;
for (UUID permanentId : targetPointer.getTargets(game, source)) {
for (UUID permanentId : getTargetPointer().getTargets(game, source)) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null
&& permanent.isPhasedIn()

View file

@ -46,8 +46,7 @@ public class DetainAllEffect extends OneShotEffect {
if (!game.isSimulation()) {
game.informPlayers("Detained permanent: " + permanent.getName());
}
FixedTarget fixedTarget = new FixedTarget(permanent, game);
detainedObjects.add(fixedTarget);
detainedObjects.add(new FixedTarget(permanent, game));
}
game.addEffect(new DetainAllRestrictionEffect(detainedObjects), source);

View file

@ -108,7 +108,7 @@ class DetainRestrictionEffect extends RestrictionEffect {
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
return this.targetPointer.getTargets(game, source).contains(permanent.getId());
return this.getTargetPointer().getTargets(game, source).contains(permanent.getId());
}
@Override

View file

@ -51,7 +51,7 @@ public class DoIfClashWonEffect extends OneShotEffect {
executingEffect.setTargetPointer(new FixedTarget(opponent.getId()));
}
} else {
executingEffect.setTargetPointer(this.targetPointer);
executingEffect.setTargetPointer(this.getTargetPointer().copy());
}
if (executingEffect instanceof OneShotEffect) {
return executingEffect.apply(game, source);

View file

@ -111,7 +111,7 @@ public class DoIfCostPaid extends OneShotEffect {
private void applyEffects(Game game, Ability source, Effects effects) {
if (!effects.isEmpty()) {
for (Effect effect : effects) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
if (effect instanceof OneShotEffect) {
effect.apply(game, source);
} else {

View file

@ -104,7 +104,7 @@ public class DoUnlessAnyPlayerPaysEffect extends OneShotEffect {
// do the effects if nobody paid
if (doEffect) {
for (Effect effect : executingEffects) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
if (effect instanceof OneShotEffect) {
result &= effect.apply(game, source);
} else {

View file

@ -75,7 +75,7 @@ public class DoUnlessControllerPaysEffect extends OneShotEffect {
// do the effects if not paid
if (doEffect) {
for (Effect effect : executingEffects) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
if (effect instanceof OneShotEffect) {
result &= effect.apply(game, source);
} else {

View file

@ -110,7 +110,7 @@ public class DoUnlessTargetPlayerOrTargetsControllerPaysEffect extends OneShotEf
// do the effects if not paid
if (doEffect) {
for (Effect effect : executingEffects) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
if (effect instanceof OneShotEffect) {
result &= effect.apply(game, source);
} else {
@ -118,7 +118,7 @@ public class DoUnlessTargetPlayerOrTargetsControllerPaysEffect extends OneShotEf
}
}
} else if (otherwiseEffect != null) {
otherwiseEffect.setTargetPointer(this.targetPointer);
otherwiseEffect.setTargetPointer(this.getTargetPointer().copy());
if (otherwiseEffect instanceof OneShotEffect) {
result &= otherwiseEffect.apply(game, source);
} else {

View file

@ -60,7 +60,7 @@ public class DoWhenCostPaid extends OneShotEffect {
int bookmark = game.bookmarkState();
if (cost.pay(source, game, source, player.getId(), false)) {
if (ability.getTargets().isEmpty()) {
ability.getEffects().setTargetPointer(getTargetPointer());
ability.getEffects().setTargetPointer(this.getTargetPointer().copy());
}
game.fireReflexiveTriggeredAbility(ability, source);
player.resetStoredBookmark(game);

View file

@ -48,7 +48,7 @@ public class DontUntapInControllersUntapStepTargetEffect extends ContinuousRuleM
if (game.getTurnStepType() != PhaseStep.UNTAP) {
return false;
}
for (UUID targetId : targetPointer.getTargets(game, source)) {
for (UUID targetId : getTargetPointer().getTargets(game, source)) {
if (!event.getTargetId().equals(targetId)) {
continue;
}

View file

@ -0,0 +1,43 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author PurpleCrowbar
*/
public class DoubleCountersTargetEffect extends OneShotEffect {
private final CounterType counterType;
public DoubleCountersTargetEffect(CounterType counterType) {
super(Outcome.Benefit);
this.counterType = counterType;
staticText = "double the number of " + counterType.getName() + " counters on it";
}
private DoubleCountersTargetEffect(final DoubleCountersTargetEffect effect) {
super(effect);
this.counterType = effect.counterType;
}
@Override
public DoubleCountersTargetEffect copy() {
return new DoubleCountersTargetEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null) {
return false;
}
return permanent.addCounters(counterType.createInstance(
permanent.getCounters(game).getCount(counterType)
), source.getControllerId(), source, game);
}
}

View file

@ -70,7 +70,7 @@ public class DraftFromSpellbookEffect extends OneShotEffect {
return false;
}
Set<Card> cards = new HashSet<>();
cards.add(cardInfo.getCard());
cards.add(cardInfo.createCard());
game.loadCards(cards, player.getId());
player.moveCards(cards, Zone.HAND, source, game);
return true;

View file

@ -32,7 +32,7 @@ public class ExileCardYouChooseTargetOpponentEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Player opponent = game.getPlayer(targetPointer.getFirst(game, source));
Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source));
if (controller == null || opponent == null) {
return false;
}

View file

@ -0,0 +1,100 @@
package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.Set;
import java.util.UUID;
/**
* @author Cguy7777
*/
public class ExileCardsFromTopOfLibraryControllerEffect extends OneShotEffect {
private final int amount;
private final boolean toUniqueExileZone;
private final boolean faceDown;
public ExileCardsFromTopOfLibraryControllerEffect(int amount) {
this(amount, false);
}
public ExileCardsFromTopOfLibraryControllerEffect(int amount, boolean toUniqueExileZone) {
this(amount, toUniqueExileZone, false);
}
public ExileCardsFromTopOfLibraryControllerEffect(int amount, boolean toUniqueExileZone, boolean faceDown) {
this(amount, toUniqueExileZone, faceDown, false);
}
/**
* @param amount number of cards to exile
* @param toUniqueExileZone moves the card to a source object dependant
* unique exile zone, so another effect of the same source object (e.g.
* Theater of Horrors) can identify the card
* @param faceDown if true, cards are exiled face down
* @param withFaceDownReminderText if true, add the reminder text for exiling one face down card
*/
public ExileCardsFromTopOfLibraryControllerEffect(int amount, boolean toUniqueExileZone, boolean faceDown, boolean withFaceDownReminderText) {
super(Outcome.Exile);
this.amount = amount;
this.toUniqueExileZone = toUniqueExileZone;
this.faceDown = faceDown;
staticText = "exile the top "
+ ((amount > 1) ? CardUtil.numberToText(amount) + " cards" : "card")
+ " of your library"
+ (faceDown ? " face down" : "")
+ (withFaceDownReminderText ? ". <i>(You can't look at it.)</i>" : "");
}
protected ExileCardsFromTopOfLibraryControllerEffect(final ExileCardsFromTopOfLibraryControllerEffect effect) {
super(effect);
this.amount = effect.amount;
this.toUniqueExileZone = effect.toUniqueExileZone;
this.faceDown = effect.faceDown;
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
UUID exileZoneId = null;
String exileZoneName = "";
if (toUniqueExileZone) {
MageObject sourceObject = source.getSourceObject(game);
if (sourceObject == null) {
return false;
}
exileZoneId = CardUtil.getExileZoneId(game, source);
exileZoneName = CardUtil.createObjectRealtedWindowTitle(source, game, null);
}
Set<Card> cards = controller.getLibrary().getTopCards(game, amount);
if (cards.isEmpty()) {
return true;
}
boolean exiledSuccessfully = false;
for (Card card : cards) {
card.setFaceDown(faceDown, game);
exiledSuccessfully |= controller.moveCardsToExile(card, source, game, !faceDown, exileZoneId, exileZoneName);
card.setFaceDown(faceDown, game);
}
return exiledSuccessfully;
}
@Override
public ExileCardsFromTopOfLibraryControllerEffect copy() {
return new ExileCardsFromTopOfLibraryControllerEffect(this);
}
}

View file

@ -52,7 +52,7 @@ public class ExileFromZoneTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(targetPointer.getFirst(game, source));
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
if (player == null) {
return false;
}

View file

@ -13,6 +13,7 @@ import mage.players.Player;
import mage.target.targetpointer.FixedTargets;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
@ -74,7 +75,7 @@ public class ExileReturnBattlefieldNextEndStepTargetEffect extends OneShotEffect
Effect effect = yourControl
? new ReturnToBattlefieldUnderYourControlTargetEffect(exiledOnly)
: new ReturnToBattlefieldUnderOwnerControlTargetEffect(false, exiledOnly);
effect.setTargetPointer(new FixedTargets(new CardsImpl(toExile), game));
effect.setTargetPointer(new FixedTargets(toExile, game));
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source);
return true;
}

View file

@ -2,7 +2,6 @@ package mage.abilities.effects.common;
import mage.ApprovingObject;
import mage.abilities.Ability;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
@ -52,7 +51,7 @@ public class ExileTargetCardCopyAndCastEffect extends OneShotEffect {
player.moveCards(card, Zone.EXILED, source, game);
Card cardCopy = game.copyCard(card, source, source.getControllerId());
if (optional && !player.chooseUse(outcome, "Cast copy of " +
card.getName() + " without paying its mana cost?", source, game)) {
card.getName() + (this.noMana ? " without paying its mana cost?" : "?" ), source, game)) {
return true;
}
game.getState().setValue("PlayFromNotOwnHandZone" + cardCopy.getId(), Boolean.TRUE);

View file

@ -30,7 +30,7 @@ public class ExileTargetEffect extends OneShotEffect {
private String exileZone = null;
private UUID exileId = null;
private boolean toSourceExileZone = false; // exile the targets to a source object specific exile zone (takes care of zone change counter)
private boolean withName = true;
private boolean withName = true; // for face down - allows to hide card name in game logs before real face down apply
public ExileTargetEffect(String effectText) {
this();
@ -75,6 +75,7 @@ public class ExileTargetEffect extends OneShotEffect {
return this;
}
// TODO: replace to withHiddenName and instructions to use
public void setWithName(boolean withName) {
this.withName = withName;
}

View file

@ -55,14 +55,14 @@ public class ExileTargetForSourceEffect extends OneShotEffect {
}
Set<UUID> objectsToMove = new LinkedHashSet<>();
if (this.targetPointer instanceof FirstTargetPointer
if (this.getTargetPointer() instanceof FirstTargetPointer
&& source.getTargets().size() > 1) {
for (Target target : source.getTargets()) {
objectsToMove.addAll(target.getTargets());
}
} else {
if (this.targetPointer != null && !this.targetPointer.getTargets(game, source).isEmpty()) {
objectsToMove.addAll(this.targetPointer.getTargets(game, source));
if (!this.getTargetPointer().getTargets(game, source).isEmpty()) {
objectsToMove.addAll(this.getTargetPointer().getTargets(game, source));
} else {
// issue with Madness keyword #6889
UUID fixedTargetId = null;

View file

@ -1,5 +1,6 @@
package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
@ -14,7 +15,9 @@ import mage.players.Player;
import mage.target.targetpointer.FixedTargets;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;
public class ExileTopXMayPlayUntilEffect extends OneShotEffect {

View file

@ -56,9 +56,7 @@ public class ExileUntilSourceLeavesEffect extends OneShotEffect {
}
ExileTargetEffect effect = new ExileTargetEffect(CardUtil.getCardExileZoneId(game, source), permanent.getIdName());
if (targetPointer != null) { // Grasping Giant
effect.setTargetPointer(targetPointer);
}
effect.setTargetPointer(this.getTargetPointer().copy());
if (effect.apply(game, source)) {
game.addDelayedTriggeredAbility(new OnLeaveReturnExiledAbility(returnToZone), source);
return true;

View file

@ -65,7 +65,7 @@ public class FlipCoinEffect extends OneShotEffect {
}
boolean result = true;
for (Effect effect : controller.flipCoin(source, game, true) ? executingEffectsWon : executingEffectsLost) {
effect.setTargetPointer(this.targetPointer);
effect.setTargetPointer(this.getTargetPointer().copy());
if (effect instanceof OneShotEffect) {
result &= effect.apply(game, source);
} else {

View file

@ -39,7 +39,7 @@ public class GainLifeTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
for (UUID playerId : targetPointer.getTargets(game, source)) {
for (UUID playerId : getTargetPointer().getTargets(game, source)) {
Player player = game.getPlayer(playerId);
if (player != null) {
player.gainLife(life.calculate(game, source, this), game, source);

View file

@ -32,11 +32,11 @@ public class ImprintTargetEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Permanent sourcePermanent = game.getPermanent(source.getSourceId());
if (sourcePermanent != null) {
Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source));
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent != null) {
sourcePermanent.imprint(permanent.getId(), game);
} else {
Card card = game.getCard(targetPointer.getFirst(game, source));
Card card = game.getCard(getTargetPointer().getFirst(game, source));
if (card != null) {
sourcePermanent.imprint(card.getId(), game);
}

View file

@ -1,32 +1,3 @@
/*
*
* 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 mage.abilities.Ability;

View file

@ -9,6 +9,7 @@ import mage.game.Game;
import mage.players.Player;
/**
* TODO: delete, there are already end turn code with control reset
* @author nantuko
*/
public class LoseControlOnOtherPlayersControllerEffect extends OneShotEffect {

View file

@ -28,7 +28,7 @@ public class LoseHalfLifeTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(targetPointer.getFirst(game, source));
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
if (player != null) {
Integer amount = (int) Math.ceil(player.getLife() / 2f);
if (amount > 0) {

View file

@ -42,11 +42,11 @@ public class LoseLifeTargetControllerEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
MageObject targetCard = targetPointer.getFirstTargetPermanentOrLKI(game, source);
MageObject targetCard = getTargetPointer().getFirstTargetPermanentOrLKI(game, source);
// if target is a countered spell
if (targetCard == null) {
targetCard = game.getLastKnownInformation(targetPointer.getFirst(game, source), Zone.STACK);
targetCard = game.getLastKnownInformation(getTargetPointer().getFirst(game, source), Zone.STACK);
}
if (targetCard != null) {

View file

@ -40,7 +40,7 @@ public class LoseLifeTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
boolean applied = false;
for (UUID playerId : targetPointer.getTargets(game, source)) {
for (UUID playerId : getTargetPointer().getTargets(game, source)) {
Player player = game.getPlayer(playerId);
if (player != null
&& player.loseLife(amount.calculate(game, source, this), game, source, false) > 0) {

View file

@ -23,7 +23,7 @@ public class MayTapOrUntapTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Permanent target = game.getPermanent(targetPointer.getFirst(game, source));
Permanent target = game.getPermanent(getTargetPointer().getFirst(game, source));
Player player = game.getPlayer(source.getControllerId());
if (target != null && player != null) {
if (target.isTapped()) {

View file

@ -105,7 +105,7 @@ public class MeldEffect extends OneShotEffect {
if (cardInfoList.isEmpty()) {
return false;
}
MeldCard meldCard = (MeldCard) cardInfoList.get(0).getCard().copy();
MeldCard meldCard = (MeldCard) cardInfoList.get(0).createCard().copy();
meldCard.setOwnerId(controller.getId());
meldCard.setTopHalfCard(meldWithCard, game);
meldCard.setBottomHalfCard(sourceCard, game);

View file

@ -38,7 +38,7 @@ public class MillCardsTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(targetPointer.getFirst(game, source));
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
if (player != null) {
player.millCards(numberCards.calculate(game, source, this), source, game);
return true;

View file

@ -42,6 +42,7 @@ public class PreventAllDamageFromChosenSourceToYouEffect extends PreventionEffec
@Override
public void init(Ability source, Game game) {
super.init(source, game);
this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game);
// be sure to note the target source's zcc, etc, if able.
if (targetSource.getFirstTarget() != null) {

View file

@ -47,6 +47,7 @@ public class PreventDamageBySourceEffect extends PreventionEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game);
mageObjectReference = new MageObjectReference(target.getFirstTarget(), game);
}

View file

@ -45,7 +45,7 @@ public class PreventDamageToTargetEffect extends PreventionEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return !this.used && super.applies(event, source, game) && event.getTargetId().equals(targetPointer.getFirst(game, source));
return !this.used && super.applies(event, source, game) && event.getTargetId().equals(getTargetPointer().getFirst(game, source));
}
@Override

View file

@ -43,6 +43,7 @@ public class PreventNextDamageFromChosenSourceToTargetEffect extends PreventionE
@Override
public void init(Ability source, Game game) {
super.init(source, game);
this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game);
}
@ -56,7 +57,7 @@ public class PreventNextDamageFromChosenSourceToTargetEffect extends PreventionE
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (!this.used && super.applies(event, source, game)) {
if (event.getTargetId().equals(targetPointer.getFirst(game, source)) && event.getSourceId().equals(targetSource.getFirstTarget())) {
if (event.getTargetId().equals(getTargetPointer().getFirst(game, source)) && event.getSourceId().equals(targetSource.getFirstTarget())) {
return true;
}
}

View file

@ -43,6 +43,7 @@ public class PreventNextDamageFromChosenSourceToYouEffect extends PreventionEffe
@Override
public void init(Ability source, Game game) {
super.init(source, game);
this.targetSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), source, game);
}

View file

@ -54,7 +54,7 @@ public class PutOnLibraryTargetEffect extends OneShotEffect {
if (controller != null) {
List<Card> cards = new ArrayList<>();
List<Permanent> permanents = new ArrayList<>();
for (UUID targetId : targetPointer.getTargets(game, source)) {
for (UUID targetId : getTargetPointer().getTargets(game, source)) {
switch (game.getState().getZone(targetId)) {
case BATTLEFIELD:
Permanent permanent = game.getPermanent(targetId);

View file

@ -1,6 +1,7 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.RedirectionEffect;
import mage.constants.Duration;
import mage.game.Game;
@ -14,7 +15,6 @@ public class RedirectDamageFromSourceToTargetEffect extends RedirectionEffect {
public RedirectDamageFromSourceToTargetEffect(Duration duration, int amountToRedirect, UsageType usageType) {
super(duration, amountToRedirect, usageType);
staticText = "The next " + amountToRedirect + " damage that would be dealt to {this} this turn is dealt to target creature you control instead.";
}
protected RedirectDamageFromSourceToTargetEffect(final RedirectDamageFromSourceToTargetEffect effect) {
@ -39,4 +39,14 @@ public class RedirectDamageFromSourceToTargetEffect extends RedirectionEffect {
}
return false;
}
@Override
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
return "the next " + amountToRedirect + " damage that would be dealt to {this} this turn is dealt to "
+ getTargetPointer().describeTargets(mode.getTargets(), "that creature")
+ " instead";
}
}

View file

@ -39,7 +39,7 @@ public class RegenerateSourceWithReflexiveEffect extends RegenerateSourceEffect
if (super.replaceEvent(event, source, game)) {
if (this.setReflexiveTarget) {
reflexive.getEffects().setTargetPointer(
new FixedTarget(targetPointer.getFirst(game, source), game)
new FixedTarget(getTargetPointer().getFirst(game, source), game)
);
}
game.fireReflexiveTriggeredAbility(reflexive, source);

View file

@ -26,7 +26,7 @@ public class RegenerateTargetEffect extends ReplacementEffectImpl {
@Override
public void init(Ability source, Game game) {
super.init(source, game);
RegenerateSourceEffect.initRegenerationShieldInfo(game, source, targetPointer.getFirst(game, source));
RegenerateSourceEffect.initRegenerationShieldInfo(game, source, getTargetPointer().getFirst(game, source));
}
@Override
@ -37,7 +37,7 @@ public class RegenerateTargetEffect extends ReplacementEffectImpl {
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
//20110204 - 701.11
Permanent permanent = game.getPermanent(targetPointer.getFirst(game, source));
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent != null && permanent.regenerate(source, game)) {
this.used = true;
return true;
@ -53,7 +53,7 @@ public class RegenerateTargetEffect extends ReplacementEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
//20110204 - 701.11c - event.getAmount() is used to signal if regeneration is allowed
return event.getAmount() == 0 && event.getTargetId().equals(targetPointer.getFirst(game, source)) && !this.used;
return event.getAmount() == 0 && event.getTargetId().equals(getTargetPointer().getFirst(game, source)) && !this.used;
}
@Override

View file

@ -22,6 +22,7 @@ public class ReturnFromExileForSourceEffect extends OneShotEffect {
private final Zone returnToZone;
private boolean pluralCards;
private boolean pluralOwners;
private boolean putPhrasing;
/**
* @param zone Zone the card should return to
@ -31,6 +32,7 @@ public class ReturnFromExileForSourceEffect extends OneShotEffect {
this.returnToZone = zone;
this.pluralCards = false;
this.pluralOwners = false;
this.putPhrasing = false;
updateText();
}
@ -39,6 +41,7 @@ public class ReturnFromExileForSourceEffect extends OneShotEffect {
this.returnToZone = effect.returnToZone;
this.pluralCards = effect.pluralCards;
this.pluralOwners = effect.pluralOwners;
this.putPhrasing = effect.putPhrasing;
}
@Override
@ -77,16 +80,22 @@ public class ReturnFromExileForSourceEffect extends OneShotEffect {
return true;
}
public ReturnFromExileForSourceEffect withText(boolean pluralCards, boolean pluralOwners) {
public ReturnFromExileForSourceEffect withText(boolean pluralCards, boolean pluralOwners, boolean putPhrasing) {
this.pluralCards = pluralCards;
this.pluralOwners = pluralOwners;
this.putPhrasing = putPhrasing;
updateText();
return this;
}
private void updateText() {
StringBuilder sb = new StringBuilder();
sb.append("return the exiled ").append(pluralCards ? "cards" : "card").append(" to ");
if (putPhrasing) {
sb.append("put ").append(pluralCards ? "all cards " : "the card ").append("exiled with {this} ");
sb.append(returnToZone == Zone.BATTLEFIELD ? "onto " : "into ");
} else {
sb.append("return the exiled ").append(pluralCards ? "cards" : "card").append(" to ");
}
if (returnToZone == Zone.BATTLEFIELD) {
sb.append("the battlefield under ");
}

View file

@ -23,9 +23,8 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect
private final boolean tapped;
private final boolean attacking;
// If true, creatures are returned to their owner's control.
// If false, creatures are returned under the effect's controller control.
private final boolean underOwnerControl;
// Targets are returned under the control of the effect controller (e.g. "under your control")
public ReturnFromGraveyardToBattlefieldTargetEffect() {
this(false);
@ -34,22 +33,17 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect
public ReturnFromGraveyardToBattlefieldTargetEffect(boolean tapped) {
this(tapped, false);
}
public ReturnFromGraveyardToBattlefieldTargetEffect(boolean tapped, boolean attacking) {
this(tapped, attacking, false);
}
public ReturnFromGraveyardToBattlefieldTargetEffect(boolean tapped, boolean attacking, boolean underOwnerControl) {
public ReturnFromGraveyardToBattlefieldTargetEffect(boolean tapped, boolean attacking) {
super(Outcome.PutCreatureInPlay);
this.tapped = tapped;
this.attacking = attacking;
this.underOwnerControl = underOwnerControl;
}
protected ReturnFromGraveyardToBattlefieldTargetEffect(final ReturnFromGraveyardToBattlefieldTargetEffect effect) {
super(effect);
this.tapped = effect.tapped;
this.attacking = effect.attacking;
this.underOwnerControl = effect.underOwnerControl;
}
@Override
@ -68,7 +62,7 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect
cardsToMove.add(card);
}
}
controller.moveCards(cardsToMove, Zone.BATTLEFIELD, source, game, tapped, false, underOwnerControl, null);
controller.moveCards(cardsToMove, Zone.BATTLEFIELD, source, game, tapped, false, false, null);
if (attacking) {
for (Card card : cardsToMove) {
game.getCombat().addAttackingCreature(card.getId(), game);
@ -119,12 +113,7 @@ public class ReturnFromGraveyardToBattlefieldTargetEffect extends OneShotEffect
sb.append(" attacking");
}
if (!yourGrave) {
if (underOwnerControl) {
sb.append("under their owner's control");
}
else {
sb.append(" under your control");
}
sb.append(" under your control");
}
return sb.toString();
}

View file

@ -2,15 +2,9 @@ package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.counters.Counter;
import mage.counters.Counters;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
@ -19,27 +13,24 @@ import java.util.UUID;
*/
public class ReturnFromGraveyardToBattlefieldWithCounterTargetEffect extends ReturnFromGraveyardToBattlefieldTargetEffect {
private final Counter counter;
private final boolean additional;
private final Counters counters;
private final String counterText;
public ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(Counter counter) {
this(counter, false);
}
public ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(Counter counter, boolean additional) {
this(counter, additional, false);
}
public ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(Counter counter, boolean additional, boolean underOwnerControl) {
super(false, false, underOwnerControl);
this.counter = counter;
this.additional = additional;
super(false);
this.counters = new Counters();
this.counters.addCounter(counter);
this.counterText = makeText(counter, additional);
}
protected ReturnFromGraveyardToBattlefieldWithCounterTargetEffect(final ReturnFromGraveyardToBattlefieldWithCounterTargetEffect effect) {
super(effect);
this.counter = effect.counter.copy();
this.additional = effect.additional;
this.counters = effect.counters.copy();
this.counterText = effect.counterText;
}
@Override
@ -50,20 +41,13 @@ public class ReturnFromGraveyardToBattlefieldWithCounterTargetEffect extends Ret
@Override
public boolean apply(Game game, Ability source) {
for (UUID targetId : getTargetPointer().getTargets(game, source)) {
AddCounterTargetReplacementEffect counterEffect = new AddCounterTargetReplacementEffect(counter);
counterEffect.setTargetPointer(new FixedTarget(targetId, game));
game.addEffect(counterEffect, source);
game.setEnterWithCounters(targetId, counters.copy());
}
return super.apply(game, source);
}
@Override
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
StringBuilder sb = new StringBuilder(super.getText(mode));
sb.append(" with ");
private String makeText(Counter counter, boolean additional) {
StringBuilder sb = new StringBuilder(" with ");
if (additional) {
if (counter.getCount() == 1) {
sb.append("an");
@ -82,52 +66,14 @@ public class ReturnFromGraveyardToBattlefieldWithCounterTargetEffect extends Ret
if (counter.getCount() != 1) {
sb.append('s');
}
if (targetPointer.isPlural(mode.getTargets())) {
sb.append(" on them");
} else {
sb.append(" on it");
}
return sb.toString();
}
}
class AddCounterTargetReplacementEffect extends ReplacementEffectImpl {
private final Counter counter;
public AddCounterTargetReplacementEffect(Counter counter) {
super(Duration.EndOfStep, Outcome.BoostCreature);
this.counter = counter;
}
private AddCounterTargetReplacementEffect(final AddCounterTargetReplacementEffect effect) {
super(effect);
this.counter = effect.counter.copy();
}
@Override
public AddCounterTargetReplacementEffect copy() {
return new AddCounterTargetReplacementEffect(this);
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
return getTargetPointer().getTargets(game, source).contains(event.getTargetId());
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent creature = ((EntersTheBattlefieldEvent) event).getTarget();
if (creature == null) {
return false;
public String getText(Mode mode) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
creature.addCounters(counter.copy(), source.getControllerId(), source, game, event.getAppliedEffects());
discard();
return false;
return super.getText(mode) + counterText + (getTargetPointer().isPlural(mode.getTargets()) ? " on them" : " on it");
}
}

View file

@ -27,7 +27,7 @@ public class ReturnToHandChosenControlledPermanentEffect extends ReturnToHandCho
@Override
public boolean apply(Game game, Ability source) {
this.targetPointer = new FixedTarget(source.getControllerId());
this.setTargetPointer(new FixedTarget(source.getControllerId()));
return super.apply(game, source);
}

View file

@ -39,7 +39,7 @@ public class ReturnToHandTargetEffect extends OneShotEffect {
}
List<UUID> copyIds = new ArrayList<>();
Set<Card> cards = new LinkedHashSet<>();
for (UUID targetId : targetPointer.getTargets(game, source)) {
for (UUID targetId : getTargetPointer().getTargets(game, source)) {
MageObject mageObject = game.getObject(targetId);
if (mageObject != null) {
if (mageObject instanceof Spell

View file

@ -107,7 +107,10 @@ public class RevealAndSeparatePilesEffect extends OneShotEffect {
Cards cards = new CardsImpl(controller.getLibrary().getTopCards(game, toReveal));
controller.revealCards(source, cards, game);
Player separatingPlayer = this.getExecutingPlayer(controller, game, source, playerWhoSeparates, "separate the revealed cards");
Player separatingPlayer = getExecutingPlayer(controller, game, source, playerWhoSeparates, "separate the revealed cards");
if (separatingPlayer == null) {
return false;
}
TargetCard target = new TargetCard(0, cards.size(), Zone.LIBRARY, filter);
List<Card> pile1 = new ArrayList<>();
separatingPlayer.choose(Outcome.Neutral, cards, target, source, game);
@ -117,10 +120,12 @@ public class RevealAndSeparatePilesEffect extends OneShotEffect {
.filter(Objects::nonNull)
.forEach(pile1::add);
cards.removeIf(target.getTargets()::contains);
List<Card> pile2 = new ArrayList<>();
pile2.addAll(cards.getCards(game));
List<Card> pile2 = new ArrayList<>(cards.getCards(game));
Player choosingPlayer = this.getExecutingPlayer(controller, game, source, playerWhoChooses, "choose the piles");
Player choosingPlayer = getExecutingPlayer(controller, game, source, playerWhoChooses, "choose the piles");
if (choosingPlayer == null) {
return false;
}
boolean choice = choosingPlayer.choosePile(outcome, "Choose a pile to put into hand.", pile1, pile2, game);
Zone pile1Zone = choice ? Zone.HAND : targetZone;

View file

@ -31,7 +31,7 @@ public class SacrificeControllerEffect extends SacrificeEffect {
@Override
public boolean apply(Game game, Ability source) {
this.targetPointer = new FixedTarget(source.getControllerId());
this.setTargetPointer(new FixedTarget(source.getControllerId()));
return super.apply(game, source);
}

View file

@ -51,7 +51,7 @@ public class SacrificeEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
boolean applied = false;
for (UUID playerId : targetPointer.getTargets(game, source)) {
for (UUID playerId : getTargetPointer().getTargets(game, source)) {
Player player = game.getPlayer(playerId);
if (player == null) {
continue;

View file

@ -49,7 +49,7 @@ public class SacrificeTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
int affectedTargets = 0;
for (UUID permanentId : targetPointer.getTargets(game, source)) {
for (UUID permanentId : getTargetPointer().getTargets(game, source)) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null && (playerIdThatHasToSacrifice == null || playerIdThatHasToSacrifice.equals(permanent.getControllerId()))) {
permanent.sacrifice(source, game);

View file

@ -38,7 +38,7 @@ public class SetPlayerLifeTargetEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(targetPointer.getFirst(game, source));
Player player = game.getPlayer(getTargetPointer().getFirst(game, source));
if (player != null) {
player.setLife(amount.calculate(game, source, this), game, source);
return true;

Some files were not shown because too many files have changed in this diff Show more