It is currently 14 May 2025, 06:21
   
Text Size

Implementing cards

Moderators: North, BetaSteward, noxx, jeffwadsworth, JayDi, TheElk801, LevelX, CCGHQ Admins

Re: Implementing cards

Postby 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:

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);
    }
}
The Conspire Ability 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.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

Postby 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().
User avatar
LevelX
DEVELOPER
 
Posts: 1677
Joined: 08 Dec 2011, 15:08
Has thanked: 174 times
Been thanked: 374 times

Previous

Return to Developers Talk

Who is online

Users browsing this forum: No registered users and 4 guests


Who is online

In total there are 4 users online :: 0 registered, 0 hidden and 4 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 4 guests

Login Form