Added some test and some minor fixes to effect ability handling.

This commit is contained in:
LevelX2 2015-06-04 13:50:39 +02:00
parent 961e292bc9
commit 53396a44f2
11 changed files with 256 additions and 76 deletions

View file

@ -972,9 +972,11 @@ public class ComputerPlayer extends PlayerImpl implements Player {
@Override
public boolean activateAbility(ActivatedAbility ability, Game game) {
for (Target target: ability.getModes().getMode().getTargets()) {
for (UUID targetId: target.getTargets()) {
game.fireEvent(GameEvent.getEvent(EventType.TARGETED, targetId, ability.getId(), ability.getControllerId()));
if (!isTestMode()) { // Test player already sends target event as he selects the target
for (Target target: ability.getModes().getMode().getTargets()) {
for (UUID targetId: target.getTargets()) {
game.fireEvent(GameEvent.getEvent(EventType.TARGETED, targetId, ability.getId(), ability.getControllerId()));
}
}
}
return super.activateAbility(ability, game);

View file

@ -61,7 +61,6 @@ public class PublicExecution extends CardImpl {
super(ownerId, 105, "Public Execution", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{5}{B}");
this.expansionSetCode = "M13";
// Destroy target creature an opponent controls. Each other creature that player controls gets -2/-0 until end of turn.
this.getSpellAbility().addEffect(new DestroyTargetEffect());
this.getSpellAbility().addTarget(new TargetCreaturePermanent(filter));

View file

@ -52,6 +52,7 @@ public class Shatter extends CardImpl {
super(ownerId, 105, "Shatter", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{R}");
this.expansionSetCode = "MRD";
// Destroy target artifact.
this.getSpellAbility().addEffect(new DestroyTargetEffect());
this.getSpellAbility().addTarget(new TargetPermanent(filter));
}

View file

@ -29,16 +29,15 @@
package mage.sets.tenthedition;
import java.util.UUID;
import mage.ObjectColor;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SpellCastAllTriggeredAbility;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.common.GainLifeEffect;
import mage.cards.CardImpl;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.stack.Spell;
import mage.filter.FilterSpell;
import mage.filter.predicate.mageobject.ColorPredicate;
/**
*
@ -46,10 +45,18 @@ import mage.game.stack.Spell;
*/
public class DemonsHorn extends CardImpl {
private final static FilterSpell filter = new FilterSpell("a black spell");
static {
filter.add(new ColorPredicate(ObjectColor.BLACK));
}
public DemonsHorn(UUID ownerId) {
super(ownerId, 320, "Demon's Horn", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{2}");
this.expansionSetCode = "10E";
this.addAbility(new DemonsHornAbility());
// Whenever a player casts a black spell, you may gain 1 life.
this.addAbility(new SpellCastAllTriggeredAbility(new GainLifeEffect(new StaticValue(1), "you may gain 1 life"), filter, true));
}
public DemonsHorn(final DemonsHorn card) {
@ -62,36 +69,3 @@ public class DemonsHorn extends CardImpl {
}
}
class DemonsHornAbility extends TriggeredAbilityImpl {
public DemonsHornAbility() {
super(Zone.BATTLEFIELD, new GainLifeEffect(1), true);
}
public DemonsHornAbility(final DemonsHornAbility ability) {
super(ability);
}
@Override
public DemonsHornAbility copy() {
return new DemonsHornAbility(this);
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == EventType.SPELL_CAST) {
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell != null && spell.getColor().isBlack()) {
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever a player casts a black spell, you may gain 1 life.";
}
}

View file

@ -0,0 +1,83 @@
/*
* 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 org.mage.test.cards.continuous;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class MightOfOldKrosaTest extends CardTestPlayerBase {
@Test
public void testTwiceMightOfOldKrosaBeginCombat() {
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
addCard(Zone.HAND, playerA, "Might of Old Krosa", 2);
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Might of Old Krosa", "Silvercoat Lion");
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Might of Old Krosa", "Silvercoat Lion");
setStopAt(1, PhaseStep.END_COMBAT);
execute();
assertGraveyardCount(playerA, "Might of Old Krosa", 2);
assertPermanentCount(playerA, "Silvercoat Lion", 1);
assertPowerToughness(playerA, "Silvercoat Lion", 6, 6);
}
/**
* Threw two Might of old Krosa's onto a creature, but only one had any effect.
*/
@Test
public void testTwiceMightOfOldKrosa() {
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
addCard(Zone.HAND, playerA, "Might of Old Krosa", 2);
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Might of Old Krosa", "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Might of Old Krosa", "Silvercoat Lion");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertGraveyardCount(playerA, "Might of Old Krosa", 2);
assertPermanentCount(playerA, "Silvercoat Lion", 1);
assertPowerToughness(playerA, "Silvercoat Lion", 10, 10);
}
}

View file

@ -316,7 +316,7 @@ public class PhantasmalImageTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Frost Titan");
addCard(Zone.HAND, playerA, "Terror");
// {1}{U} - Target creature gains shroud until end of turn and can't be blocked this turn.
addCard(Zone.HAND, playerA, "Veil of Secrecy");
addCard(Zone.HAND, playerA, "Veil of Secrecy");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
@ -328,32 +328,31 @@ public class PhantasmalImageTest extends CardTestPlayerBase {
setChoice(playerB, "Frost Titan");
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Terror", "Frost Titan"); // of player Bs Phantasmal Image copying Frost Titan
// should be countered if not paying {2}
// should be countered if not paying {2}
setStopAt(2, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, "Veil of Secrecy", 1);
assertGraveyardCount(playerA, "Terror", 1);
assertLife(playerB, 20);
assertLife(playerA, 20);
assertPermanentCount(playerA, "Frost Titan", 1);
assertPermanentCount(playerA, "Frost Titan", 1);
assertGraveyardCount(playerB, "Phantasmal Image", 1); // if triggered ability did not work, the Titan would be in the graveyard instaed
}
// I've casted a Phantasmal Image targeting opponent's Wurmcoil Engine
// When my Phantasmal Image died, it didn't triggered the Wurmcoil Engine's last ability
// (When Wurmcoil Engine dies, put a 3/3 colorless Wurm artifact creature token with deathtouch and
// a 3/3 colorless Wurm artifact creature token with lifelink onto the battlefield.)
@Test
public void testDiesTriggeredAbilities() {
addCard(Zone.BATTLEFIELD, playerA, "Wurmcoil Engine");
// Destroy target artifact or enchantment.
// Destroy target creature an opponent controls. Each other creature that player controls gets -2/-0 until end of turn.
addCard(Zone.HAND, playerA, "Public Execution");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
@ -364,22 +363,124 @@ public class PhantasmalImageTest extends CardTestPlayerBase {
setChoice(playerB, "Wurmcoil Engine");
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution", "Wurmcoil Engine"); // of player Bs Phantasmal Image copying Frost Titan
// should be countered if not paying {2}
// should be countered if not paying {2}
setStopAt(2, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, "Public Execution", 1);
assertLife(playerB, 20);
assertLife(playerA, 20);
assertPermanentCount(playerA, "Wurmcoil Engine", 1);
assertPermanentCount(playerA, "Wurmcoil Engine", 1);
assertGraveyardCount(playerB, "Phantasmal Image", 1);
assertPermanentCount(playerB, "Wurm", 2); // if triggered ability did not work, the Titan would be in the graveyard instaed
}
/**
* Phantasmal Image is not regestering Leave the battlefield triggers,
* persist and undying triggers
*/
@Test
public void testLeavesTheBattlefieldTriggeredAbilities() {
// Shadow (This creature can block or be blocked by only creatures with shadow.)
// When Thalakos Seer leaves the battlefield, draw a card.
addCard(Zone.BATTLEFIELD, playerA, "Thalakos Seer");
// Destroy target creature an opponent controls. Each other creature that player controls gets -2/-0 until end of turn.
addCard(Zone.HAND, playerA, "Public Execution");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
addCard(Zone.HAND, playerB, "Phantasmal Image");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); // not targeted
setChoice(playerB, "Thalakos Seer");
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution", "Thalakos Seer");
setStopAt(2, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, "Public Execution", 1);
assertLife(playerB, 20);
assertLife(playerA, 20);
assertPermanentCount(playerA, "Thalakos Seer", 1);
assertGraveyardCount(playerB, "Phantasmal Image", 1);
assertHandCount(playerB, 2); // 1 from draw turn 2 and 1 from Thalakos Seer leaves the battlefield trigger
}
/**
* Action
* Game State 1 -----------------> Game State 2
* (On 'field) (Move to GY) (In graveyard)
*
* LTB abilities such as Persist are expceptional in that they trigger based on their existence and
* state of objects before the event (Game State 1, when the card is on the battlefield) rather than
* after (Game State 2, when the card is in the graveyard). It doesn't matter that the LTB ability
* doesn't exist in Game State 2. [CR 603.6d]
*
* 603.6d Normally, objects that exist immediately after an event are checked to see if the event matched any trigger conditions.
* Continuous effects that exist at that time are used to determine what the trigger conditions are and what the objects involved
* in the event look like. However, some triggered abilities must be treated specially. Leaves-the-battlefield abilities, abilities
* that trigger when a permanent phases out, abilities that trigger when an object that all players can see is put into a hand or
* library, abilities that trigger specifically when an object becomes unattached, abilities that trigger when a player loses control
* of an object, and abilities that trigger when a player planeswalks away from a plane will trigger based on their existence, and
* the appearance of objects, prior to the event rather than afterward. The game has to look back in time to determine if these abilities trigger.
*
* Example: Two creatures are on the battlefield along with an artifact that has the ability Whenever a creature dies, you gain 1 life.
* Someone plays a spell that destroys all artifacts, creatures, and enchantments. The artifacts ability triggers twice, even though
* the artifact goes to its owners graveyard at the same time as the creatures.
*
*/
@Test
public void testPersist() {
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
// When Kitchen Finks enters the battlefield, you gain 2 life.
// Persist (When this creature dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it.)
addCard(Zone.HAND, playerA, "Kitchen Finks");
// Destroy target creature an opponent controls. Each other creature that player controls gets -2/-0 until end of turn.
addCard(Zone.HAND, playerA, "Public Execution");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
// You may have Phantasmal Image enter the battlefield as a copy of any creature
// on the battlefield, except it's an Illusion in addition to its other types and
// it gains "When this creature becomes the target of a spell or ability, sacrifice it."
addCard(Zone.HAND, playerB, "Phantasmal Image");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Kitchen Finks");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image"); // not targeted
setChoice(playerB, "Kitchen Finks");
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, "Public Execution", "Kitchen Finks");
setChoice(playerB, "Kitchen Finks");
setStopAt(2, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, "Public Execution", 1);
assertLife(playerA, 22);
assertLife(playerB, 24);
assertPermanentCount(playerA, "Kitchen Finks", 1);
assertHandCount(playerB, "Phantasmal Image", 0);
assertGraveyardCount(playerB, "Phantasmal Image", 0);
assertPermanentCount(playerB, "Kitchen Finks", 1);
assertPowerToughness(playerB, "Kitchen Finks", 2, 1);
}
}

View file

@ -25,11 +25,15 @@ public class SynodCenturionTest extends CardTestPlayerBase {
@Test
public void testAlone() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
// Whenever a player casts a black spell, you may gain 1 life.
addCard(Zone.BATTLEFIELD, playerA, "Demon's Horn");
// Destroy target artifact.
addCard(Zone.HAND, playerA, "Shatter");
// When you control no other artifacts, sacrifice Synod Centurion.
addCard(Zone.HAND, playerA, "Synod Centurion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Synod Centurion");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Shatter", "Demon's Horn");
setStopAt(1, PhaseStep.END_TURN);

View file

@ -32,6 +32,7 @@ import java.util.UUID;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
*
@ -48,23 +49,29 @@ public abstract class StateTriggeredAbility extends TriggeredAbilityImpl {
}
@Override
public void trigger(Game game, UUID controllerId) {
public final boolean checkEventType(GameEvent event, Game game) {
//20100716 - 603.8
Boolean triggered = (Boolean) game.getState().getValue(this.getSourceId().toString() + "triggered");
Boolean triggered = (Boolean) game.getState().getValue(getSourceId().toString() + "triggered");
if (triggered == null) {
triggered = Boolean.FALSE;
}
if (!triggered) {
game.getState().setValue(this.getSourceId().toString() + "triggered", Boolean.TRUE);
super.trigger(game, controllerId);
}
return !triggered;
}
@Override
public void trigger(Game game, UUID controllerId) {
//20100716 - 603.8
game.getState().setValue(this.getSourceId().toString() + "triggered", Boolean.TRUE);
super.trigger(game, controllerId);
}
@Override
public boolean resolve(Game game) {
//20100716 - 603.8
boolean result = super.resolve(game);
game.getState().setValue(this.getSourceId().toString() + "triggered", Boolean.FALSE);
return super.resolve(game);
return result;
}
public void counter(Game game) {

View file

@ -32,7 +32,6 @@ import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
/**
*
@ -60,7 +59,7 @@ public class BecomesTargetTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return event.getTargetId().equals(sourceId);
return event.getTargetId().equals(getSourceId());
}
@Override

View file

@ -28,6 +28,7 @@
package mage.abilities.effects.common;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
@ -56,13 +57,17 @@ public class SacrificeSourceEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
MageObject sourceObject = source.getSourceObjectIfItStillExists(game);
if (sourceObject instanceof Permanent) {
Permanent permanent = (Permanent) sourceObject;
// you can only sacrifice a permanent you control
if (source.getControllerId().equals(permanent.getControllerId())) {
return permanent.sacrifice(source.getSourceId(), game);
}
return true;
} else {
// no permanent?
sourceObject.getName();
}
return false;
}

View file

@ -35,10 +35,11 @@ import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.EntersTheBattlefieldEvent;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
@ -68,9 +69,11 @@ public class PersistAbility extends DiesTriggeredAbility {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (super.checkTrigger(event, game)) {
Permanent p = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD);
if (p.getCounters().getCount(CounterType.M1M1) == 0) {
game.getState().setValue("persist" + getSourceId().toString(), new FixedTarget(p.getId()));
Permanent permanent = ((ZoneChangeEvent) event).getTarget();
if (permanent.getCounters().getCount(CounterType.M1M1) == 0) {
FixedTarget fixedTarget = new FixedTarget(permanent.getId());
fixedTarget.init(game, this);
game.getState().setValue("persist" + getSourceId().toString(), fixedTarget);
return true;
}
}
@ -109,7 +112,7 @@ class PersistEffect extends OneShotEffect {
class PersistReplacementEffect extends ReplacementEffectImpl {
PersistReplacementEffect() {
super(Duration.OneUse, Outcome.UnboostCreature, false);
super(Duration.Custom, Outcome.UnboostCreature, false);
selfScope = true;
staticText = "return it to the battlefield under its owner's control with a -1/-1 counter on it";
}
@ -129,7 +132,7 @@ class PersistReplacementEffect extends ReplacementEffectImpl {
if (permanent != null) {
permanent.addCounters(CounterType.M1M1.createInstance(), game);
}
used = true;
discard();
return false;
}
@ -142,7 +145,9 @@ class PersistReplacementEffect extends ReplacementEffectImpl {
public boolean applies(GameEvent event, Ability source, Game game) {
if (event.getTargetId().equals(source.getSourceId())) {
Object fixedTarget = game.getState().getValue("persist" + source.getSourceId().toString());
if (fixedTarget instanceof FixedTarget && ((FixedTarget) fixedTarget).getFirst(game, source).equals(source.getSourceId())) {
if (fixedTarget instanceof FixedTarget && ((FixedTarget) fixedTarget).getTarget().equals(source.getSourceId()) &&
((FixedTarget) fixedTarget).getZoneChangeCounter() + 1 == game.getState().getZoneChangeCounter(source.getSourceId())) {
return true;
}
}