Help implementing a card
Moderators: North, BetaSteward, noxx, jeffwadsworth, JayDi, TheElk801, LevelX, CCGHQ Admins
Re: Help implementing a card
by djbrez » 04 Mar 2016, 17:18
its funny, this is exactly the one I copied... the problem I had was that I couldn't limit the damage prevention to only Uncle Istvan... PreventAllDamageByAllEffect appeared to extend the protection all creatures. The part I seem to be missing is how to limit the protection to just this one card. I think I have to add an additional filter to do that, not sure how.escplan9 wrote:What I like to do is use gatherer . wizards . com advanced search to find similar cards. For instance I put in rules text to search for "prevent all damage" "by creatures" and results are at link below.
Uncle Istvan is amongst those cards as you'll see. Check out the other similar cards there and how they are implemented in XMage.
For instance, Ethereal Haze uses this for a similar effect:
- Code: Select all
this.getSpellAbility().addEffect(new PreventAllDamageByAllEffect(new FilterCreaturePermanent("creatures"), Duration.EndOfTurn, false));
thanks!!
Don
Re: Help implementing a card
by escplan9 » 04 Mar 2016, 17:25
With regards to Glint Hawk, Faerie Impostor and Drake Familiar, I created a new pull request for the bug fixes and new card. Tested good. Having some issues with the test project at the moment (trying to do the mvn clean install -DskipTests now to see if that resolves the weird errors I'm seeing), but have tests that should work for the cards as well up shortly.
edit: the Maven clean command fixed up the issues. Added the tests to the pull request.
edit: the Maven clean command fixed up the issues. Added the tests to the pull request.
Re: Help implementing a card
by escplan9 » 04 Mar 2016, 19:36
I see what you mean. Possibly look around in mage.filter.predicate for something that can be used so the FilterCreatePermanent is only with regards to the source (Uncle Istvan). I'm sure there's an easy way that I'm just not seeing (I'm pretty new to developing in XMage myself, so it hasn't jumped out to me either). Also ondjbrez wrote:its funny, this is exactly the one I copied... the problem I had was that I couldn't limit the damage prevention to only Uncle Istvan... PreventAllDamageByAllEffect appeared to extend the protection all creatures. The part I seem to be missing is how to limit the protection to just this one card. I think I have to add an additional filter to do that, not sure how.escplan9 wrote:What I like to do is use gatherer . wizards . com advanced search to find similar cards. For instance I put in rules text to search for "prevent all damage" "by creatures" and results are at link below.
Uncle Istvan is amongst those cards as you'll see. Check out the other similar cards there and how they are implemented in XMage.
For instance, Ethereal Haze uses this for a similar effect:
- Code: Select all
this.getSpellAbility().addEffect(new PreventAllDamageByAllEffect(new FilterCreaturePermanent("creatures"), Duration.EndOfTurn, false));
thanks!!
Don
- Code: Select all
http://ct-magefree.rhcloud.com/cards
Re: Help implementing a card
by escplan9 » 05 Mar 2016, 14:48
I'm working on the requested card Circle of Affliction and I think I have a working version, but not entirely sure. I do not think it is registering each time a source deals damage - if two creatures of the chosen color attack me, there should be two separate triggers for Circle of Affliction I imagine. Instead right after combat damage it just triggers once.
Maybe I need to add a loop so it triggers for each creature of the color that dealt damage? I would need to do something like get a collection of all sources of damage during combat, and then have it trigger for each one of those.
Or the EventType I'm using isn't what I need it to be. Looking at the other Event Types though I'm not sure what else would work here. Or I just need to rework the code quite a bit more and not rely on the event types.
I see other cards that look at when sources deal damage to the player doing things like calculating the total amount of damage done
- Code: Select all
Ability ability = new CircleOfAfflictionTriggeredAbility();
ability.addTarget(new TargetPlayer());
this.addAbility(ability);
... ( inside CircleOfAfflictionTriggeredAbility() ) ...
public CircleOfAfflictionTriggeredAbility() {
super(Zone.BATTLEFIELD, new DoIfCostPaid(new CircleOfAfflictionEffect(), new GenericManaCost(1)), false);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGED_PLAYER;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
boolean shouldTrigger = false;
Permanent circleOfAffliction = game.getPermanent(getSourceId());
if (circleOfAffliction != null && event.getType() == GameEvent.EventType.DAMAGED_PLAYER) {
ObjectColor chosenColor = (ObjectColor) game.getState().getValue(circleOfAffliction.getId() + "_color");
if (chosenColor != null) {
MageObject damageSource = game.getObject(event.getSourceId());
if (damageSource != null) {
shouldTrigger = damageSource.getColor(game).shares(chosenColor);
}
}
}
return shouldTrigger;
}
... (there also is a class for CircleOfAfflictionEffect that handles the life drain portion, but that seems fine and unrelated to this issue) ...
Maybe I need to add a loop so it triggers for each creature of the color that dealt damage? I would need to do something like get a collection of all sources of damage during combat, and then have it trigger for each one of those.
Or the EventType I'm using isn't what I need it to be. Looking at the other Event Types though I'm not sure what else would work here. Or I just need to rework the code quite a bit more and not rely on the event types.
I see other cards that look at when sources deal damage to the player doing things like calculating the total amount of damage done
- Code: Select all
if ((event.getTargetId().equals(this.getControllerId()))) {
this.getEffects().get(0).setValue("damageAmount", event.getAmount());
return true;
}
... (then later)...
int damage = (Integer) this.getValue("damageAmount");
Re: Help implementing a card
by LevelX » 06 Mar 2016, 00:39
Don't check again for the event.getType() its already checked in checkEventType method.escplan9 wrote: Instead right after combat damage it just triggers once.[/code]
It should trigger for each permanent doing combat damage in a damage step to a player.
If not, something is wrong (your card or the Framework class) and one has to look for that.
Also use
- Code: Select all
Permanent circleOfAffliction = game.getPermanentOrLKIBattlefield(getSourceId());
And please post always your complete code of a card with questions, it makes it a lot easier to test something or to find a bug in the code.
-
LevelX - DEVELOPER
- Posts: 1677
- Joined: 08 Dec 2011, 15:08
- Has thanked: 174 times
- Been thanked: 374 times
Re: Help implementing a card
by escplan9 » 06 Mar 2016, 03:03
Full code for the card - I took out the extra getEventType() check like you recommended. I'll do more testing on it tomorrow.
- Code: Select all
package mage.sets.planarchaos;
import java.util.UUID;
import mage.MageObject;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ChooseColorEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPlayer;
/**
*
* @author escplan9 (Derek Monturo - dmontur1 at gmail dot com)
*/
public class CircleOfAffliction extends CardImpl {
public CircleOfAffliction(UUID ownerId) {
super(ownerId, 66, "Circle of Affliction", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}");
this.expansionSetCode = "PLC";
// As Circle of Affliction enters the battlefield, choose a color.
this.addAbility(new EntersBattlefieldAbility(new ChooseColorEffect(Outcome.Neutral)));
// Whenever a source of the chosen color deals damage to you, you may pay {1}. If you do, target player loses 1 life and you gain 1 life.
Ability ability = new CircleOfAfflictionTriggeredAbility();
ability.addTarget(new TargetPlayer());
this.addAbility(ability);
}
public CircleOfAffliction(final CircleOfAffliction card) {
super(card);
}
@Override
public CircleOfAffliction copy() {
return new CircleOfAffliction(this);
}
}
class CircleOfAfflictionTriggeredAbility extends TriggeredAbilityImpl {
public CircleOfAfflictionTriggeredAbility() {
super(Zone.BATTLEFIELD, new DoIfCostPaid(new CircleOfAfflictionEffect(), new GenericManaCost(1)), false);
}
public CircleOfAfflictionTriggeredAbility(final CircleOfAfflictionTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGED_PLAYER;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
boolean shouldTrigger = false;
Permanent circleOfAffliction = game.getPermanent(getSourceId());
if (circleOfAffliction != null) {
ObjectColor chosenColor = (ObjectColor) game.getState().getValue(circleOfAffliction.getId() + "_color");
if (chosenColor != null) {
MageObject damageSource = game.getObject(event.getSourceId());
if (damageSource != null) {
shouldTrigger = damageSource.getColor(game).shares(chosenColor);
}
}
}
return shouldTrigger;
}
@Override
public CircleOfAfflictionTriggeredAbility copy() {
return new CircleOfAfflictionTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever a source of the chosen color deals damage to you, you may pay {1}. If you do, target player loses 1 life and you gain 1 life.";
}
}
class CircleOfAfflictionEffect extends OneShotEffect {
CircleOfAfflictionEffect() {
super(Outcome.Detriment);
staticText = "target player loses 1 life and you gain 1 life";
}
CircleOfAfflictionEffect(final CircleOfAfflictionEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player targetPlayer = game.getPlayer(source.getFirstTarget());
Player you = game.getPlayer(source.getControllerId());
if (targetPlayer != null) {
targetPlayer.loseLife(1, game);
}
if (you != null) {
you.gainLife(1, game);
}
return true;
}
@Override
public CircleOfAfflictionEffect copy() {
return new CircleOfAfflictionEffect(this);
}
}
Re: Help implementing a card
by LevelX » 06 Mar 2016, 08:52
There is no problem:escplan9 wrote:Full code for the card - I took out the extra getEventType() check like you recommended. I'll do more testing on it tomorrow
Code only shortened
- Code: Select all
/*
* 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.sets.planarchaos;
import java.util.UUID;
import mage.MageObject;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.ChooseColorEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LoseLifeTargetEffect;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.TargetPlayer;
/**
*
* @author escplan9 (Derek Monturo - dmontur1 at gmail dot com)
*/
public class CircleOfAffliction extends CardImpl {
public CircleOfAffliction(UUID ownerId) {
super(ownerId, 66, "Circle of Affliction", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}");
this.expansionSetCode = "PLC";
// As Circle of Affliction enters the battlefield, choose a color.
this.addAbility(new EntersBattlefieldAbility(new ChooseColorEffect(Outcome.Neutral)));
// Whenever a source of the chosen color deals damage to you, you may pay {1}. If you do, target player loses 1 life and you gain 1 life.
Ability ability = new CircleOfAfflictionTriggeredAbility();
ability.addTarget(new TargetPlayer());
this.addAbility(ability);
}
public CircleOfAffliction(final CircleOfAffliction card) {
super(card);
}
@Override
public CircleOfAffliction copy() {
return new CircleOfAffliction(this);
}
}
class CircleOfAfflictionTriggeredAbility extends TriggeredAbilityImpl {
public CircleOfAfflictionTriggeredAbility() {
super(Zone.BATTLEFIELD, new DoIfCostPaid(new LoseLifeTargetEffect(1), new GenericManaCost(1)), false);
((DoIfCostPaid) getEffects().get(0)).addEffect(new GainLifeEffect(1));
}
public CircleOfAfflictionTriggeredAbility(final CircleOfAfflictionTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGED_PLAYER;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent circleOfAffliction = game.getPermanentOrLKIBattlefield(getSourceId());
if (circleOfAffliction != null) {
ObjectColor chosenColor = (ObjectColor) game.getState().getValue(circleOfAffliction.getId() + "_color");
if (chosenColor != null) {
MageObject damageSource = game.getObject(event.getSourceId());
if (damageSource != null) {
return true;
}
}
}
return false;
}
@Override
public CircleOfAfflictionTriggeredAbility copy() {
return new CircleOfAfflictionTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever a source of the chosen color deals damage to you, you may pay {1}. If you do, target player loses 1 life and you gain 1 life.";
}
}
- Code: Select all
/*
* 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.triggers.damage;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class CircleOfAfflictionTest extends CardTestPlayerBase {
@Test
public void testTwoAttackersDamage() {
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 2);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
// As Circle of Affliction enters the battlefield, choose a color.
// Whenever a source of the chosen color deals damage to you, you may pay {1}. If you do, target player loses 1 life and you gain 1 life.
addCard(Zone.HAND, playerA, "Circle of Affliction", 1);// {1}{B}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Circle of Affliction");
setChoice(playerA, "White");
attack(2, playerB, "Silvercoat Lion");
attack(2, playerB, "Silvercoat Lion");
addTarget(playerA, playerB);
addTarget(playerA, playerB);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Circle of Affliction", 1);
assertLife(playerA, 18);
assertLife(playerB, 18);
}
}
-
LevelX - DEVELOPER
- Posts: 1677
- Joined: 08 Dec 2011, 15:08
- Has thanked: 174 times
- Been thanked: 374 times
Re: Help implementing a card
by escplan9 » 06 Mar 2016, 12:55
Awesome. Thanks for figuring that out and simplifying it for Circle of Affliction. I was basing the programming off some work done on various similar cards like Story Circle, Retaliator Griffin, Sludge Strider, Teferi's Moat that involved choosing colors, sources dealing damage, and the life drain effect.
Only question is why your version cut out the check for the source being of the right color?
edit: The simplifications you made were where I borrowed a lot of code from Retaliator Griffin a card from Alara Reborn. I'll also test if that card is triggering properly since it's another "whenever a source deals damage to you..." and it largely used the old code I had in there. Tested, and Retaliator Griffin checks out. I'll have to go over the code changes you made to see if I can figure out why it wasn't triggering twice on mine before.
Only question is why your version cut out the check for the source being of the right color?
- Code: Select all
ObjectColor chosenColor = (ObjectColor) game.getState().getValue(circleOfAffliction.getId() + "_color");
if (chosenColor != null) {
MageObject damageSource = game.getObject(event.getSourceId());
if (damageSource != null) {
return true;
}
}
- Code: Select all
ObjectColor chosenColor = (ObjectColor) game.getState().getValue(circleOfAffliction.getId() + "_color");
if (chosenColor != null) {
MageObject damageSource = game.getObject(event.getSourceId());
if (damageSource != null) {
shouldTrigger = damageSource.getColor(game).shares(chosenColor);
}
}
edit: The simplifications you made were where I borrowed a lot of code from Retaliator Griffin a card from Alara Reborn. I'll also test if that card is triggering properly since it's another "whenever a source deals damage to you..." and it largely used the old code I had in there. Tested, and Retaliator Griffin checks out. I'll have to go over the code changes you made to see if I can figure out why it wasn't triggering twice on mine before.
Re: Help implementing a card
by LevelX » 06 Mar 2016, 13:40
I deleted too much... my mistakeescplan9 wrote:Only question is why your version cut out the check for the source being of the right color?
That's why we should add another test case with a creature of non chosen color attacking the controller.
-
LevelX - DEVELOPER
- Posts: 1677
- Joined: 08 Dec 2011, 15:08
- Has thanked: 174 times
- Been thanked: 374 times
Re: Help implementing a card
by escplan9 » 06 Mar 2016, 14:00
I figured so much and added the color-compare check back in. I also added a bunch of tests and a pull request for the card and the tests. Looks good now!LevelX wrote:I deleted too much... my mistakeescplan9 wrote:Only question is why your version cut out the check for the source being of the right color?
That's why we should add another test case with a creature of non chosen color attacking the controller.
edit: So trying to understand the wizardry you did with simplifying / shortening the code. Could you walk me through what is going on here?
- Code: Select all
super(Zone.BATTLEFIELD, new DoIfCostPaid(new LoseLifeTargetEffect(1), new GenericManaCost(1)), false);
((DoIfCostPaid) getEffects().get(0)).addEffect(new GainLifeEffect(1));
Re: Help implementing a card
by LevelX » 06 Mar 2016, 19:01
Yes it's simply getting the DoIfCostPaid effect and adding another effect to its effects that will be applied if the cost was paid.escplan9 wrote:The getEffects() function returns a set of Effects. You're getting the first index returned there because... it's the only effect
-
LevelX - DEVELOPER
- Posts: 1677
- Joined: 08 Dec 2011, 15:08
- Has thanked: 174 times
- Been thanked: 374 times
Re: Help implementing a card
by escplan9 » 07 Mar 2016, 14:31
I recently implemented the card Words of Worship. It was a simple card to implement thanks to many cards with similar effects already implemented such as Words of War. When I manually test the card it works fine. However, I wanted to create some automated tests in Mage.Tests for it and don't understand how activateAbility works there, or what is wrong with my test otherwise:
edit: The test fails on the assertLife portion - it returns 20 life instead of 25 that is expected.
- Code: Select all
/*
* 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.enchantments;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* Tests for the enchantments "Word of X" such as Words of Worship and Words of War
*
* @author escplan9 (Derek Monturo - dmontur1 at gmail dot com)
*/
public class WordsOfXTests extends CardTestPlayerBase {
/**
* Basic test - during upkeep, pay to activate and gain 5 life instead of draw a card.
*/
@Test
public void testWordsOfWorshipUpkeepActivate() {
// Enchantment {2}{W}
// {1}: The next time you would draw a card this turn, you gain 5 life instead.
addCard(Zone.BATTLEFIELD, playerA, "Words of Worship", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
addCard(Zone.LIBRARY, playerA, "White Knight", 4);
activateAbility(1, PhaseStep.UPKEEP, playerA, "{1}:");
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertHandCount(playerA, 0); // should not have drawn a card
assertLife(playerA, 25); // gain 5 life instead of draw card
}
}
edit: The test fails on the assertLife portion - it returns 20 life instead of 25 that is expected.
Re: Help implementing a card
by LevelX » 07 Mar 2016, 21:47
It compares if the rules text of the ability starts with the string you set in the activateAbility parameter.escplan9 wrote:Could someone clarify how activateAbility works? I see in other tests a few different ways it should work. For equipment it'll just have the string "Equip". For some abilities it will just have the activated cost as I have done here. For some other cards it will have some or all of the text that follows the activation like Spellskite "{UP}: Change a target of target spell or ability to {this}."
You have to be sure that the part you give is unique over all available activated abilities to get the one you want.
Also it can sometimes tricky if the ability has a slighty other rule text as you think. If I'not sure if the text is correct I check it with the debugger in Tetsplayer.priority method where the string comparison is done.
Tests can sometimes be tricky to define, because of all the parameters that can be wrong and sometimes only debugging helps to understand and find the reason why the test fails.
-
LevelX - DEVELOPER
- Posts: 1677
- Joined: 08 Dec 2011, 15:08
- Has thanked: 174 times
- Been thanked: 374 times
Re: Help implementing a card
by MarcoMarin » 26 Mar 2016, 07:10
Regarding Uncle Istvan:
The ideal would be to have a combination of PreventAllDamageToSourceEffect AND PreventDamageBySourceEffect.
I've actually just made one.
It's for Arabian Nights' Desert Nomads, by inheriting from PreventAllDamageToSourceEffect and copying 1 condition from PreventDamageBySourceEffect.
Well, it's a simplistic version which applies only to subtypes.
But it should be trivial to copy over more stuff from the latter class and do a "PreventDamageToSourceBySourceEffect", for someone who knows more Java (which I don't), just beware that it expects a target choice from the player, while e.g. Uncle Istvan is unconditional to all creatures.
So my 'bySubtype' class should be enough, but if you could test it please check it out here (until I get pull request rights ): https://github.com/Marco-Marin/mage
(Edit: btw, I've placed the class inside the cards themselves, so not to mess up the rest of the code, hopefully this will be replaced by the more powerful/general effect, not just subtypes)
For the devs:
I noticed something on "PreventionEffectImpl": where a comment says:
The ideal would be to have a combination of PreventAllDamageToSourceEffect AND PreventDamageBySourceEffect.
I've actually just made one.
It's for Arabian Nights' Desert Nomads, by inheriting from PreventAllDamageToSourceEffect and copying 1 condition from PreventDamageBySourceEffect.
Well, it's a simplistic version which applies only to subtypes.
But it should be trivial to copy over more stuff from the latter class and do a "PreventDamageToSourceBySourceEffect", for someone who knows more Java (which I don't), just beware that it expects a target choice from the player, while e.g. Uncle Istvan is unconditional to all creatures.
So my 'bySubtype' class should be enough, but if you could test it please check it out here (until I get pull request rights ): https://github.com/Marco-Marin/mage
(Edit: btw, I've placed the class inside the cards themselves, so not to mess up the rest of the code, hopefully this will be replaced by the more powerful/general effect, not just subtypes)
For the devs:
I noticed something on "PreventionEffectImpl": where a comment says:
- Code: Select all
// damage amount is reduced or set to 0 so complete replacement of damage event is never neccessary
- MarcoMarin
- Posts: 32
- Joined: 20 Mar 2016, 07:49
- Has thanked: 16 times
- Been thanked: 2 times
Re: Help implementing a card
by escplan9 » 08 Apr 2016, 21:32
Working on the card Vanishing and not 100% sure on the expected behavior. Right now when the enchanted creature phases out, the Enchantment card Vanishing just "lingers" on the battlefield where the creature used to be. Then when the creature phases back in, the Enchantment is re-attached to it. I'm not sure if this is an issue requiring a change in handling for Phasing, or a change in handling for the card I coded in.
http://www.mtgsalvation.com/forums/magi ... -vanishing
"Enchantments attached to the creature will "phase out indirectly," and stay attached to it, even though it is phased out." and "Enchantments and equipment phase in/out with the permanents they are associated with."
My code below:
Vanishing
Not a high priority by any means.
http://www.mtgsalvation.com/forums/magi ... -vanishing
"Enchantments attached to the creature will "phase out indirectly," and stay attached to it, even though it is phased out." and "Enchantments and equipment phase in/out with the permanents they are associated with."
My code below:
Vanishing
- Code: Select all
package mage.sets.visions;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.PhaseOutAttachedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
/**
*
* @author escplan9 (Derek Monturo - dmontur1 at gmail dot com)
*/
public class Vanishing extends CardImpl {
public Vanishing(UUID ownerId) {
super(ownerId, 39, "Vanishing", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{U}");
this.expansionSetCode = "VIS";
this.subtype.add("Aura");
// Enchant creature
TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.Detriment));
Ability ability = new EnchantAbility(auraTarget.getTargetName());
this.addAbility(ability);
// {U}{U}: Enchanted creature phases out.
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new PhaseOutAttachedEffect(), new ManaCostsImpl("{U}{U}")));
}
public Vanishing(final Vanishing card) {
super(card);
}
@Override
public Vanishing copy() {
return new Vanishing(this);
}
}
- Code: Select all
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
*
* @author escplan9 (Derek Monturo - dmontur1 at gmail dot com)
*/
public class PhaseOutAttachedEffect extends OneShotEffect {
public PhaseOutAttachedEffect() {
super(Outcome.Detriment);
this.staticText = "Enchanted creature phases out";
}
public PhaseOutAttachedEffect(final PhaseOutAttachedEffect effect) {
super(effect);
}
@Override
public PhaseOutAttachedEffect copy() {
return new PhaseOutAttachedEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent enchantment = game.getPermanentOrLKIBattlefield(source.getSourceId());
if(enchantment != null) {
Permanent enchanted = game.getPermanent(enchantment.getAttachedTo());
if(enchanted != null) {
return enchanted.phaseOut(game);
}
}
return false;
}
}
Not a high priority by any means.
Who is online
Users browsing this forum: No registered users and 2 guests