It is currently 25 Apr 2024, 17:32
   
Text Size

Shadows over Innistrad

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

Re: Shadows over Innistrad

Postby kroot » 30 Mar 2016, 21:59

Here's the function that the flipwalkers use to transforms, if curious:

https://github.com/magefree/mage/blob/6 ... ffect.java

Might need to write a "die and return transformed" case?
kroot
 
Posts: 14
Joined: 20 Sep 2015, 00:42
Has thanked: 0 time
Been thanked: 3 times

Re: Shadows over Innistrad

Postby DamnPete » 31 Mar 2016, 18:28

I've been trying to work on The Gitrog Monster. I have found that it doesn't trigger when:
  • Sacrificing land (e.g.: from it's own ability or activating Evolving Wilds)
  • Discarding card from hand (e.g.: cleanup phase)

It does work when a land is moved from top of library to graveyard, having been achieved from Sin Prodder's trigger.

In the code, I notice it triggers off events of the ZONE_CHANGE_GROUP type, which until now apparently only Sidisi, Brood Tyrant has ever used (unless my IDE failed to correctly pick up all usages).
I suppose this event type is used so that the Gitrog's ability doesn't fire off multiple times if more than one land is placed in the Graveyard simultaneously (such as Armageddon effects).
It works fine for Sidisi because she only cares when cards come from the library, in which case "moveCards" will be called, which is where the ZoneChangeGroupEvent is instantiated. However cards moving into Graveyard by other means never trigger a ZoneChangeGroupEvent.

I figure the solution is to capture all events where cards change zones and create a ZoneChangeGroupEvent when this happens, but I don't know where this could be done. My best guess as someone who just started reading the code would be that it could be done somewhere where Simultaneous Event processing happens (GameState afaik). Probably in a similar manner to how it is done in "moveCards", creating a collection of all cards moved by events happening simultaneously?
DamnPete
 
Posts: 1
Joined: 31 Mar 2016, 18:14
Has thanked: 0 time
Been thanked: 0 time

Re: Shadows over Innistrad

Postby escplan9 » 31 Mar 2016, 20:46

Ah, now I see why ZoneEventChangeGroup was used there... either way I'm not sure the best course to resolve the issues there.

On another note, unless someone claims them, I'll start working on some combination of the remaining cards tomorrow (day off):

From Under the Floorboards - done
Pick the Brain - done
Creeping Dread - done

Then we will have all SOI cards implemented (assuming all goes well with the witch being worked on)! There's still about a dozen cards with bugs in them to be fixed as well:

Link to github issues here filtering them -
Code: Select all
https://github.com/magefree/mage/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Abug+SOI
Last edited by escplan9 on 04 Apr 2016, 18:50, edited 5 times in total.
escplan9
 
Posts: 257
Joined: 10 Aug 2015, 22:38
Has thanked: 26 times
Been thanked: 40 times

Re: Shadows over Innistrad

Postby halljared » 01 Apr 2016, 06:03

Witch is almost done. I did some JANK to make it work properly and I'll be eager to get feedback on the pull request. Let's just say I'm either confused about or not-a-fan-of working with the more complex transform cases.
halljared
 
Posts: 11
Joined: 27 Mar 2016, 20:05
Has thanked: 1 time
Been thanked: 3 times

Re: Shadows over Innistrad

Postby escplan9 » 01 Apr 2016, 11:03

Finished From Under the Floorboards. Tested both parts manually.
escplan9
 
Posts: 257
Joined: 10 Aug 2015, 22:38
Has thanked: 26 times
Been thanked: 40 times

Re: Shadows over Innistrad

Postby escplan9 » 01 Apr 2016, 17:30

Pick the Brain done as well. Manually tested both cases.
escplan9
 
Posts: 257
Joined: 10 Aug 2015, 22:38
Has thanked: 26 times
Been thanked: 40 times

Re: Shadows over Innistrad

Postby escplan9 » 01 Apr 2016, 22:20

Working on Creeping Dread. I probably way overcomplicated it. Testing it now. Seems to be working - when the computer discarded a card of the same type, they lost 3 life. Otherwise they just discarded a card and no life was lost.

My ugly monstrosity of code for it:
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.shadowsoverinnistrad;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Rarity;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;

/**
 *
 * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com)
 */
public class CreepingDread extends CardImpl {

    public CreepingDread(UUID ownerId) {
        super(ownerId, 104, "Creeping Dread", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{3}{B}");
        this.expansionSetCode = "SOI";

        // At the beginning of your upkeep, each player discards a card. Each opponent who discarded a card that shares a card type with the card you discarded loses 3 life.
        this.addAbility(new BeginningOfUpkeepTriggeredAbility(new CreepingDreadEffect(), TargetController.YOU, false));
    }

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

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

class CreepingDreadEffect extends OneShotEffect {

    public CreepingDreadEffect() {
        super(Outcome.Detriment);
        this.staticText = "each player discards a card. Each opponent who discarded a card that shares a card type with the card you discarded loses 3 life.";
    }

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

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

    /*
    * When a spell or ability instructs each player to discard a card,
    starting with the player whose turn it is and proceeding in turn order,
    each player selects a card from his or her hand without revealing it,
    sets it aside, and then all of those cards are revealed and discarded at once.
   
    http://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid=409851
    */
    @Override
    public boolean apply(Game game, Ability source) {
               
        // controller discards a card - store info on card type
        Player controller = game.getPlayer(source.getControllerId());
        if (controller != null) {
           
            List<CardType> typesChosen = new ArrayList<>();
            Map<Player,Card> cardsChosen = new HashMap<>();
            if(!controller.getHand().isEmpty()) {     
               
                TargetCard controllerTarget = new TargetCard(Zone.HAND, new FilterCard());
                if(controller.choose(Outcome.Discard, controller.getHand(), controllerTarget, game)) {
                    Card card = controller.getHand().get(controllerTarget.getFirstTarget(), game);
                    if (card != null) {
                        typesChosen = card.getCardType();
                        cardsChosen.put(controller, card);
                    }
                }
            }
           
            ArrayList<Player> opponentsAffected = new ArrayList<>();
            for (UUID playerId : game.getOpponents(source.getControllerId())) {
                Player opponent = game.getPlayer(playerId);
                // opponent discards a card - if it is same card type as controller, add to opponentsAffected
                if(!opponent.getHand().isEmpty()) {
                    TargetCard target = new TargetCard(Zone.HAND, new FilterCard());
                    if(opponent.choose(Outcome.Discard, opponent.getHand(), target, game)) {
                        Card card = opponent.getHand().get(target.getFirstTarget(), game);
                        if (card != null) {                           
                            if (!typesChosen.isEmpty()) {
                                for (CardType cType : typesChosen) {
                                    for (CardType oType : card.getCardType()) {
                                        if (cType == oType) {
                                            opponentsAffected.add(opponent);
                                            break;
                                        }
                                    }
                                }
                            }   
                           
                            cardsChosen.put(opponent, card);
                        }
                    }
                }
            }

            // each opponent who discarded a card of the same type loses 3 life
            if (!opponentsAffected.isEmpty()) {
                for(Player opponent : opponentsAffected) {
                    opponent.loseLife(3, game);
                }
            }
           
            // everyone discards the card at the same time
            if (!cardsChosen.isEmpty()) {               
                for (Map.Entry<Player, Card> entry : cardsChosen.entrySet()) {
                    Player player = entry.getKey();
                    Card cardChosen = entry.getValue();
                    if (player != null && cardChosen != null) {
                        player.discard(cardChosen, source, game);
                    }
                }
            }
           
            return true;
        }

        return false;
    }
}
Finished Creeping Dread. For now. Will try to create some multi-player tests to confirm works as expected - worked in 1v1 fine.

edit: Added a bunch of multi-player tests. All check out. Code is ugly, but works perfectly as far as I can tell.
escplan9
 
Posts: 257
Joined: 10 Aug 2015, 22:38
Has thanked: 26 times
Been thanked: 40 times

Re: Shadows over Innistrad

Postby escplan9 » 04 Apr 2016, 18:51

All SOI cards are implemented now! Our priority now is fixing the remaining bugs:

Link to github issues here filtering them (14 remaining as of now) -
Code: Select all
https://github.com/magefree/mage/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Abug+SOI
Top priority cards are the ones with the highest likelihood of constructed play:

Gitrog Monster - land in grave does not consistently trigger
Jace, Unraveler of Secrets - emblem does not counter any spells
Archangel Avacyn // Avacyn, the Purifier - damages herself upon transformation
Prized Amalgam - does not trigger from Gravecrawler cast from grave
Startled Awake - returns as enchantment/sorcery from grave
Engulf The Shore - does not return tokens
Eerie Interlude - only returns one creature from exile
escplan9
 
Posts: 257
Joined: 10 Aug 2015, 22:38
Has thanked: 26 times
Been thanked: 40 times

Re: Shadows over Innistrad

Postby halljared » 05 Apr 2016, 00:31

The Gitrog bug is pretty annoying. I have a fix that's implemented but bugged. I understand the bug and should be able to fix it by early tomorrow. I'm out for the night though.
halljared
 
Posts: 11
Joined: 27 Mar 2016, 20:05
Has thanked: 1 time
Been thanked: 3 times

Re: Shadows over Innistrad

Postby escplan9 » 06 Apr 2016, 19:59

Thanks for all your time and contributions everyone! We now only have one remaining SOI specific bug to resolve:

Jace, Unraveler of Secrets - emblem does not counter any spells
https://github.com/magefree/mage/issues/1739

And possibly the sealed pool generation?
https://github.com/magefree/mage/issues/1727
escplan9
 
Posts: 257
Joined: 10 Aug 2015, 22:38
Has thanked: 26 times
Been thanked: 40 times

Re: Shadows over Innistrad

Postby escplan9 » 07 Apr 2016, 14:12

Corrupted Grafstone was never implemented! Not sure if we are missing any other cards. spjspj started working on this and had to go to sleep and sent me what he has so far. I'm at work so cannot devote much time to this until later tonight (at least 7 hours from now). See code below:

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.shadowsoverinnistrad;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.Mana;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTappedAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.common.ManaEffect;
import mage.abilities.mana.ManaAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
import mage.constants.CardType;
import mage.constants.ColoredManaSymbol;
import mage.constants.Rarity;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;

/**
 *
 * @author spjspj
 */
public class CorruptedGrafstone extends CardImpl {

    public CorruptedGrafstone(UUID ownerId) {
        super(ownerId, 253, "Corrupted Grafstone", Rarity.RARE, new CardType[]{CardType.ARTIFACT}, "{2}");
        this.expansionSetCode = "SOI";

        // Corrupted Grafstone enters the battlefield tapped.
        this.addAbility(new EntersBattlefieldTappedAbility());

        // {T}: Choose a color of a card in your graveyard. Add one mana of that color to your mana pool.
        this.addAbility(new CorruptedGrafstoneManaAbility());
    }

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

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


class CorruptedGrafstoneManaAbility extends ManaAbility {

    public CorruptedGrafstoneManaAbility() {
        super(Zone.BATTLEFIELD, new CorruptedGrafstoneManaEffect(), new TapSourceCost());
    }

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

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

    @Override
    public List<Mana> getNetMana(Game game) {
        return ((CorruptedGrafstoneManaEffect) getEffects().get(0)).getNetMana(game, this);
    }
}


class CorruptedGrafstoneManaEffect extends ManaEffect {

    private final Mana computedMana;

    public CorruptedGrafstoneManaEffect() {
        super();
        computedMana = new Mana();
        this.staticText = "Choose a color of a card in your graveyard. Add one mana of that color to your mana pool";
    }

    public CorruptedGrafstoneManaEffect(final CorruptedGrafstoneManaEffect effect) {
        super(effect);
        this.computedMana = effect.computedMana.copy();
    }

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

    @Override
    public boolean apply(Game game, Ability source) {
        Mana types = getManaTypesInGraveyard(game, source);
        Choice choice = new ChoiceImpl(true);
        choice.setMessage("Pick a mana color");
        if (types.getBlack() > 0) {
            choice.getChoices().add("Black");
        }
        if (types.getRed() > 0) {
            choice.getChoices().add("Red");
        }
        if (types.getBlue() > 0) {
            choice.getChoices().add("Blue");
        }
        if (types.getGreen() > 0) {
            choice.getChoices().add("Green");
        }
        if (types.getWhite() > 0) {
            choice.getChoices().add("White");
        }
        if (!choice.getChoices().isEmpty()) {
            Player player = game.getPlayer(source.getControllerId());
            if (choice.getChoices().size() == 1) {
                choice.setChoice(choice.getChoices().iterator().next());
            } else {
                player.choose(outcome, choice, game);
            }
            if (choice.getChoice() != null) {
                Mana computedMana = new Mana();
                switch (choice.getChoice()) {
                    case "Black":
                        computedMana.setBlack(1);
                        break;
                    case "Blue":
                        computedMana.setBlue(1);
                        break;
                    case "Red":
                        computedMana.setRed(1);
                        break;
                    case "Green":
                        computedMana.setGreen(1);
                        break;
                    case "White":
                        computedMana.setWhite(1);
                        break;
                }
                checkToFirePossibleEvents(computedMana, game, source);
                player.getManaPool().addMana(computedMana, game, source);
            }
        }
        return true;
    }

    @Override
    public Mana getMana(Game game, Ability source) {
        return null;
    }
   
    public List<Mana> getNetMana(Game game, Ability source) {
        List<Mana> netManas = new ArrayList<>();
        Mana types = getManaTypesInGraveyard(game, source);
        if (types.getBlack() > 0) {
            netManas.add(new Mana(ColoredManaSymbol.B));
        }
        if (types.getRed() > 0) {
            netManas.add(new Mana(ColoredManaSymbol.R));
        }
        if (types.getBlue() > 0) {
            netManas.add(new Mana(ColoredManaSymbol.U));
        }
        if (types.getGreen() > 0) {
            netManas.add(new Mana(ColoredManaSymbol.G));
        }
        if (types.getWhite() > 0) {
            netManas.add(new Mana(ColoredManaSymbol.W));
        }
        if (types.getGeneric() > 0) {
            netManas.add(new Mana(0, 0, 0, 0, 0, 0, 0, 1));
        }
        return netManas;
    }

    private Mana getManaTypesInGraveyard(Game game, Ability source) {

        if (source != null && source.getControllerId() != null) {
            Player controller = game.getPlayer(source.getControllerId());

            if (controller != null) {
               
                // check for Graveyard not being empty or having colorless in here somewhere
               
                for (Card card : controller.getGraveyard().getCards(game)) {
                    Mana types = new Mana();

                    for (ObjectColor color : card.getColor(game).getColors()) {
                        if (color.isWhite()) {
                            types.setWhite(1);
                        }
                        if (color.isBlue()) {
                            types.setBlue(1);
                        }
                        if (color.isBlack()) {
                            types.setBlack(1);
                        }
                        if (color.isRed()) {
                            types.setRed(1);
                        }
                        if (color.isGreen()) {
                            types.setGreen(1);
                        }
                    }
                    return types;
                }
            }
        }
       
        return null;
    }
}
I did some minor testing on the card and it needs some work:

* No cards in the grave throws an exception on "too many iterations of player priority" or something.
* Only colorless cards in grave still allows tapping it, but produces no mana and no option given
* One color in grave taps for that color, with no choice given
* More than one color in grave taps for the "first" color in grave??? with no choice given

edit: To clarify, this is the current code spjspj wrote and its current behavior. I will not have time to work on this card until much later tonight. From what I recall from discussion last night, LevelX may be pushing a release later tonight as well - so someone can take over the card from here to make sure it gets in with the release tonight.
escplan9
 
Posts: 257
Joined: 10 Aug 2015, 22:38
Has thanked: 26 times
Been thanked: 40 times

Re: Shadows over Innistrad

Postby fireshoes » 07 Apr 2016, 15:04

For bullets 2 & 3, that sounds like the correct functionality (if only one choice, seems fine to streamline and make that choice for them).

I think for bullet 1, you will need a return false on the else part of "if (choice.getChoice() != null)". Otherwise it will get to the check to fire event and add mana part below that, I believe.

Probably should check for player != null after "Player player = game.getPlayer(source.getControllerId());" as well.
User avatar
fireshoes
 
Posts: 536
Joined: 20 Aug 2014, 03:51
Has thanked: 201 times
Been thanked: 49 times

Re: Shadows over Innistrad

Postby Doctor Weird » 07 Apr 2016, 15:20

escplan9 wrote:* Only colorless cards in grave still allows tapping it, but produces no mana and no option given
Colorless is not a color, so you can't add colorless mana with Corrupted Grafstone. You can still tap it, because it doesn't require a valid target to be chosen, and because the choosing of a color is part of the effect, not the cost, it just won't do anything once it resolves because there are no colors to choose.

And yes, if there is only one choice there's no point in asking, just streamlining it should be fine, there's no "may" so the player would be forced to choose a color anyway, might as well skip the small waste of time.
Doctor Weird
 
Posts: 180
Joined: 25 May 2015, 01:33
Has thanked: 7 times
Been thanked: 52 times

Re: Shadows over Innistrad

Postby escplan9 » 07 Apr 2016, 15:25

I didn't mean to say all 4 cases were wrong. Just saying I do not have time to further work on the card until much later tonight, but this is what I noticed so far incase someone has time to take it over. I believe LevelX said he was planning on another release tonight to deal with some of the major bugs. Might as well get this card in there if we can.
escplan9
 
Posts: 257
Joined: 10 Aug 2015, 22:38
Has thanked: 26 times
Been thanked: 40 times

Re: Shadows over Innistrad

Postby escplan9 » 08 Apr 2016, 15:57

Spjspj completed Corrupted Grafstone by the way. We should be all set with original card implementation for SOI now. Just have more bugs to patch up.
escplan9
 
Posts: 257
Joined: 10 Aug 2015, 22:38
Has thanked: 26 times
Been thanked: 40 times

Previous

Return to Developers Talk

Who is online

Users browsing this forum: No registered users and 9 guests


Who is online

In total there are 9 users online :: 0 registered, 0 hidden and 9 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 9 guests

Login Form