mirror of
https://github.com/correl/mage.git
synced 2024-12-24 11:50:45 +00:00
Tyvar Kell and gain ability fixes:
* GainAbilityTargetEffect - reworked to support static/dynamic targets, added support of spells (card + related permanent); * SpellCastControllerTriggeredAbility - now it can setup the target to a card instead a spell; * Added checks/errors on wrong ability adding code (example: if you add permanent's ability by game state instead permanent's method); * Tyvar Kell Emblem now use a standard code; * Test framework: added additional logs for some errors;
This commit is contained in:
parent
f131fd0d12
commit
6dcbcbe962
13 changed files with 291 additions and 140 deletions
|
@ -0,0 +1,87 @@
|
|||
package org.mage.test.cards.single.khm;
|
||||
|
||||
import mage.abilities.keyword.HasteAbility;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class TyvarKellTest extends CardTestPlayerBase {
|
||||
|
||||
@Test
|
||||
public void test_Emblem_Normal() {
|
||||
removeAllCardsFromHand(playerA);
|
||||
|
||||
// −6: You get an emblem with "Whenever you cast an Elf spell, it gains haste until end of turn and you draw two cards."
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Tyvar Kell");
|
||||
//
|
||||
addCard(Zone.HAND, playerA, "Arbor Elf", 1); // {G}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
|
||||
|
||||
// prepare emblem
|
||||
addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tyvar Kell", CounterType.LOYALTY, 10);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-6:");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
checkHandCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 1); // 1x elf
|
||||
|
||||
// cast elf and draw
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Elf");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
checkHandCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2); // 2x from draw
|
||||
checkAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Elf", HasteAbility.class, true);
|
||||
|
||||
// check that it can attack
|
||||
attack(1, playerA, "Arbor Elf", playerB);
|
||||
|
||||
// haste must end on next turn
|
||||
checkAbility("end", 2, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Elf", HasteAbility.class, false);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(2, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertLife(playerB, 20 - 1); // 1x from elf
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Emblem_Blink() {
|
||||
removeAllCardsFromHand(playerA);
|
||||
|
||||
// −6: You get an emblem with "Whenever you cast an Elf spell, it gains haste until end of turn and you draw two cards."
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Tyvar Kell");
|
||||
//
|
||||
addCard(Zone.HAND, playerA, "Arbor Elf", 1); // {G}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
|
||||
//
|
||||
// Exile target creature you control, then return that card to the battlefield under your control.
|
||||
addCard(Zone.HAND, playerA, "Cloudshift", 1); // {W}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
|
||||
|
||||
// prepare emblem
|
||||
addCounters(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tyvar Kell", CounterType.LOYALTY, 10);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-6:");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
checkHandCount("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2); // 1x elf + 1x shift
|
||||
|
||||
// cast elf and draw
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Elf");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
checkHandCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 3); // 2x from draw + 1x shift
|
||||
checkAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Elf", HasteAbility.class, true);
|
||||
|
||||
// blink elf, so it must lose haste
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloudshift", "Arbor Elf");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
checkAbility("blink", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Elf", HasteAbility.class, false);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
}
|
||||
}
|
|
@ -29,8 +29,10 @@ public class HallOfTheBanditLordTest extends CardTestPlayerBase {
|
|||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Roughrider");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
this.assertAbility(playerA, "Goblin Roughrider", HasteAbility.getInstance(), true);
|
||||
}
|
||||
|
|
|
@ -1392,7 +1392,12 @@ public class TestPlayer implements Player {
|
|||
}
|
||||
|
||||
private void assertHandCount(PlayerAction action, Game game, Player player, int count) {
|
||||
Assert.assertEquals(action.getActionName() + " - hand must contain " + count, count, player.getHand().size());
|
||||
if (player.getHand().size() != count) {
|
||||
printStart("Hand of " + player.getName());
|
||||
printCards(player.getHand().getCards(game));
|
||||
printEnd();
|
||||
Assert.fail(action.getActionName() + " - hand must contain " + count + ", but found " + player.getHand().size());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertHandCardCount(PlayerAction action, Game game, Player player, String cardName, int count) {
|
||||
|
@ -1416,7 +1421,12 @@ public class TestPlayer implements Player {
|
|||
}
|
||||
}
|
||||
|
||||
Assert.assertEquals(action.getActionName() + " - command zone must contain " + count + " cards of " + cardName, count, realCount);
|
||||
if (realCount != count) {
|
||||
printStart("Cards in command zone from " + player.getName());
|
||||
printCards(game.getCommanderCardsFromCommandZone(player));
|
||||
printEnd();
|
||||
Assert.fail(action.getActionName() + " - must have " + count + " cards with name " + cardName + ", but found " + realCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertColor(PlayerAction action, Game game, Player player, String permanentName, String colors, boolean mustHave) {
|
||||
|
|
|
@ -15,14 +15,14 @@ import mage.target.targetpointer.FixedTarget;
|
|||
public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
|
||||
|
||||
private static final FilterSpell spellCard = new FilterSpell("a spell");
|
||||
|
||||
protected FilterSpell filter;
|
||||
protected String rule;
|
||||
|
||||
/**
|
||||
* If true, the source that triggered the ability will be set as target to
|
||||
* effect.
|
||||
*/
|
||||
// The source SPELL that triggered the ability will be set as target to effect
|
||||
protected boolean rememberSource = false;
|
||||
// Use it if you want remember CARD instead spell
|
||||
protected boolean rememberSourceAsCard = false;
|
||||
|
||||
public SpellCastControllerTriggeredAbility(Effect effect, boolean optional) {
|
||||
this(Zone.BATTLEFIELD, effect, spellCard, optional, false);
|
||||
|
@ -42,16 +42,22 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
|
|||
}
|
||||
|
||||
public SpellCastControllerTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, boolean rememberSource) {
|
||||
this(zone, effect, filter, optional, rememberSource, false);
|
||||
}
|
||||
|
||||
public SpellCastControllerTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, boolean rememberSource, boolean rememberSourceAsCard) {
|
||||
super(zone, effect, optional);
|
||||
this.filter = filter;
|
||||
this.rememberSource = rememberSource;
|
||||
this.rememberSourceAsCard = rememberSourceAsCard;
|
||||
}
|
||||
|
||||
public SpellCastControllerTriggeredAbility(final SpellCastControllerTriggeredAbility ability) {
|
||||
super(ability);
|
||||
this.filter = ability.filter;
|
||||
this.rememberSource = ability.rememberSource;
|
||||
this.rule = ability.rule;
|
||||
this.rememberSource = ability.rememberSource;
|
||||
this.rememberSourceAsCard = ability.rememberSourceAsCard;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -65,7 +71,12 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
|
|||
Spell spell = game.getStack().getSpell(event.getTargetId());
|
||||
if (spell != null && filter.match(spell, getSourceId(), getControllerId(), game)) {
|
||||
if (rememberSource) {
|
||||
this.getEffects().get(0).setTargetPointer(new FixedTarget(spell.getId(), game));
|
||||
if (rememberSourceAsCard) {
|
||||
this.getEffects().get(0).setTargetPointer(new FixedTarget(spell.getCard().getId(), game));
|
||||
} else {
|
||||
this.getEffects().get(0).setTargetPointer(new FixedTarget(spell.getId(), game));
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -33,8 +33,18 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
protected long order;
|
||||
protected boolean used = false;
|
||||
protected boolean discarded = false; // for manual effect discard
|
||||
|
||||
// 611.2c
|
||||
// Two types of affected objects (targets):
|
||||
// 1. Static targets - setup it on ability resolve or effect creation (effect's init method, only once)
|
||||
// 2. Dynamic targets - can be changed after resolve, so use targetPointer, filters or own lists to find affected objects
|
||||
//
|
||||
// If your ability/effect supports multi use cases (one time use, static, target pointers) then affectedObjectsSet can be useful:
|
||||
// * affectedObjectsSet - true on static objects and false on dynamic objects (see rules from 611.2c)
|
||||
// Full implement example: GainAbilityTargetEffect
|
||||
protected boolean affectedObjectsSet = false;
|
||||
protected List<MageObjectReference> affectedObjectList = new ArrayList<>();
|
||||
|
||||
protected boolean temporary = false;
|
||||
protected EnumSet<DependencyType> dependencyTypes; // this effect has the dependencyTypes defined here
|
||||
protected EnumSet<DependencyType> dependendToTypes; // this effect is dependent to this types
|
||||
|
@ -154,10 +164,18 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
@Override
|
||||
public void init(Ability source, Game game, UUID activePlayerId) {
|
||||
targetPointer.init(game, source);
|
||||
//20100716 - 611.2c
|
||||
if (AbilityType.ACTIVATED == source.getAbilityType()
|
||||
|| AbilityType.SPELL == source.getAbilityType()
|
||||
|| AbilityType.TRIGGERED == source.getAbilityType()) {
|
||||
// 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
|
||||
// determined when that continuous effect begins. After that point, the set won’t change.
|
||||
// (Note that this works differently than a continuous effect from a static ability.)
|
||||
// A continuous effect generated by the resolution of a spell or ability that doesn’t
|
||||
// modify the characteristics or change the controller of any objects modifies the
|
||||
// rules of the game, so it can affect objects that weren’t affected when that continuous
|
||||
// effect began.If a single continuous effect has parts that modify the characteristics or
|
||||
// changes the controller of any objects and other parts that don’t, the set of objects
|
||||
// each part applies to is determined independently.
|
||||
if (AbilityType.STATIC != source.getAbilityType()) {
|
||||
if (layer != null) {
|
||||
switch (layer) {
|
||||
case CopyEffects_1:
|
||||
|
|
|
@ -19,7 +19,10 @@ public abstract class EffectImpl implements Effect {
|
|||
protected UUID id;
|
||||
protected Outcome outcome;
|
||||
protected EffectType effectType;
|
||||
|
||||
// read related docs about static and dynamic targets in ContinuousEffectImpl.affectedObjectsSet
|
||||
protected TargetPointer targetPointer = FirstTargetPointer.getInstance();
|
||||
|
||||
protected String staticText = "";
|
||||
protected Map<String, Object> values;
|
||||
protected String concatPrefix = ""; // combines multiple effects in text rule
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.effects.common.continuous;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.Mode;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
|
@ -9,17 +10,18 @@ import mage.game.Game;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.target.Target;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class GainAbilityTargetEffect extends ContinuousEffectImpl {
|
||||
|
||||
protected Ability ability;
|
||||
// shall a card gain the ability (otherwise permanent)
|
||||
private final boolean onCard;
|
||||
|
||||
// shall a card gain the ability (otherwise a permanent)
|
||||
private final boolean useOnCard; // only one card per ability supported
|
||||
private boolean waitingCardPermanent = false; // wait the permanent from card's resolve (for inner usage only)
|
||||
|
||||
// Duration until next phase step of player
|
||||
private PhaseStep durationPhaseStep = null;
|
||||
|
@ -34,15 +36,15 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
|
|||
this(ability, duration, rule, false);
|
||||
}
|
||||
|
||||
public GainAbilityTargetEffect(Ability ability, Duration duration, String rule, boolean onCard) {
|
||||
this(ability, duration, rule, onCard, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA);
|
||||
public GainAbilityTargetEffect(Ability ability, Duration duration, String rule, boolean useOnCard) {
|
||||
this(ability, duration, rule, useOnCard, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA);
|
||||
}
|
||||
|
||||
public GainAbilityTargetEffect(Ability ability, Duration duration, String rule, boolean onCard, Layer layer, SubLayer subLayer) {
|
||||
public GainAbilityTargetEffect(Ability ability, Duration duration, String rule, boolean useOnCard, Layer layer, SubLayer subLayer) {
|
||||
super(duration, layer, subLayer, ability.getEffects().getOutcome(ability, Outcome.AddAbility));
|
||||
this.ability = ability;
|
||||
staticText = rule;
|
||||
this.onCard = onCard;
|
||||
this.staticText = rule;
|
||||
this.useOnCard = useOnCard;
|
||||
|
||||
this.generateGainAbilityDependencies(ability, null);
|
||||
}
|
||||
|
@ -50,8 +52,9 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
|
|||
public GainAbilityTargetEffect(final GainAbilityTargetEffect effect) {
|
||||
super(effect);
|
||||
this.ability = effect.ability.copy();
|
||||
ability.newId(); // This is needed if the effect is copied e.g. by a clone so the ability can be added multiple times to permanents
|
||||
this.onCard = effect.onCard;
|
||||
this.ability.newId(); // This is needed if the effect is copied e.g. by a clone so the ability can be added multiple times to permanents
|
||||
this.useOnCard = effect.useOnCard;
|
||||
this.waitingCardPermanent = effect.waitingCardPermanent;
|
||||
this.durationPhaseStep = effect.durationPhaseStep;
|
||||
this.durationPlayerId = effect.durationPlayerId;
|
||||
this.sameStep = effect.sameStep;
|
||||
|
@ -70,10 +73,38 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
|
|||
@Override
|
||||
public void init(Ability source, Game game) {
|
||||
super.init(source, game);
|
||||
|
||||
if (durationPhaseStep != null) {
|
||||
durationPlayerId = source.getControllerId();
|
||||
sameStep = true;
|
||||
}
|
||||
|
||||
// must support dynamic targets from static ability and static targets from activated abilities
|
||||
if (this.affectedObjectsSet) {
|
||||
// target permanents (by default)
|
||||
targetPointer.getTargets(game, source)
|
||||
.stream()
|
||||
.map(game::getPermanent)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(permanent -> {
|
||||
this.affectedObjectList.add(new MageObjectReference(permanent, game));
|
||||
});
|
||||
|
||||
// target cards with linked permanents
|
||||
if (this.useOnCard) {
|
||||
targetPointer.getTargets(game, source)
|
||||
.stream()
|
||||
.map(game::getCard)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(card -> {
|
||||
this.affectedObjectList.add(new MageObjectReference(card, game));
|
||||
});
|
||||
waitingCardPermanent = true;
|
||||
if (this.affectedObjectList.size() > 1) {
|
||||
throw new IllegalArgumentException("Gain ability can't target a multiple cards (unsupported)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -97,29 +128,74 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
int affectedTargets = 0;
|
||||
if (onCard) {
|
||||
for (UUID cardId : targetPointer.getTargets(game, source)) {
|
||||
Card card = game.getCard(cardId);
|
||||
if (card != null) {
|
||||
game.getState().addOtherAbility(card, ability);
|
||||
if (affectedObjectsSet) {
|
||||
// STATIC TARGETS
|
||||
List<MageObjectReference> newWaitingPermanents = new ArrayList<>();
|
||||
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) {
|
||||
MageObjectReference mor = it.next();
|
||||
|
||||
// look for permanent
|
||||
Permanent permanent = mor.getPermanent(game);
|
||||
if (permanent != null) {
|
||||
this.waitingCardPermanent = false;
|
||||
permanent.addAbility(ability, source.getSourceId(), game);
|
||||
affectedTargets++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// look for card with linked permanent
|
||||
if (this.useOnCard) {
|
||||
Card card = mor.getCard(game);
|
||||
if (card != null) {
|
||||
game.getState().addOtherAbility(card, ability);
|
||||
affectedTargets++;
|
||||
continue;
|
||||
} else {
|
||||
// start waiting a spell's permanent (example: Tyvar Kell's emblem)
|
||||
Permanent perm = game.getPermanent(mor.getSourceId());
|
||||
if (perm != null) {
|
||||
newWaitingPermanents.add(new MageObjectReference(perm, game));
|
||||
this.waitingCardPermanent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// bad target, can be removed
|
||||
it.remove();
|
||||
}
|
||||
if (duration == Duration.OneUse) {
|
||||
|
||||
// add new linked permanents to targets
|
||||
if (!newWaitingPermanents.isEmpty()) {
|
||||
this.affectedObjectList.addAll(newWaitingPermanents);
|
||||
return affectedTargets > 0;
|
||||
}
|
||||
|
||||
// no more valid targets
|
||||
if (this.affectedObjectList.isEmpty()) {
|
||||
discard();
|
||||
}
|
||||
|
||||
// no more valid permanents (card was countered without new permanent)
|
||||
if (duration == Duration.Custom && affectedTargets == 0 && !this.waitingCardPermanent) {
|
||||
discard();
|
||||
}
|
||||
} else {
|
||||
for (UUID permanentId : targetPointer.getTargets(game, source)) {
|
||||
Permanent permanent = game.getPermanentOrLKIBattlefield(permanentId);
|
||||
// DYNAMIC TARGETS
|
||||
for (UUID objectId : targetPointer.getTargets(game, source)) {
|
||||
Permanent permanent = game.getPermanent(objectId);
|
||||
if (permanent != null) {
|
||||
permanent.addAbility(ability, source.getSourceId(), game);
|
||||
affectedTargets++;
|
||||
continue;
|
||||
}
|
||||
if (this.useOnCard) {
|
||||
Card card = game.getCard(objectId);
|
||||
if (card != null) {
|
||||
game.getState().addOtherAbility(card, ability);
|
||||
affectedTargets++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (duration == Duration.Custom && affectedTargets == 0) {
|
||||
this.discard();
|
||||
}
|
||||
return affectedTargets > 0;
|
||||
}
|
||||
|
||||
|
@ -157,5 +233,4 @@ public class GainAbilityTargetEffect extends ContinuousEffectImpl {
|
|||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
package mage.cards;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectImpl;
|
||||
import mage.Mana;
|
||||
|
@ -19,6 +16,7 @@ import mage.game.*;
|
|||
import mage.game.command.CommandObject;
|
||||
import mage.game.events.*;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.StackObject;
|
||||
import mage.util.CardUtil;
|
||||
|
@ -26,6 +24,10 @@ import mage.util.GameLog;
|
|||
import mage.watchers.Watcher;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
|
||||
public abstract class CardImpl extends MageObjectImpl implements Card {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -310,6 +312,12 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
|||
abilities.add(subAbility);
|
||||
}
|
||||
|
||||
// dynamic check: you can't add ability to the PermanentCard, use permanent.addAbility(a, source, game) instead
|
||||
// reason: triggered abilities are not processing here
|
||||
if (this instanceof PermanentCard) {
|
||||
throw new IllegalArgumentException("Wrong code usage. Don't use that method for permanents, use permanent.addAbility(a, source, game) instead.");
|
||||
}
|
||||
|
||||
// verify check: draw effect can't be rollback after mana usage (example: Chromatic Sphere)
|
||||
// (player can cheat with cancel button to see next card)
|
||||
// verify test will catch that errors
|
||||
|
@ -325,8 +333,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected void addAbilities(List<Ability> abilities) {
|
||||
|
|
|
@ -2978,7 +2978,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
// remembers if a object was in a zone during the resolution of an effect
|
||||
// e.g. Wrath destroys all and you the question is is the replacement effect to apply because the source was also moved by the same effect
|
||||
// because it ahppens all at the same time the replcaement effect has still to be applied
|
||||
// because it happens all at the same time the replacement effect has still to be applied
|
||||
Set<UUID> idSet = shortLivingLKI.computeIfAbsent(zone, k -> new HashSet<>());
|
||||
idSet.add(objectId);
|
||||
if (object instanceof Permanent) {
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
package mage.game;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import static java.util.Collections.emptyList;
|
||||
import java.util.stream.Collectors;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.*;
|
||||
|
@ -25,6 +21,7 @@ import mage.game.command.Plane;
|
|||
import mage.game.events.*;
|
||||
import mage.game.permanent.Battlefield;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
import mage.game.permanent.PermanentToken;
|
||||
import mage.game.stack.SpellStack;
|
||||
import mage.game.stack.StackObject;
|
||||
|
@ -39,6 +36,12 @@ import mage.util.ThreadLocalStringBuilder;
|
|||
import mage.watchers.Watcher;
|
||||
import mage.watchers.Watchers;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* <p>
|
||||
|
@ -1067,6 +1070,8 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
* @param ability
|
||||
*/
|
||||
public void addOtherAbility(Card attachedTo, Ability ability) {
|
||||
checkWrongDynamicAbilityUsage(attachedTo, ability);
|
||||
|
||||
addOtherAbility(attachedTo, ability, true);
|
||||
}
|
||||
|
||||
|
@ -1079,6 +1084,8 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
* state
|
||||
*/
|
||||
public void addOtherAbility(Card attachedTo, Ability ability, boolean copyAbility) {
|
||||
checkWrongDynamicAbilityUsage(attachedTo, ability);
|
||||
|
||||
Ability newAbility;
|
||||
if (ability instanceof MageSingleton || !copyAbility) {
|
||||
newAbility = ability;
|
||||
|
@ -1094,6 +1101,16 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
addAbility(newAbility, attachedTo.getId(), attachedTo);
|
||||
}
|
||||
|
||||
private void checkWrongDynamicAbilityUsage(Card attachedTo, Ability ability) {
|
||||
// dynamic abilities for card only
|
||||
// permanent's abilities are static and generated each reset cycle
|
||||
if (attachedTo instanceof PermanentCard) {
|
||||
throw new IllegalArgumentException("Error, wrong code usage. If you want to add new ability to the "
|
||||
+ "permanent then use a permanent.addAbility(a, source, game): "
|
||||
+ ability.getClass().getCanonicalName() + " - " + ability.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes Triggered abilities that belong to sourceId This is used if a
|
||||
* token leaves the battlefield
|
||||
|
|
|
@ -1,23 +1,17 @@
|
|||
package mage.game.command.emblems;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SpellCastControllerTriggeredAbility;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
|
||||
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
||||
import mage.abilities.keyword.HasteAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.*;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterSpell;
|
||||
import mage.game.Game;
|
||||
import mage.game.command.Emblem;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.Spell;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author weirddan455
|
||||
*/
|
||||
public final class TyvarKellEmblem extends Emblem {
|
||||
|
@ -32,74 +26,12 @@ public final class TyvarKellEmblem extends Emblem {
|
|||
public TyvarKellEmblem() {
|
||||
this.setName("Emblem Tyvar");
|
||||
this.setExpansionSetCodeForImage("KHM");
|
||||
Ability ability = new SpellCastControllerTriggeredAbility(Zone.COMMAND, new TyvarKellEmblemEffect(
|
||||
HasteAbility.getInstance(), Duration.EndOfTurn), filter, false, true
|
||||
|
||||
Ability ability = new SpellCastControllerTriggeredAbility(Zone.COMMAND,
|
||||
new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn, null, true),
|
||||
filter, false, true, true
|
||||
);
|
||||
ability.addEffect(new DrawCardSourceControllerEffect(2, "you").concatBy("and"));
|
||||
this.getAbilities().add(ability);
|
||||
}
|
||||
}
|
||||
|
||||
class TyvarKellEmblemEffect extends ContinuousEffectImpl {
|
||||
|
||||
protected Ability ability;
|
||||
|
||||
public TyvarKellEmblemEffect(Ability ability, Duration duration) {
|
||||
super(duration, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
|
||||
this.ability = ability;
|
||||
this.generateGainAbilityDependencies(ability, null);
|
||||
this.staticText = "it gains haste until end of turn";
|
||||
}
|
||||
|
||||
public TyvarKellEmblemEffect(final TyvarKellEmblemEffect effect) {
|
||||
super(effect);
|
||||
this.ability = effect.ability.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TyvarKellEmblemEffect copy() {
|
||||
return new TyvarKellEmblemEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init (Ability source, Game game) {
|
||||
super.init(source, game);
|
||||
if (this.affectedObjectsSet) {
|
||||
Spell spell = game.getStack().getSpell(targetPointer.getFirst(game, source));
|
||||
if (spell != null) {
|
||||
Card card = game.getCard(spell.getSourceId());
|
||||
if (card != null) {
|
||||
affectedObjectList.add(new MageObjectReference(card, game));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
if (this.affectedObjectsSet) {
|
||||
for (Iterator<MageObjectReference> it = affectedObjectList.iterator(); it.hasNext(); ) {
|
||||
MageObjectReference mor = it.next();
|
||||
Card card = mor.getCard(game);
|
||||
Permanent perm = game.getPermanent(mor.getSourceId());
|
||||
boolean applied = false;
|
||||
if (card != null && !card.hasAbility(ability, game)) {
|
||||
game.getState().addOtherAbility(card, ability);
|
||||
applied = true;
|
||||
}
|
||||
if (perm != null && perm.getZoneChangeCounter(game) == mor.getZoneChangeCounter() + 1) {
|
||||
perm.addAbility(ability, source.getSourceId(), game);
|
||||
applied = true;
|
||||
}
|
||||
if (!applied) {
|
||||
it.remove();
|
||||
if (affectedObjectList.isEmpty()) {
|
||||
discard();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,6 +176,13 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
String getValue(GameState state);
|
||||
|
||||
/**
|
||||
* Add abilities to the permanent, can be used in effects
|
||||
*
|
||||
* @param ability
|
||||
* @param sourceId
|
||||
* @param game
|
||||
*/
|
||||
void addAbility(Ability ability, UUID sourceId, Game game);
|
||||
|
||||
void removeAllAbilities(UUID sourceId, Game game);
|
||||
|
|
|
@ -47,9 +47,6 @@ public class PermanentCard extends PermanentImpl {
|
|||
if (otherAbilities != null) {
|
||||
abilities.addAll(otherAbilities);
|
||||
}
|
||||
/*if (card.getCardType().contains(CardType.PLANESWALKER)) {
|
||||
this.loyalty = new MageInt(card.getLoyalty().getValue());
|
||||
}*/
|
||||
if (card instanceof LevelerCard) {
|
||||
maxLevelCounters = ((LevelerCard) card).getMaxLevelCounters();
|
||||
}
|
||||
|
@ -89,6 +86,7 @@ public class PermanentCard extends PermanentImpl {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// copy only own abilities; all dynamic added abilities must be added in the parent call
|
||||
this.abilities = card.getAbilities().copy();
|
||||
// only set spellAbility to null if it has no targets IE: Dance of the Dead bug #7031
|
||||
if (this.getSpellAbility() != null
|
||||
|
@ -96,21 +94,6 @@ public class PermanentCard extends PermanentImpl {
|
|||
this.spellAbility = null; // will be set on first getSpellAbility call if card has one.
|
||||
}
|
||||
}
|
||||
// adventure cards must show adventure spell info on battlefield too
|
||||
/*
|
||||
if (card instanceof AdventureCard) {
|
||||
// Adventure card spell abilities should not appear on permanents.
|
||||
List<Ability> toRemove = new ArrayList<Ability>();
|
||||
for (Ability ability : this.abilities) {
|
||||
if (ability instanceof SpellAbility) {
|
||||
if (((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.ADVENTURE_SPELL) {
|
||||
toRemove.add(ability);
|
||||
}
|
||||
}
|
||||
}
|
||||
toRemove.forEach(ability -> this.abilities.remove(ability));
|
||||
}
|
||||
*/
|
||||
this.abilities.setControllerId(this.controllerId);
|
||||
this.abilities.setSourceId(objectId);
|
||||
this.cardType.clear();
|
||||
|
|
Loading…
Reference in a new issue