It is currently 28 Apr 2024, 01:16
   
Text Size

Help implementing a card

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

Re: Help implementing a card

Postby jeffwadsworth » 03 Jan 2014, 20:40

@cbt33

Also, consider these changes to your apply section (the formating is messed up, but it is readable. I just took your code and "tried" to modify it in the code block:
Code: Select all
@Override
    public boolean apply(Game game, Ability source) {
Player you = game.getPlayer(source.getControllerId());
if (you != null) {
        FilterCard filter = new FilterCard();
        Player damagedPlayer = game.getPlayer(this.getTargetPointer().getFirst(game, source));
        if (damagedPlayer != null) {
            filter.add(new OwnerIdPredicate(damagedPlayer.getId()));
            Target target = new TargetCardInGraveyard(filter);
if (you.chooseTarget(Outcome.Neutral, target, source, game) {
UUID exileId = CardUtil.getCardExileZoneId(game, source);
            game.getPermanent(target.getFirstTarget()).moveToExile(exileId, "Zombie Canibal", source.getSourceId(), game);
return true;
        }
}
}
        return false;
    }
jeffwadsworth
Super Tester Elite
 
Posts: 1171
Joined: 20 Oct 2010, 04:47
Location: USA
Has thanked: 287 times
Been thanked: 69 times

Re: Help implementing a card

Postby jeffwadsworth » 14 Feb 2014, 21:00

Code for Canker Abomination. Put here for convenience.

Code: Select all
package mage.sets.eventide;

import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.counters.CounterType;
import mage.filter.common.FilterCreaturePermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetOpponent;

/**
 *
 * @author jeffwadsworth
 *
 */
public class CankerAbomination extends CardImpl<CankerAbomination> {

    public CankerAbomination(UUID ownerId) {
        super(ownerId, 115, "Canker Abomination", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{2}{B/G}{B/G}");
        this.expansionSetCode = "EVE";
        this.subtype.add("Treefolk");
        this.subtype.add("Horror");

        this.color.setGreen(true);
        this.color.setBlack(true);
        this.power = new MageInt(6);
        this.toughness = new MageInt(6);

        // As Canker Abomination enters the battlefield, choose an opponent. Canker Abomination enters the battlefield with a -1/-1 counter on it for each creature that player controls.
        Ability ability = new AsEntersBattlefieldAbility(new CankerAbominationEffect());
        Target target = new TargetOpponent(true);
        target.setNotTarget(true);
        ability.addTarget(target);
        this.addAbility(ability);

    }

    public CankerAbomination(final CankerAbomination card) {
        super(card);
    }

    @Override
    public CankerAbomination copy() {
        return new CankerAbomination(this);
    }
}

class CankerAbominationEffect extends OneShotEffect<CankerAbominationEffect> {

    public CankerAbominationEffect() {
        super(Outcome.Neutral);
        this.staticText = "choose an opponent. {this} enters the battlefield with a -1/-1 counter on it for each creature that player controls";
    }

    public CankerAbominationEffect(final CankerAbominationEffect effect) {
        super(effect);
    }

    @Override
    public CankerAbominationEffect copy() {
        return new CankerAbominationEffect(this);
    }

    @Override
    public boolean apply(Game game, Ability source) {
        Player player = game.getPlayer(source.getControllerId());
        Permanent CankerAbomination = game.getPermanent(source.getSourceId());
        if (player != null && CankerAbomination != null) {
            Player chosenPlayer = game.getPlayer(source.getFirstTarget());
            if (chosenPlayer != null) {
                game.informPlayers(CankerAbomination.getName() + ": " + player.getName() + " has chosen " + chosenPlayer.getName());
                int amount = game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), chosenPlayer.getId(), game).size();
                CankerAbomination.addCounters(CounterType.M1M1.createInstance(amount), game);
                return true;
            }
        }
        return false;
    }
}
jeffwadsworth
Super Tester Elite
 
Posts: 1171
Joined: 20 Oct 2010, 04:47
Location: USA
Has thanked: 287 times
Been thanked: 69 times

Re: Help implementing a card

Postby jeffwadsworth » 16 May 2014, 19:52

Having an issue with Unstoppable Ash.
The "Predicates.or" within the ChampionAbility class does not seem to work for this, yet it looks okay. The Champion ability will not allow any targets to be chosen. Permanents in play that I control include both the Treefolk and Warrior subtypes, namely Bosk Banneret and Cat Warriors.


Code:

Code: Select all
package mage.sets.morningtide;

import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.BecomesBlockedAllTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continious.BoostTargetEffect;
import mage.abilities.keyword.ChampionAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.TargetController;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.ControllerPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;

/**
 *
 * @author jeffwadsworth
 *
 */
public class UnstoppableAsh extends CardImpl<UnstoppableAsh> {

    final static private FilterCreaturePermanent filter = new FilterCreaturePermanent("a creature you control");

    static {
        filter.add(new ControllerPredicate(TargetController.YOU));
    }

    public UnstoppableAsh(UUID ownerId) {
        super(ownerId, 137, "Unstoppable Ash", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{3}{G}");
        this.expansionSetCode = "MOR";
        this.subtype.add("Treefolk");
        this.subtype.add("Warrior");

        this.color.setGreen(true);
        this.power = new MageInt(5);
        this.toughness = new MageInt(5);

        // Trample
        this.addAbility(TrampleAbility.getInstance());

        // Champion a Treefolk or Warrior
        this.addAbility(new ChampionAbility(this, "Treefolk, Warrior"));
       
        // Whenever a creature you control becomes blocked, it gets +0/+5 until end of turn.
        this.addAbility(new BecomesBlockedAllTriggeredAbility(new UnstoppableAshEffect(), false, filter, true));

    }

    public UnstoppableAsh(final UnstoppableAsh card) {
        super(card);
    }

    @Override
    public UnstoppableAsh copy() {
        return new UnstoppableAsh(this);
    }
}

class UnstoppableAshEffect extends OneShotEffect<UnstoppableAshEffect> {

    UnstoppableAshEffect() {
        super(Outcome.Neutral);
        staticText = "it gets +0/+5 until end of turn";
    }

    UnstoppableAshEffect(final UnstoppableAshEffect effect) {
        super(effect);
    }

    @Override
    public boolean apply(Game game, Ability source) {
        Permanent creature = game.getPermanent(this.getTargetPointer().getFirst(game, source));
        if (creature != null) {
            game.addEffect(new BoostTargetEffect(0, 5, Duration.EndOfTurn), source);
            return true;
        }
        return false;
    }

    @Override
    public UnstoppableAshEffect copy() {
        return new UnstoppableAshEffect(this);
    }
}
The ChampionAbility code:

Code: Select all
package mage.abilities.keyword;

import java.util.ArrayList;
import java.util.UUID;

import mage.constants.Outcome;
import mage.constants.Zone;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.StaticAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.LeavesBattlefieldTriggeredAbility;
import mage.abilities.costs.CostImpl;
import mage.abilities.effects.common.ReturnFromExileForSourceEffect;
import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect;
import mage.cards.Card;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.Predicate;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.filter.predicate.permanent.AnotherPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.common.TargetControlledCreaturePermanent;

/*
 * @author LevelX2
 *
 *
 * 702.70. Champion
 *
 * 702.70a Champion represents two triggered abilities. "Champion an [object]" means
 * "When this permanent enters the battlefield, sacrifice it unless you exile another
 * [object] you control" and "When this permanent leaves the battlefield, return the
 * exiled card to the battlefield under its owner's control."
 *
 * 702.70b The two abilities represented by champion are linked. See rule 607, "Linked Abilities." #
 *
 * 702.70c A permanent is "championed" by another permanent if the latter exiles
 * the former as the direct result of a champion ability. #
 *
 */

public class ChampionAbility extends StaticAbility<ChampionAbility> {

    protected String[] subtypes;
    protected String objectDescription;


    public ChampionAbility(Card card, String subtype) {
        this(card, new String[]{subtype});
    }
   
    public ChampionAbility(Card card, String[] subtypes) {
        super(Zone.BATTLEFIELD, null);

        this.subtypes = subtypes;

        StringBuilder sb = new StringBuilder("another ");
        ArrayList<Predicate<MageObject>> subtypesPredicates = new ArrayList<>();
        int i = 0;
        for (String subtype : this.subtypes) {
            subtypesPredicates.add(new SubtypePredicate(subtype));
            if (i == 0) {
                sb.append(subtype);
            } else {
                sb.append(" or ").append(subtype);
            }
            i++;
        }
        this.objectDescription = sb.toString();
        FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(objectDescription);
        filter.add(Predicates.or(subtypesPredicates));
        filter.add(new AnotherPredicate());

        // When this permanent enters the battlefield, sacrifice it unless you exile another [object] you control.
        Ability ability1 = new EntersBattlefieldTriggeredAbility(
                new SacrificeSourceUnlessPaysEffect(new ChampionExileCost(filter, new StringBuilder(card.getName()).append(" championed creatures").toString())),false);
        ability1.setRuleVisible(false);
        card.addAbility(ability1);

        // When this permanent leaves the battlefield, return the exiled card to the battlefield under its owner's control.
        Ability ability2 = new LeavesBattlefieldTriggeredAbility(new ReturnFromExileForSourceEffect(Zone.BATTLEFIELD), false);
        ability2.setRuleVisible(false);
        card.addAbility(ability2);
    }

    public ChampionAbility(final ChampionAbility ability) {
        super(ability);
        this.subtypes = ability.subtypes;
        this.objectDescription = ability.objectDescription;
    }

    @Override
    public ChampionAbility copy() {
        return new ChampionAbility(this);
    }

    @Override
    public String getRule() {
        StringBuilder sb = new StringBuilder("Champion ").append(objectDescription);
        sb.append("<i>(When this enters the battlefield, sacrifice it unless you exile another ");
        sb.append(objectDescription);
        sb.append(" you control. When this leaves the battlefield, that card returns to the battlefield.)</i>");
        return sb.toString();
    }
}

class ChampionExileCost extends CostImpl<ChampionExileCost> {

    private String exileZone = null;

    public ChampionExileCost(FilterControlledCreaturePermanent filter, String exileZone) {
        this.addTarget(new TargetControlledCreaturePermanent(1,1,filter, true));
        this.text = "exile " + filter.getMessage() + " you control";
        this.exileZone = exileZone;
    }

    public ChampionExileCost(ChampionExileCost cost) {
        super(cost);
        this.exileZone = cost.exileZone;
    }

    @Override
    public boolean pay(Ability ability, Game game, UUID sourceId, UUID controllerId, boolean noMana) {
        if (targets.choose(Outcome.Exile, controllerId, sourceId, game)) {
            for (UUID targetId: targets.get(0).getTargets()) {
                Permanent permanent = game.getPermanent(targetId);
                if (permanent == null) {
                    return false;
                }
                paid |= permanent.moveToExile(sourceId, exileZone, sourceId, game);
            }
        }
        return paid;
    }

    @Override
    public boolean canPay(UUID sourceId, UUID controllerId, Game game) {
        return targets.canChoose(controllerId, game);
    }

    @Override
    public ChampionExileCost copy() {
        return new ChampionExileCost(this);
    }
}
jeffwadsworth
Super Tester Elite
 
Posts: 1171
Joined: 20 Oct 2010, 04:47
Location: USA
Has thanked: 287 times
Been thanked: 69 times

Re: Help implementing a card

Postby LevelX » 17 May 2014, 04:49

You use the wrong signature to create the champion ability.
Instead of
this.addAbility(new ChampionAbility(this, "Treefolk, Warrior"));
use
this.addAbility(new ChampionAbility(this, new String[]{"Treefolk", "Warrior"}));

Regards
User avatar
LevelX
DEVELOPER
 
Posts: 1677
Joined: 08 Dec 2011, 15:08
Has thanked: 174 times
Been thanked: 374 times

Re: Help implementing a card

Postby jeffwadsworth » 22 May 2014, 20:57

Here is the code for Godhead of Awe. It does not work, yet the code looks okay.
The casting cost of 0 is just for testing purposes.

Code: Select all
package mage.sets.shadowmoor;

import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continious.SetPowerToughnessAllEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.filter.predicate.permanent.AnotherPredicate;

/**
 *
 * @author jeffwadsworth
 *
 */
public class GodheadOfAwe extends CardImpl<GodheadOfAwe> {

    private static final FilterPermanent filter = new FilterPermanent("Other creatures");

    static {
        filter.add(new CardTypePredicate(CardType.CREATURE));
        filter.add(new AnotherPredicate());
    }

    public GodheadOfAwe(UUID ownerId) {
        super(ownerId, 142, "Godhead of Awe", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{0}");
        this.expansionSetCode = "SHM";
        this.subtype.add("Spirit");
        this.subtype.add("Avatar");

        this.color.setBlue(true);
        this.color.setWhite(true);
        this.power = new MageInt(4);
        this.toughness = new MageInt(4);

        // Flying
        this.addAbility(FlyingAbility.getInstance());

        // Other creatures are 1/1.
        this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SetPowerToughnessAllEffect(1, 1, Duration.WhileOnBattlefield, filter, true)));
    }

    public GodheadOfAwe(final GodheadOfAwe card) {
        super(card);
    }

    @Override
    public GodheadOfAwe copy() {
        return new GodheadOfAwe(this);
    }
}
jeffwadsworth
Super Tester Elite
 
Posts: 1171
Joined: 20 Oct 2010, 04:47
Location: USA
Has thanked: 287 times
Been thanked: 69 times

Re: Help implementing a card

Postby LevelX » 23 May 2014, 06:31

jeffwadsworth wrote:Here is the code for Godhead of Awe. It does not work, yet the code looks okay.
The casting cost of 0 is just for testing purposes.

Code: Select all
package mage.sets.shadowmoor;

import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continious.SetPowerToughnessAllEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.filter.predicate.permanent.AnotherPredicate;

/**
 *
 * @author jeffwadsworth
 *
 */
public class GodheadOfAwe extends CardImpl<GodheadOfAwe> {

    private static final FilterPermanent filter = new FilterPermanent("Other creatures");

    static {
        filter.add(new CardTypePredicate(CardType.CREATURE));
        filter.add(new AnotherPredicate());
    }

    public GodheadOfAwe(UUID ownerId) {
        super(ownerId, 142, "Godhead of Awe", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{0}");
        this.expansionSetCode = "SHM";
        this.subtype.add("Spirit");
        this.subtype.add("Avatar");

        this.color.setBlue(true);
        this.color.setWhite(true);
        this.power = new MageInt(4);
        this.toughness = new MageInt(4);

        // Flying
        this.addAbility(FlyingAbility.getInstance());

        // Other creatures are 1/1.
        this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new SetPowerToughnessAllEffect(1, 1, Duration.WhileOnBattlefield, filter, true)));
    }

    public GodheadOfAwe(final GodheadOfAwe card) {
        super(card);
    }

    @Override
    public GodheadOfAwe copy() {
        return new GodheadOfAwe(this);
    }
}
I fixed a bug and submitted the changes:
SetPowerToughnessAllEffect - Fixed a bug that the effect did not work correctly for static abilities.

Was this your problem (the effect of Godhead didn't work for creatures that entered the battlefield after Godhead)or what exactly did not work?
User avatar
LevelX
DEVELOPER
 
Posts: 1677
Joined: 08 Dec 2011, 15:08
Has thanked: 174 times
Been thanked: 374 times

Re: Help implementing a card

Postby jeffwadsworth » 23 May 2014, 13:45

Your fix worked great. The problem before the fix: When GOA entered the battlefield, the P/T were not changed. Subsequent cards entering the battlefield were not changed either.
jeffwadsworth
Super Tester Elite
 
Posts: 1171
Joined: 20 Oct 2010, 04:47
Location: USA
Has thanked: 287 times
Been thanked: 69 times

Re: Help implementing a card

Postby jeffwadsworth » 30 May 2014, 20:15

Here is some testing code for Chorus of the Conclave. It is not an implementation...just a test of the simple trigger. The "cast_spell" trigger does not work for me in the latest (May 30th) build. I have seen issues like this crop up on my installation and just want to verify that it isn't something else. Does this trigger work for anyone?


Code: Select all
package mage.sets.commander;

import java.util.UUID;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.keyword.ForestwalkAbility;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;

/**
 *
 * @author jeffwadsworth
 *
 */
public class ChorusOfTheConclave extends CardImpl<ChorusOfTheConclave> {

    public ChorusOfTheConclave(UUID ownerId) {
        super(ownerId, 189, "Chorus of the Conclave", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{1}");
        this.expansionSetCode = "CMD";
        this.supertype.add("Legendary");
        this.subtype.add("Dryad");

        this.color.setGreen(true);
        this.color.setWhite(true);
        this.power = new MageInt(3);
        this.toughness = new MageInt(8);

        // Forestwalk
        this.addAbility(new ForestwalkAbility());

        // As an additional cost to cast creature spells, you may pay any amount of mana. If you do, that creature enters the battlefield with that many additional +1/+1 counters on it.
        this.addAbility(new ChorusOfTheConclaveTriggeredAbility());

    }

    public ChorusOfTheConclave(final ChorusOfTheConclave card) {
        super(card);
    }

    @Override
    public ChorusOfTheConclave copy() {
        return new ChorusOfTheConclave(this);
    }
}

class ChorusOfTheConclaveTriggeredAbility extends TriggeredAbilityImpl<ChorusOfTheConclaveTriggeredAbility> {

    ChorusOfTheConclaveTriggeredAbility() {
        //super(Zone.BATTLEFIELD, new EmptyEffect(""), false);
        super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance(4)), true);
    }

    ChorusOfTheConclaveTriggeredAbility(final ChorusOfTheConclaveTriggeredAbility ability) {
        super(ability);
    }

    @Override
    public ChorusOfTheConclaveTriggeredAbility copy() {
        return new ChorusOfTheConclaveTriggeredAbility(this);
    }

    @Override
    public boolean checkTrigger(GameEvent event, Game game) {
        if (event.getType() == GameEvent.EventType.CAST_SPELL) {
            System.out.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
            return true;
        }
        return false;
    }

    @Override
    public String getRule() {
        return "As an additional cost to cast creature spells, you may pay any amount of mana. If you do, that creature enters the battlefield with that many additional +1/+1 counters on it.";
    }
}
jeffwadsworth
Super Tester Elite
 
Posts: 1171
Joined: 20 Oct 2010, 04:47
Location: USA
Has thanked: 287 times
Been thanked: 69 times

Re: Help implementing a card

Postby LevelX » 30 May 2014, 20:43

jeffwadsworth wrote:Here is some testing code for Chorus of the Conclave. It is not an implementation...just a test of the simple trigger. The "cast_spell" trigger does not work for me in the latest (May 30th) build. I have seen issues like this crop up on my installation and just want to verify that it isn't something else. Does this trigger work for anyone?

[/code]
It does not trigger because the event CAST_SPELL is only used for replacement effects. So only game.replaceEvent() is called and you have to create a replacement effect to react to it.
Chorus of the Conclave's ability isn't a triggered ability.

Chorus of the Conclave creates an additional cost that must be added to the spell ability by a replacement effect.
User avatar
LevelX
DEVELOPER
 
Posts: 1677
Joined: 08 Dec 2011, 15:08
Has thanked: 174 times
Been thanked: 374 times

Re: Help implementing a card

Postby jeffwadsworth » 30 May 2014, 21:12

Ahh. I noticed Molten Disaster which uses the same event check within a TriggeredAbility and thought it would work.

Code: Select all
package mage.sets.modernmasters;

import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.condition.common.KickedCondition;
import mage.abilities.decorator.ConditionalTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continious.GainAbilitySourceEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.KickerAbility;
import mage.abilities.keyword.SplitSecondAbility;
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.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;

/**
 *
 * @author LevelX2
 */
public class MoltenDisaster extends CardImpl<MoltenDisaster> {

    public MoltenDisaster(UUID ownerId) {
        super(ownerId, 123, "Molten Disaster", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{X}{R}{R}");
        this.expansionSetCode = "MMA";

        this.color.setRed(true);

        // If Molten Disaster was kicked, it has split second.
        Ability ability = new ConditionalTriggeredAbility(new MoltenDisasterTriggeredAbility(), KickedCondition.getInstance(), "");
        ability.setRuleAtTheTop(true);
        this.addAbility(ability);
        // Kicker {R}
        this.addAbility(new KickerAbility("{R}"));
        // Molten Disaster deals X damage to each creature without flying and each player.
        this.getSpellAbility().addEffect(new MoltenDisasterEffect());
    }

    public MoltenDisaster(final MoltenDisaster card) {
        super(card);
    }

    @Override
    public MoltenDisaster copy() {
        return new MoltenDisaster(this);
    }
}

class MoltenDisasterTriggeredAbility extends TriggeredAbilityImpl<MoltenDisasterTriggeredAbility> {

    public MoltenDisasterTriggeredAbility() {
        super(Zone.HAND, new GainAbilitySourceEffect(new SplitSecondAbility(), Duration.WhileOnStack), false);
    }

    public MoltenDisasterTriggeredAbility(final MoltenDisasterTriggeredAbility ability) {
        super(ability);
    }

    @Override
    public MoltenDisasterTriggeredAbility copy() {
        return new MoltenDisasterTriggeredAbility(this);
    }

    @Override
    public boolean checkTrigger(GameEvent event, Game game) {
        if (event.getType().equals(GameEvent.EventType.CAST_SPELL) && event.getSourceId().equals(this.getSourceId())) {
            return true;
        }
        return false;
    }

    @Override
    public String getRule() {
        return "If Molten Disaster was kicked, it has split second <i>(As long as this spell is on the stack, players can't cast spells or activate abilities that aren't mana abilities.)</i>";
    }
}

class MoltenDisasterEffect extends OneShotEffect<MoltenDisasterEffect> {

    private static final FilterCreaturePermanent filter = new FilterCreaturePermanent();

    static {
        filter.add(Predicates.not(new AbilityPredicate(FlyingAbility.class)));
    }

    public MoltenDisasterEffect() {
        super(Outcome.Damage);
        staticText = "{this} deals X damage to each creature without flying and each player";
    }

    public MoltenDisasterEffect(final MoltenDisasterEffect effect) {
        super(effect);
    }

    @Override
    public MoltenDisasterEffect copy() {
        return new MoltenDisasterEffect(this);
    }

    @Override
    public boolean apply(Game game, Ability source) {
        int amount = source.getManaCostsToPay().getX();
        for (Permanent permanent: game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) {
            permanent.damage(amount, source.getId(), game, true, false);
        }
        for (UUID playerId: game.getPlayer(source.getControllerId()).getInRange()) {
            Player player = game.getPlayer(playerId);
            if (player != null) {
                player.damage(amount, source.getSourceId(), game, false, true);
            }
        }
        return true;
    }

}
jeffwadsworth
Super Tester Elite
 
Posts: 1171
Joined: 20 Oct 2010, 04:47
Location: USA
Has thanked: 287 times
Been thanked: 69 times

Re: Help implementing a card

Postby LevelX » 30 May 2014, 22:18

jeffwadsworth wrote:Ahh. I noticed Molten Disaster which uses the same event check within a TriggeredAbility and thought it would work.

You selected a bad template in this case. :mrgreen:
I guess Molten Disaster add split second ability won't work as intended.
I add it to my list of cards that have to be fixed.
User avatar
LevelX
DEVELOPER
 
Posts: 1677
Joined: 08 Dec 2011, 15:08
Has thanked: 174 times
Been thanked: 374 times

Re: Help implementing a card

Postby Lysk » 04 Apr 2015, 19:53

Hello everyone,

I am completely new to Java and probably should have started with a less difficult card implementation, but since this one is one of my favourite commanders I thought maybe give it a shot.

So I tried implementing Scion of the Ur-Dragon, hamstering code examples from Entomb and Sarkhan's Triumph. The one thing I cannot get working right away is the correct ability implementation, so it copies the searched card once it's been put into the graveyard.

Since I (still) cannot post my code because it would make my post look "too spamy for a new user" (thanks, forum engine) here's the pastebin link: pastebin[dot]com/xEQTg2EE

Could someone fix it or tell me how to fix it please?

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.timespiral;
 
import java.util.List;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.effects.SearchEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.common.FilterCreatureCard;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCardInLibrary;
 
/**
 *
 * @author Lysk
 */
public class ScionOfTheUrDragon extends CardImpl{
   
    public ScionOfTheUrDragon(UUID ownerId)
    {
        super(ownerId, 246, "Scion of the Ur-Dragon", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, "{W}{U}{B}{R}{G}");
        this.expansionSetCode = "TSP";
       
        this.supertype.add("Legendary");
        this.subtype.add("Dragon");
        this.subtype.add("Avatar");
       
        // WUBRG
        this.color.setRed(true);
        this.color.setBlue(true);
        this.color.setGreen(true);
        this.color.setBlack(true);
        this.color.setWhite(true);
       
        // 4/4
        this.power = new MageInt(4);
        this.toughness = new MageInt(4);
               
        // Flying
        this.addAbility(FlyingAbility.getInstance());
       
        // TODO: The actual ability
    }
 
    public ScionOfTheUrDragon(final ScionOfTheUrDragon card) {
        super(card);
    }
 
    @Override
    public ScionOfTheUrDragon copy() {
        return new ScionOfTheUrDragon(this);
    }
}
 
 
class ScionOfTheUrDragonEffect extends SearchEffect {
   
    private static final FilterCreatureCard filter = new FilterCreatureCard("Dragon creature card");
   
    static {
        filter.add(new SubtypePredicate("Dragon"));
    }
 
  public ScionOfTheUrDragonEffect() {
        super(new TargetCardInLibrary(new FilterCard()), Outcome.Neutral);
        staticText = "Search your library for a Dragon permanent card and put it into your graveyard. If you do, Scion of the Ur-Dragon becomes a copy of that card until end of turn. Then shuffle your library.";
    }
 
    public ScionOfTheUrDragonEffect(final ScionOfTheUrDragonEffect effect) {
        super(effect);
    }
 
    @Override
    public ScionOfTheUrDragonEffect copy() {
        return new ScionOfTheUrDragonEffect(this);
    }
 
    @Override
    public boolean apply(Game game, Ability source) {
        Player controller = game.getPlayer(source.getControllerId());
        if (controller == null) {
            return false;
        }
        boolean result = false;     
        // Search your library for a Dragon permanent card
        TargetCardInLibrary targetDragonPermanent = new TargetCardInLibrary(filter);
        if (controller.searchLibrary(targetDragonPermanent, game)) {
            if (targetDragonPermanent.getTargets().size() > 0) {
                for (UUID cardId: (List<UUID>)targetDragonPermanent.getTargets()) {
                    Card card = controller.getLibrary().remove(cardId, game);
                    if (card != null) {
                        // and put it into your graveyard
                        controller.moveCardToGraveyardWithInfo(card, source.getSourceId(), game, Zone.LIBRARY);                       
                        result = true;
                    }
                }
            }
        }
       
        if(result) {
            // TODO: Scion of the Ur-Dragon becomes a copy of that card until end of turn
        }
           
        controller.shuffleLibrary(game);
        return result;
    }
   

    }
Lysk
 
Posts: 21
Joined: 07 Mar 2015, 11:57
Has thanked: 5 times
Been thanked: 4 times

Re: Help implementing a card

Postby LevelX » 04 Apr 2015, 21:25

Lysk wrote:Hello everyone,
Hiho

Lysk wrote:I am completely new to Java and probably should have started with a less difficult card implementation, but since this one is one of my favourite commanders I thought maybe give it a shot.
Maybe you're right. :D

Lysk wrote:So I tried implementing Scion of the Ur-Dragon, hamstering code examples from Entomb and Sarkhan's Triumph. The one thing I cannot get working right away is the correct ability implementation, so it copies the searched card once it's been put into the graveyard.
You can look at the code of Dimir Doppelganger or Lazav, Dimir Mastermind that should be good examples. Easy to find if you use the Card Tracker where you can filter e.g. only implemented cards (use % as wildcard for rule search).
But copy effects are always a bit hard and for sure there are still some quirks or better said some details in existings codes that don't work correctly in such effects.

Lysk wrote:Since I (still) cannot post my code because it would make my post look "too spamy for a new user" (thanks, forum engine) here's the pastebin link: pastebin[dot]com/xEQTg2EE
Could someone fix it or tell me how to fix it please?
You can starting with post number 5. So it shouldn't be a problem now. I also added your code to your post.

And use the Perl script gen-card.pl to generate the card frame, it's helpful believe me.
E.g. you can remove the color setting from your code it's done automatically now.

If you have more questions don't hesitate to post.
User avatar
LevelX
DEVELOPER
 
Posts: 1677
Joined: 08 Dec 2011, 15:08
Has thanked: 174 times
Been thanked: 374 times

Re: Help implementing a card

Postby TGower » 19 May 2015, 03:16

Sorry for the newbie question, but once I have finished the coding of a card how do I get it to show up in the deck editor/game for testing? Card is Rootwater Diver {U} Creature - Merfolk with Tap, sac: return target artifact from your graveyard to your hand. Code is
Code: Select all
...
/**
 *
 * @author Trevor
 */

public class RootwaterDiver extends CardImpl {
    public RootwaterDiver(UUID ownerId) {
        super(ownerId, 81, "Rootwater Diver", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{U}");
      this.expansionSetCode = "TMP";
        this.subtype.add("Merfolk");
        this.color.setBlue(true);
        this.power = new MageInt(1);
        this.toughness = new MageInt(1);
        this.addAbility(new ColorlessManaAbility());
        Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(), new TapSourceCost());
        ability.addCost(new SacrificeSourceCost());
        ability.addTarget(new TargetCardInYourGraveyard(new FilterArtifactCard("artifact card from your graveyard")));
        this.addAbility(ability);

    }
        public RootwaterDiver(final RootwaterDiver card) {
        super(card);
    }

    @Override
    public RootwaterDiver copy() {
        return new RootwaterDiver(this);
    }
}
Package and imports removed do to spam protection on the forum. File name is a java file named Rootwater Diver inside mage-sets-tempest under Mage Sets->Source Packages. I Committed the file, cleaned Mage root, Mage Client, and Mage Server before doing a Build with Dependencies and running the server and client. Not sure what I'm missing. Thank you.
TGower
 
Posts: 3
Joined: 18 May 2015, 19:04
Has thanked: 0 time
Been thanked: 0 time

Re: Help implementing a card

Postby LevelX » 19 May 2015, 08:04

TGower wrote:Sorry for the newbie question, but once I have finished the coding of a card how do I get it to show up in the deck editor/game for testing? Card is Rootwater Diver {U} Creature - Merfolk with Tap, sac: return target artifact from your graveyard to your hand. Code is
Code: Select all
...
/**
 *
 * @author Trevor
 */

public class RootwaterDiver extends CardImpl {
    public RootwaterDiver(UUID ownerId) {
        super(ownerId, 81, "Rootwater Diver", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{U}");
      this.expansionSetCode = "TMP";
        this.subtype.add("Merfolk");
        this.color.setBlue(true);
        this.power = new MageInt(1);
        this.toughness = new MageInt(1);
        this.addAbility(new ColorlessManaAbility());
        Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ReturnToHandTargetEffect(), new TapSourceCost());
        ability.addCost(new SacrificeSourceCost());
        ability.addTarget(new TargetCardInYourGraveyard(new FilterArtifactCard("artifact card from your graveyard")));
        this.addAbility(ability);

    }
        public RootwaterDiver(final RootwaterDiver card) {
        super(card);
    }

    @Override
    public RootwaterDiver copy() {
        return new RootwaterDiver(this);
    }
}
Package and imports removed do to spam protection on the forum. File name is a java file named Rootwater Diver inside mage-sets-tempest under Mage Sets->Source Packages. I Committed the file, cleaned Mage root, Mage Client, and Mage Server before doing a Build with Dependencies and running the server and client. Not sure what I'm missing. Thank you.
1) Use always the perl script gen-card.pl to create the java class frame. It's much easier than doing it by manually and helps to prevent some simple mistakes. See here to get some info about how to instrall and use it: https://github.com/magefree/mage/wiki/Developer-Getting-Started#perl

2) You don't you see your newly implemented card because the card is not added to the client H2 db. The deck editor works with the H2 client db to show the included cards.
After adding new cards in the IDE you have to check the "force database update" check box in the connect dialog window of the clinet to get the client db updated. Alternatively you can delete all files in the db folder of the client before connecting leading also to a db copy of the server db.
As the xmage server program is started, all missing card entries for the card db are generated from the card classes. For all already existing card records nothing happens this way.
That means if you change the implementation of a card that has influence of the db record, you have to delete the db files of the server before starting the xmage server, so the changes to the card class will be reflected to the db file of the server.
User avatar
LevelX
DEVELOPER
 
Posts: 1677
Joined: 08 Dec 2011, 15:08
Has thanked: 174 times
Been thanked: 374 times

PreviousNext

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