Implementing cards
Moderators: North, BetaSteward, noxx, jeffwadsworth, JayDi, TheElk801, LevelX, CCGHQ Admins
Re: Implementing cards
by jeffwadsworth » 08 Jan 2015, 18:43
Working on Wort, the RaidMother. The Conspire ability is put on the instant or sorcery that is cast, but the trigger "spell cast" does not fire when it hits the stack. Does anyone see what I am missing in the code below?
Wort, the RaidMother code:
Wort, the RaidMother code:
- 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.shadowmoor;
import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.keyword.ConspireAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Duration;
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.token.Token;
/**
*
* @author jeffwadsworth
*/
public class WortTheRaidmother extends CardImpl {
public WortTheRaidmother(UUID ownerId) {
super(ownerId, 223, "Wort, the Raidmother", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{1}");
this.expansionSetCode = "SHM";
this.supertype.add("Legendary");
this.subtype.add("Goblin");
this.subtype.add("Shaman");
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// When Wort, the Raidmother enters the battlefield, put two 1/1 red and green Goblin Warrior creature tokens onto the battlefield.
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new WortGoblinToken(), 2)));
// Each red or green instant or sorcery spell you cast has conspire.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new WortTheRaidmotherEffect()));
}
public WortTheRaidmother(final WortTheRaidmother card) {
super(card);
}
@Override
public WortTheRaidmother copy() {
return new WortTheRaidmother(this);
}
}
class WortTheRaidmotherEffect extends ReplacementEffectImpl {
public WortTheRaidmotherEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
staticText = "Each red or green instant or sorcery spell you cast has conspire";
}
public WortTheRaidmotherEffect(final WortTheRaidmotherEffect effect) {
super(effect);
}
@Override
public WortTheRaidmotherEffect copy() {
return new WortTheRaidmotherEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
MageObject object = game.getObject(event.getSourceId());
if (object != null) {
Card card = (Card) object;
System.out.println("The card is " + card.getName());
Ability ability = new ConspireAbility(card);
if (!card.getAbilities().contains(ability)) {
card.addAbility(ability);
ability.setControllerId(source.getControllerId());
ability.setSourceId(card.getId());
game.getState().addAbility(ability, source.getSourceId(), card);
}
}
return false;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.CAST_SPELL;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if ((event.getType() == GameEvent.EventType.CAST_SPELL)
&& event.getPlayerId() == source.getControllerId()) {
MageObject spellObject = game.getObject(event.getSourceId());
if (spellObject != null) {
if (spellObject.getCardType().contains(CardType.INSTANT)
|| spellObject.getCardType().contains(CardType.SORCERY)) {
if (spellObject.getColor().contains(ObjectColor.RED)
|| spellObject.getColor().contains(ObjectColor.GREEN)) {
return true;
}
}
}
}
return false;
}
}
class WortGoblinToken extends Token {
public WortGoblinToken() {
super("Goblin", "1/1 red and green Goblin Warrior creature token");
cardType.add(CardType.CREATURE);
subtype.add("Goblin");
subtype.add("Warrior");
color.setRed(true);
color.setGreen(true);
power = new MageInt(1);
toughness = new MageInt(1);
}
}
- 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.abilities.keyword;
import java.util.Iterator;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs;
import mage.abilities.costs.OptionalAdditionalCost;
import mage.abilities.costs.OptionalAdditionalCostImpl;
import mage.abilities.costs.OptionalAdditionalSourceCosts;
import mage.abilities.costs.common.TapTargetCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.SharesColorWithSourcePredicate;
import mage.filter.predicate.permanent.TappedPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.target.common.TargetControlledPermanent;
/**
* 702.77. Conspire 702.77a Conspire is a keyword that represents two abilities.
* The first is a static ability that functions while the spell with conspire is
* on the stack. The second is a triggered ability that functions while the
* spell with conspire is on the stack. "Conspire" means "As an additional cost
* to cast this spell, you may tap two untapped creatures you control that each
* share a color with it" and "When you cast this spell, if its conspire cost
* was paid, copy it. If the spell has any targets, you may choose new targets
* for the copy." Paying a spell’s conspire cost follows the rules for paying
* additional costs in rules 601.2b and 601.2e–g. 702.77b If a spell has
* multiple instances of conspire, each is paid separately and triggers based on
* its own payment, not any other instance of conspire. *
*
* @author jeffwadsworth heavily based off the replicate keyword by LevelX
*/
public class ConspireAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
private static final String keywordText = "Conspire";
private static final String reminderTextCost = "<i>As you cast this spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose a new target for the copy.)</i>";
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("two untapped creatures you control that share a color with it");
static {
filter.add(Predicates.not(new TappedPredicate()));
filter.add(new SharesColorWithSourcePredicate());
}
Cost costConspire = new TapTargetCost(new TargetControlledPermanent(2, 2, filter, true));
OptionalAdditionalCost conspireCost = new OptionalAdditionalCostImpl(keywordText, "-", reminderTextCost, costConspire);
public ConspireAbility(Card card) {
super(Zone.STACK, null);
setRuleAtTheTop(false);
card.addAbility(new ConspireTriggeredAbility());
}
public ConspireAbility(final ConspireAbility ability) {
super(ability);
conspireCost = ability.conspireCost;
}
@Override
public ConspireAbility copy() {
return new ConspireAbility(this);
}
@Override
public void addCost(Cost cost) {
if (conspireCost != null) {
((Costs) conspireCost).add(cost);
}
}
@Override
public boolean isActivated() {
if (conspireCost != null) {
return conspireCost.isActivated();
}
return false;
}
public void resetConspire() {
if (conspireCost != null) {
conspireCost.reset();
}
}
@Override
public void addOptionalAdditionalCosts(Ability ability, Game game) {
if (ability instanceof SpellAbility) {
Player player = game.getPlayer(controllerId);
if (player != null) {
this.resetConspire();
if (player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(conspireCost.getText(false)).append(" ?").toString(), game)) {
conspireCost.activate();
for (Iterator it = ((Costs) conspireCost).iterator(); it.hasNext();) {
Cost cost = (Cost) it.next();
ability.getCosts().add(cost.copy());
}
}
}
}
}
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();
if (conspireCost != null) {
sb.append(conspireCost.getText(false));
sb.append(" ").append(conspireCost.getReminderText());
}
return sb.toString();
}
@Override
public String getCastMessageSuffix() {
if (conspireCost != null) {
return conspireCost.getCastSuffixMessage(0);
} else {
return "";
}
}
public String getReminderText() {
if (conspireCost != null) {
return conspireCost.getReminderText();
} else {
return "";
}
}
}
class ConspireTriggeredAbility extends TriggeredAbilityImpl {
public ConspireTriggeredAbility() {
super(Zone.STACK, new ConspireEffect());
this.setRuleVisible(false);
}
private ConspireTriggeredAbility(final ConspireTriggeredAbility ability) {
super(ability);
}
@Override
public ConspireTriggeredAbility copy() {
return new ConspireTriggeredAbility(this);
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.SPELL_CAST) {
System.out.println("A spell resolved and the event source is " + game.getCard(event.getSourceId()).getName());
if (event.getSourceId().equals(this.sourceId)) {
System.out.println("It makes it here, so the trigger works");
StackObject spell = game.getStack().getStackObject(this.sourceId);
if (spell instanceof Spell) {
Card card = game.getCard(spell.getSourceId());
if (card != null) {
System.out.println("The card in the trigger is " + card.getName());
for (Ability ability : card.getAbilities()) {
if (ability instanceof ConspireAbility) {
if (((ConspireAbility) ability).isActivated()) {
for (Effect effect : this.getEffects()) {
effect.setValue("ConspireSpell", spell);
}
return true;
}
}
}
}
}
}
}
return false;
}
@Override
public String getRule() {
return "Conspire: <i>As you cast this spell, you may tap two untapped creatures you control that share a color with it. When you do, copy it and you may choose a new target for the copy.)</i>";
}
}
class ConspireEffect extends OneShotEffect {
public ConspireEffect() {
super(Outcome.Copy);
}
public ConspireEffect(final ConspireEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Spell spell = (Spell) this.getValue("ConspireSpell");
if (spell != null) {
Card card = game.getCard(spell.getSourceId());
if (card != null) {
System.out.println("The card in the ConspireAbility code is " + card.getName());
for (Ability ability : card.getAbilities()) {
if (ability instanceof ConspireAbility) {
if (((ConspireAbility) ability).isActivated()) {
((ConspireAbility) ability).resetConspire();
}
}
}
Spell copy = spell.copySpell();
System.out.println("The copied spell is " + spell.getName());
copy.setControllerId(source.getControllerId());
copy.setCopiedSpell(true);
game.getStack().push(copy);
copy.chooseNewTargets(game, source.getControllerId());
game.informPlayers(new StringBuilder(controller.getName()).append(copy.getActivatedMessage(game)).toString());
return true;
}
}
}
return false;
}
@Override
public ConspireEffect copy() {
return new ConspireEffect(this);
}
}
- jeffwadsworth
- Super Tester Elite
- Posts: 1172
- Joined: 20 Oct 2010, 04:47
- Location: USA
- Has thanked: 287 times
- Been thanked: 70 times
Re: Implementing cards
by LevelX » 09 Jan 2015, 12:44
Concerning Wort, the Raidmother
The problem is, will for the card ever the added ability be removed.
Take a look at Soulfire Grand Master that gives all spells on the stack lifelink.
I made a continuous effect that adds the ability to the card with each call of applyEffects().
And a watcher, that removes the ability from the card as soon as the card leaves the stack.
Another way could be to use the otherAbilities of the GameState().
The problem is, will for the card ever the added ability be removed.
Take a look at Soulfire Grand Master that gives all spells on the stack lifelink.
I made a continuous effect that adds the ability to the card with each call of applyEffects().
And a watcher, that removes the ability from the card as soon as the card leaves the stack.
Another way could be to use the otherAbilities of the GameState().
-
LevelX - DEVELOPER
- Posts: 1677
- Joined: 08 Dec 2011, 15:08
- Has thanked: 174 times
- Been thanked: 374 times
17 posts
• Page 2 of 2 • 1, 2
Who is online
Users browsing this forum: No registered users and 4 guests