forked from External/mage
* Copy spells - improved combo support with other abilities like Kicker or Entwine (#7192):
* Now ZCC of copied spells syncs with source card or coping spell (allows to keep ability settings that depends on ZCC); * Fixed bug that allows to lost kicked status in copied spells after counter the original spell or moves the original card (see #7192); * Test framework: improved support of targeting copy or non copy spells on stack;
This commit is contained in:
parent
936be75a66
commit
4d362d7edc
32 changed files with 522 additions and 302 deletions
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
package mage.abilities.condition.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
|
|
@ -21,10 +19,15 @@ public enum KickedCondition implements Condition {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Card card = game.getCard(source.getSourceId());
|
||||
if (card == null) {
|
||||
// if permanent spell was copied then it enters with sourceId = PermanentToken instead Card (example: Lithoform Engine)
|
||||
card = game.getPermanentEntering(source.getSourceId());
|
||||
}
|
||||
|
||||
if (card != null) {
|
||||
for (Ability ability: card.getAbilities()) {
|
||||
for (Ability ability : card.getAbilities()) {
|
||||
if (ability instanceof KickerAbility) {
|
||||
if(((KickerAbility) ability).isKicked(game, source, "")) {
|
||||
if (((KickerAbility) ability).isKicked(game, source, "")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import mage.filter.FilterInPlay;
|
|||
import mage.filter.predicate.mageobject.FromSetPredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.CopiedStackObjectEvent;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
|
|
@ -82,7 +81,7 @@ public abstract class CopySpellForEachItCouldTargetEffect<T extends MageItem> ex
|
|||
}
|
||||
|
||||
// collect objects that can be targeted
|
||||
Spell copy = spell.copySpell(source.getControllerId());
|
||||
Spell copy = spell.copySpell(source.getControllerId(), game);
|
||||
modifyCopy(copy, game, source);
|
||||
Target sampleTarget = targetsToBeChanged.iterator().next().getTarget(copy);
|
||||
sampleTarget.setNotTarget(true);
|
||||
|
|
@ -94,7 +93,7 @@ public abstract class CopySpellForEachItCouldTargetEffect<T extends MageItem> ex
|
|||
obj = game.getPlayer(objId);
|
||||
}
|
||||
if (obj != null) {
|
||||
copy = spell.copySpell(source.getControllerId());
|
||||
copy = spell.copySpell(source.getControllerId(), game);
|
||||
try {
|
||||
modifyCopy(copy, (T) obj, game, source);
|
||||
if (!filter.match((T) obj, source.getSourceId(), actingPlayer.getId(), game)) {
|
||||
|
|
|
|||
|
|
@ -34,15 +34,15 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
|
|||
private final CardType additionalCardType;
|
||||
private boolean hasHaste;
|
||||
private final int number;
|
||||
private List<Permanent> addedTokenPermanents;
|
||||
private final List<Permanent> addedTokenPermanents;
|
||||
private SubType additionalSubType;
|
||||
private SubType onlySubType;
|
||||
private boolean tapped;
|
||||
private boolean attacking;
|
||||
private UUID attackedPlayer;
|
||||
private final boolean tapped;
|
||||
private final boolean attacking;
|
||||
private final UUID attackedPlayer;
|
||||
private final int tokenPower;
|
||||
private final int tokenToughness;
|
||||
private boolean gainsFlying;
|
||||
private final boolean gainsFlying;
|
||||
private boolean becomesArtifact;
|
||||
private ObjectColor color;
|
||||
private boolean useLKI = false;
|
||||
|
|
@ -170,7 +170,7 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
|
|||
}
|
||||
|
||||
EmptyToken token = new EmptyToken();
|
||||
CardUtil.copyTo(token).from(copyFrom); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
|
||||
CardUtil.copyTo(token).from(copyFrom, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
|
||||
applier.apply(game, token, source, targetId);
|
||||
if (becomesArtifact) {
|
||||
token.addCardType(CardType.ARTIFACT);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
package mage.abilities.effects.common;
|
||||
|
||||
import java.util.Objects;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.DelayedTriggeredAbility;
|
||||
|
|
@ -20,10 +19,11 @@ import mage.game.stack.Spell;
|
|||
import mage.game.stack.StackObject;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jeffwadsworth
|
||||
*
|
||||
* <p>
|
||||
* 702.49. Epic 702.49a Epic represents two spell abilities, one of which
|
||||
* creates a delayed triggered ability. “Epic” means “For the rest of the game,
|
||||
* you can't cast spells,” and “At the beginning of each of your upkeeps for the
|
||||
|
|
@ -55,7 +55,7 @@ public class EpicEffect extends OneShotEffect {
|
|||
if (spell == null) {
|
||||
return false;
|
||||
}
|
||||
spell = spell.copySpell(source.getControllerId());
|
||||
spell = spell.copySpell(source.getControllerId(), game);
|
||||
// Remove Epic effect from the spell
|
||||
Effect epicEffect = null;
|
||||
for (Effect effect : spell.getSpellAbility().getEffects()) {
|
||||
|
|
@ -118,9 +118,7 @@ class EpicReplacementEffect extends ContinuousRuleModifyingEffectImpl {
|
|||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
if (Objects.equals(source.getControllerId(), event.getPlayerId())) {
|
||||
MageObject object = game.getObject(event.getSourceId());
|
||||
if (object != null) {
|
||||
return true;
|
||||
}
|
||||
return object != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import mage.util.CardUtil;
|
|||
*/
|
||||
public class EmbalmAbility extends ActivatedAbilityImpl {
|
||||
|
||||
private String rule;
|
||||
private final String rule;
|
||||
|
||||
public EmbalmAbility(Cost cost, Card card) {
|
||||
super(Zone.GRAVEYARD, new EmbalmEffect(), cost);
|
||||
|
|
@ -85,7 +85,7 @@ class EmbalmEffect extends OneShotEffect {
|
|||
return false;
|
||||
}
|
||||
EmptyToken token = new EmptyToken();
|
||||
CardUtil.copyTo(token).from(card); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
|
||||
CardUtil.copyTo(token).from(card, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
|
||||
token.getColor(game).setColor(ObjectColor.WHITE);
|
||||
token.addSubType(game, SubType.ZOMBIE);
|
||||
token.getManaCost().clear();
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ class EncoreEffect extends OneShotEffect {
|
|||
return false;
|
||||
}
|
||||
EmptyToken token = new EmptyToken();
|
||||
CardUtil.copyTo(token).from(card);
|
||||
CardUtil.copyTo(token).from(card, game);
|
||||
Set<MageObjectReference> addedTokens = new HashSet<>();
|
||||
int opponentCount = game.getOpponents(source.getControllerId()).size();
|
||||
if (opponentCount < 1) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import mage.abilities.costs.OptionalAdditionalCost;
|
|||
import mage.abilities.costs.OptionalAdditionalCostImpl;
|
||||
import mage.abilities.costs.OptionalAdditionalModeSourceCosts;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.constants.AbilityType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
|
|
@ -144,17 +143,7 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
|
|||
}
|
||||
|
||||
private String getActivationKey(Ability source, Game game) {
|
||||
// same logic as KickerAbility, uses for source ability only
|
||||
int zcc = 0;
|
||||
if (source.getAbilityType() == AbilityType.TRIGGERED) {
|
||||
zcc = source.getSourceObjectZoneChangeCounter();
|
||||
}
|
||||
if (zcc == 0) {
|
||||
zcc = game.getState().getZoneChangeCounter(source.getSourceId());
|
||||
}
|
||||
if (zcc > 0 && (source.getAbilityType() == AbilityType.TRIGGERED)) {
|
||||
--zcc;
|
||||
}
|
||||
return String.valueOf(zcc);
|
||||
// same logic as KickerAbility
|
||||
return KickerAbility.getActivationKey(source, game);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class EternalizeEffect extends OneShotEffect {
|
|||
return false;
|
||||
}
|
||||
EmptyToken token = new EmptyToken();
|
||||
CardUtil.copyTo(token).from(card); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
|
||||
CardUtil.copyTo(token).from(card, game); // needed so that entersBattlefied triggered abilities see the attributes (e.g. Master Biomancer)
|
||||
token.getColor(game).setColor(ObjectColor.BLACK);
|
||||
token.addSubType(game, SubType.ZOMBIE);
|
||||
token.getManaCost().clear();
|
||||
|
|
|
|||
|
|
@ -159,18 +159,30 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
|||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.KICKED, source.getSourceId(), source, source.getControllerId()));
|
||||
}
|
||||
|
||||
private String getActivationKey(Ability source, String costText, Game game) {
|
||||
int zcc = 0;
|
||||
if (source.getAbilityType() == AbilityType.TRIGGERED) {
|
||||
zcc = source.getSourceObjectZoneChangeCounter();
|
||||
}
|
||||
/**
|
||||
* Return activation zcc key for searching spell's settings in source object
|
||||
*
|
||||
* @param source
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
public static String getActivationKey(Ability source, Game game) {
|
||||
// must use ZCC from the moment of spell's ability activation
|
||||
int zcc = source.getSourceObjectZoneChangeCounter();
|
||||
if (zcc == 0) {
|
||||
// if ability is not activated yet (example: triggered ability checking the kicker conditional)
|
||||
zcc = game.getState().getZoneChangeCounter(source.getSourceId());
|
||||
}
|
||||
if (zcc > 0 && (source.getAbilityType() == AbilityType.TRIGGERED)) {
|
||||
|
||||
// triggers or activated abilities moves to stack and card's ZCC is changed -- so you must use workaround to find spell's zcc
|
||||
if (source.getAbilityType() == AbilityType.TRIGGERED || source.getAbilityType() == AbilityType.ACTIVATED) {
|
||||
--zcc;
|
||||
}
|
||||
return zcc + ((kickerCosts.size() > 1) ? costText : "");
|
||||
return zcc + "";
|
||||
}
|
||||
|
||||
private String getActivationKey(Ability source, String costText, Game game) {
|
||||
return getActivationKey(source, game) + ((kickerCosts.size() > 1) ? costText : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
package mage.abilities.mana.conditional;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.ConditionalMana;
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
|
|
@ -20,8 +19,9 @@ import mage.game.Game;
|
|||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class ConditionalSpellManaBuilder extends ConditionalManaBuilder {
|
||||
|
|
@ -66,9 +66,9 @@ class SpellCastManaCondition extends ManaCondition implements Condition {
|
|||
if (source instanceof SpellAbility) {
|
||||
MageObject object = game.getObject(source.getSourceId());
|
||||
if (game.inCheckPlayableState() && object instanceof Card) {
|
||||
Spell spell = new Spell((Card) object, (SpellAbility) source, source.getControllerId(), game.getState().getZone(source.getSourceId()));
|
||||
return spell != null && filter.match(spell, source.getSourceId(), source.getControllerId(), game);
|
||||
}
|
||||
Spell spell = new Spell((Card) object, (SpellAbility) source, source.getControllerId(), game.getState().getZone(source.getSourceId()), game);
|
||||
return filter.match(spell, source.getSourceId(), source.getControllerId(), game);
|
||||
}
|
||||
if ((object instanceof StackObject)) {
|
||||
return filter.match((StackObject) object, source.getSourceId(), source.getControllerId(), game);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue