diff --git a/Mage.Sets/src/mage/cards/s/SpellweaverVolute.java b/Mage.Sets/src/mage/cards/s/SpellweaverVolute.java new file mode 100644 index 00000000000..ea17f9d45c0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SpellweaverVolute.java @@ -0,0 +1,150 @@ +/* + * 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.cards.s; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; + +/** + * + * @author LevelX2 + */ +public class SpellweaverVolute extends CardImpl { + + public SpellweaverVolute(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{U}{U}"); + + this.subtype.add(SubType.AURA); + + // Enchant instant card in a graveyard + FilterCard filter = new FilterCard("instant card in a graveyard"); + filter.add(new CardTypePredicate(CardType.INSTANT)); + TargetCardInGraveyard auraTarget = new TargetCardInGraveyard(filter); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.Detriment)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // Whenever you cast a sorcery spell, copy the enchanted instant card. You may cast the copy without paying its mana cost. + // If you do, exile the enchanted card and attach Spellweaver Volute to another instant card in a graveyard. + FilterSpell filterSpell = new FilterSpell("a sorcery spell"); + filterSpell.add(new CardTypePredicate(CardType.SORCERY)); + this.addAbility(new SpellCastControllerTriggeredAbility(new SpellweaverVoluteEffect(), filterSpell, false)); + } + + public SpellweaverVolute(final SpellweaverVolute card) { + super(card); + } + + @Override + public SpellweaverVolute copy() { + return new SpellweaverVolute(this); + } +} + +class SpellweaverVoluteEffect extends OneShotEffect { + + public SpellweaverVoluteEffect() { + super(Outcome.Benefit); + this.staticText = "copy the enchanted instant card. You may cast the copy without paying its mana cost. \n" + + "If you do, exile the enchanted card and attach {this} to another instant card in a graveyard"; + } + + public SpellweaverVoluteEffect(final SpellweaverVoluteEffect effect) { + super(effect); + } + + @Override + public SpellweaverVoluteEffect copy() { + return new SpellweaverVoluteEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + if (sourcePermanent != null && sourcePermanent.getAttachedTo() != null) { + Card enchantedCard = game.getCard(sourcePermanent.getAttachedTo()); + if (enchantedCard != null && game.getState().getZone(enchantedCard.getId()) == Zone.GRAVEYARD) { + Player ownerEnchanted = game.getPlayer(enchantedCard.getOwnerId()); + if (ownerEnchanted != null + && controller.chooseUse(outcome, "Create a copy of " + enchantedCard.getName() + '?', source, game)) { + Card copiedCard = game.copyCard(enchantedCard, source, source.getControllerId()); + if (copiedCard != null) { + ownerEnchanted.getGraveyard().add(copiedCard); + game.getState().setZone(copiedCard.getId(), Zone.GRAVEYARD); + if (controller.chooseUse(outcome, "Cast the copied card without paying mana cost?", source, game)) { + if (copiedCard.getSpellAbility() != null) { + controller.cast(copiedCard.getSpellAbility(), game, true); + } + if (controller.moveCards(enchantedCard, Zone.EXILED, source, game)) { + FilterCard filter = new FilterCard("instant card in a graveyard"); + filter.add(new CardTypePredicate(CardType.INSTANT)); + TargetCardInGraveyard auraTarget = new TargetCardInGraveyard(filter); + if (auraTarget.canChoose(source.getSourceId(), controller.getId(), game)) { + controller.choose(outcome, auraTarget, source.getSourceId(), game); + Card newAuraTarget = game.getCard(auraTarget.getFirstTarget()); + if (newAuraTarget != null) { + if (enchantedCard.getId().equals(newAuraTarget.getId())) { + } else if (newAuraTarget.addAttachment(sourcePermanent.getId(), game)) { + game.informPlayers(sourcePermanent.getLogName() + " was attached to " + newAuraTarget.getLogName()); + } + } + } + } + + } + } + } + } + + } + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/FutureSight.java b/Mage.Sets/src/mage/sets/FutureSight.java index 6edb2d5ce9b..9f578ac5ff6 100644 --- a/Mage.Sets/src/mage/sets/FutureSight.java +++ b/Mage.Sets/src/mage/sets/FutureSight.java @@ -200,6 +200,7 @@ public class FutureSight extends ExpansionSet { cards.add(new SetCardInfo("Snake Cult Initiation", 89, Rarity.UNCOMMON, mage.cards.s.SnakeCultInitiation.class)); cards.add(new SetCardInfo("Soultether Golem", 164, Rarity.UNCOMMON, mage.cards.s.SoultetherGolem.class)); cards.add(new SetCardInfo("Sparkspitter", 109, Rarity.UNCOMMON, mage.cards.s.Sparkspitter.class)); + cards.add(new SetCardInfo("Spellweaver Volute", 59, Rarity.RARE, mage.cards.s.SpellweaverVolute.class)); cards.add(new SetCardInfo("Spellwild Ouphe", 151, Rarity.UNCOMMON, mage.cards.s.SpellwildOuphe.class)); cards.add(new SetCardInfo("Spin into Myth", 60, Rarity.UNCOMMON, mage.cards.s.SpinIntoMyth.class)); cards.add(new SetCardInfo("Spirit en-Dal", 17, Rarity.UNCOMMON, mage.cards.s.SpiritEnDal.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/EnchantingGraveyardCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/EnchantingGraveyardCardsTest.java new file mode 100644 index 00000000000..13bf22dd561 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/EnchantingGraveyardCardsTest.java @@ -0,0 +1,220 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.enchantments; + +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class EnchantingGraveyardCardsTest extends CardTestPlayerBase { + + static final String LIGHTNING_BOLT = "Lightning Bolt"; + static final String SPELLWEAVER_VOLUTE = "Spellweaver Volute"; + + /** + * Test that a card in the graveyard can be enchanted + */ + @Test + public void testSpellwaeverVoluteNormal() { + // Enchant instant card in a graveyard + // Whenever you cast a sorcery spell, copy the enchanted instant card. You may cast the copy without paying its mana cost. + // If you do, exile the enchanted card and attach Spellweaver Volute to another instant card in a graveyard. + addCard(Zone.HAND, playerA, SPELLWEAVER_VOLUTE, 1); // Enchantment Aura {3}{U}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + // Lightning Bolt deals 3 damage to target creature or player. + addCard(Zone.GRAVEYARD, playerB, "Lightning Bolt", 1); // Instant {R} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, SPELLWEAVER_VOLUTE, LIGHTNING_BOLT); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertHandCount(playerA, 0); + assertPermanentCount(playerA, SPELLWEAVER_VOLUTE, 1); + assertGraveyardCount(playerB, LIGHTNING_BOLT, 1); + + Permanent spellweaver = getPermanent(SPELLWEAVER_VOLUTE); + Card attachedToCard = null; + if (spellweaver != null) { + attachedToCard = playerB.getGraveyard().get(spellweaver.getAttachedTo(), currentGame); + } + Assert.assertTrue(SPELLWEAVER_VOLUTE + " has to be attached to Lightning Bolt in graveyard", attachedToCard != null && attachedToCard.getName().equals(LIGHTNING_BOLT)); + } + + /** + * Test that a card in the graveyard can be enchanted and the enchanted card + * switches to a new one + */ + @Test + public void testSpellwaeverVoluteAndSorcery() { + + // Enchant instant card in a graveyard + // Whenever you cast a sorcery spell, copy the enchanted instant card. You may cast the copy without paying its mana cost. + // If you do, exile the enchanted card and attach Spellweaver Volute to another instant card in a graveyard. + addCard(Zone.HAND, playerA, SPELLWEAVER_VOLUTE, 1); // Enchantment Aura {3}{U}{U} + addCard(Zone.HAND, playerA, "Cloak of Feathers"); // Sorcery {U} + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + addCard(Zone.GRAVEYARD, playerA, "Aerial Volley", 1); // Instant {G} + + // Lightning Bolt deals 3 damage to target creature or player. + addCard(Zone.GRAVEYARD, playerB, "Lightning Bolt", 1); // Instant {R} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, SPELLWEAVER_VOLUTE, LIGHTNING_BOLT); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloak of Feathers", "Silvercoat Lion"); + setChoice(playerA, "Yes"); // play the L. Bold + addTarget(playerA, playerB); // Add Target for the L. Bold + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 17); + + assertHandCount(playerA, 1); + assertGraveyardCount(playerA, "Cloak of Feathers", 1); + assertPermanentCount(playerA, SPELLWEAVER_VOLUTE, 1); + assertGraveyardCount(playerA, "Aerial Volley", 1); + assertExileCount(playerB, LIGHTNING_BOLT, 1); + assertAbility(playerA, "Silvercoat Lion", FlyingAbility.getInstance(), true); + Permanent spellweaver = getPermanent(SPELLWEAVER_VOLUTE); + Card attachedToCard = null; + if (spellweaver != null) { + attachedToCard = playerA.getGraveyard().get(spellweaver.getAttachedTo(), currentGame); + } + Assert.assertTrue(SPELLWEAVER_VOLUTE + " has to be attached to Aerial Volley in graveyard", attachedToCard != null && attachedToCard.getName().equals("Aerial Volley")); + + assertHandCount(playerA, 1); + + } + + /** + * Test that a card in the graveyard can be enchanted and the enchanted card + * switches to a new one + */ + @Test + public void testSpellwaeverVoluteAndSorceryWithoutNewTarget() { + + // Enchant instant card in a graveyard + // Whenever you cast a sorcery spell, copy the enchanted instant card. You may cast the copy without paying its mana cost. + // If you do, exile the enchanted card and attach Spellweaver Volute to another instant card in a graveyard. + addCard(Zone.HAND, playerA, SPELLWEAVER_VOLUTE, 1); // Enchantment Aura {3}{U}{U} + addCard(Zone.HAND, playerA, "Cloak of Feathers"); // Sorcery {U} + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + + // Lightning Bolt deals 3 damage to target creature or player. + addCard(Zone.GRAVEYARD, playerB, "Lightning Bolt", 1); // Instant {R} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, SPELLWEAVER_VOLUTE, LIGHTNING_BOLT); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cloak of Feathers", "Silvercoat Lion"); + setChoice(playerA, "Yes"); // play the L. Bold + addTarget(playerA, playerB); // Add Target for the L. Bold + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 17); + + assertHandCount(playerA, 1); + assertGraveyardCount(playerA, "Cloak of Feathers", 1); + assertPermanentCount(playerA, SPELLWEAVER_VOLUTE, 0); + assertGraveyardCount(playerA, SPELLWEAVER_VOLUTE, 1); + assertExileCount(playerB, LIGHTNING_BOLT, 1); + assertAbility(playerA, "Silvercoat Lion", FlyingAbility.getInstance(), true); + + assertGraveyardCount(playerA, SPELLWEAVER_VOLUTE, 1); + + for (Card card : currentGame.getExile().getAllCards(currentGame)) { + if (card.getName().equals(LIGHTNING_BOLT)) { + Assert.assertTrue(LIGHTNING_BOLT + " may not have any attachments", card.getAttachments().isEmpty()); + + } + } + assertHandCount(playerA, 1); + + } + + /** + * Test that a card in the graveyard can be enchanted and if the Enchantment + * returns to hand, the enchanting ends + */ + @Test + public void testSpellwaeverVoluteAndReturnToHand() { + + // Enchant instant card in a graveyard + // Whenever you cast a sorcery spell, copy the enchanted instant card. You may cast the copy without paying its mana cost. + // If you do, exile the enchanted card and attach Spellweaver Volute to another instant card in a graveyard. + addCard(Zone.HAND, playerA, SPELLWEAVER_VOLUTE, 1); // Enchantment Aura {3}{U}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + // Lightning Bolt deals 3 damage to target creature or player. + addCard(Zone.GRAVEYARD, playerB, "Lightning Bolt", 1); // Instant {R} + + // Return target permanent to its owner's hand. + addCard(Zone.HAND, playerB, "Boomerang", 1); // Instant {U}{U} + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, SPELLWEAVER_VOLUTE, LIGHTNING_BOLT); + + castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, "Boomerang", SPELLWEAVER_VOLUTE); + + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertHandCount(playerA, SPELLWEAVER_VOLUTE, 1); + assertGraveyardCount(playerB, "Boomerang", 1); + assertPermanentCount(playerA, SPELLWEAVER_VOLUTE, 0); + + for (Card card : playerB.getGraveyard().getCards(currentGame)) { + if (card.getName().equals(LIGHTNING_BOLT)) { + Assert.assertTrue(LIGHTNING_BOLT + " may not have any attachments", card.getAttachments().isEmpty()); + + } + } + assertHandCount(playerA, 1); + + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/AttachEffect.java b/Mage/src/main/java/mage/abilities/effects/common/AttachEffect.java index 2fe23522239..efe93399ea8 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/AttachEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/AttachEffect.java @@ -29,10 +29,12 @@ package mage.abilities.effects.common; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; import mage.constants.Outcome; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.TargetCard; /** * @@ -72,9 +74,16 @@ public class AttachEffect extends OneShotEffect { if (player != null) { return player.addAttachment(source.getSourceId(), game); } + if (source.getTargets().get(0) instanceof TargetCard) { // e.g. Spellweaver Volute + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card != null) { + return card.addAttachment(source.getSourceId(), game); + } + } } } } + return false; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java index f707e908249..7af38326fb4 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ChooseCreatureTypeEffect.java @@ -27,14 +27,11 @@ */ package mage.abilities.effects.common; -import java.util.LinkedHashSet; -import java.util.stream.Collectors; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.choices.Choice; import mage.choices.ChoiceCreatureType; -import mage.choices.ChoiceImpl; import mage.constants.Outcome; import mage.constants.SubType; import mage.game.Game; diff --git a/Mage/src/main/java/mage/cards/Card.java b/Mage/src/main/java/mage/cards/Card.java index 728951f59e4..16a30646ee0 100644 --- a/Mage/src/main/java/mage/cards/Card.java +++ b/Mage/src/main/java/mage/cards/Card.java @@ -27,6 +27,8 @@ */ package mage.cards; +import java.util.List; +import java.util.UUID; import mage.MageObject; import mage.Mana; import mage.ObjectColor; @@ -42,19 +44,14 @@ import mage.game.Game; import mage.game.GameState; import mage.game.permanent.Permanent; -import java.util.List; -import java.util.UUID; - public interface Card extends MageObject { - final String regexBlack = ".*\\x7b.{0,2}B.{0,2}\\x7d.*"; final String regexBlue = ".*\\x7b.{0,2}U.{0,2}\\x7d.*"; final String regexRed = ".*\\x7b.{0,2}R.{0,2}\\x7d.*"; final String regexGreen = ".*\\x7b.{0,2}G.{0,2}\\x7d.*"; final String regexWhite = ".*\\x7b.{0,2}W.{0,2}\\x7d.*"; - UUID getOwnerId(); String getCardNumber(); @@ -248,4 +245,9 @@ public interface Card extends MageObject { return mana; } + List getAttachments(); + + boolean addAttachment(UUID permanentId, Game game); + + boolean removeAttachment(UUID permanentId, Game game); } diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index d2f8d1a2300..4c9aaf5397b 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -94,6 +94,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card { protected boolean morphCard; protected boolean allCreatureTypes; + protected List attachments = new ArrayList<>(); + public CardImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs) { this(ownerId, setInfo, cardTypes, costs, SpellAbilityType.BASE); } @@ -169,6 +171,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card { flipCardName = card.flipCardName; splitCard = card.splitCard; usesVariousArt = card.usesVariousArt; + this.attachments.addAll(card.attachments); } @Override @@ -840,11 +843,53 @@ public abstract class CardImpl extends MageObjectImpl implements Card { return super.getSubtype(game); } + @Override public boolean isAllCreatureTypes() { return allCreatureTypes; } + @Override public void setIsAllCreatureTypes(boolean value) { allCreatureTypes = value; } + + @Override + public List getAttachments() { + return attachments; + } + + @Override + public boolean addAttachment(UUID permanentId, Game game) { + if (!this.attachments.contains(permanentId)) { + Permanent attachment = game.getPermanent(permanentId); + if (attachment == null) { + attachment = game.getPermanentEntering(permanentId); + } + if (attachment != null) { + if (!game.replaceEvent(new GameEvent(GameEvent.EventType.ATTACH, objectId, permanentId, attachment.getControllerId()))) { + this.attachments.add(permanentId); + attachment.attachTo(objectId, game); + game.fireEvent(new GameEvent(GameEvent.EventType.ATTACHED, objectId, permanentId, attachment.getControllerId())); + return true; + } + } + } + return false; + } + + @Override + public boolean removeAttachment(UUID permanentId, Game game) { + if (this.attachments.contains(permanentId)) { + Permanent attachment = game.getPermanent(permanentId); + if (attachment != null) { + attachment.unattach(game); + } + if (!game.replaceEvent(new GameEvent(GameEvent.EventType.UNATTACH, objectId, permanentId, attachment != null ? attachment.getControllerId() : null))) { + this.attachments.remove(permanentId); + game.fireEvent(new GameEvent(GameEvent.EventType.UNATTACHED, objectId, permanentId, attachment != null ? attachment.getControllerId() : null)); + return true; + } + } + return false; + } } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 7b5e10477b5..52c0856a99a 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1901,6 +1901,15 @@ public abstract class GameImpl implements Game, Serializable { } } } + } else if (target instanceof TargetCard) { + Card attachedTo = getCard(perm.getAttachedTo()); + if (attachedTo == null + || !((TargetCard) spellAbility.getTargets().get(0)).canTarget(perm.getControllerId(), perm.getAttachedTo(), spellAbility, this)) { + if (movePermanentToGraveyardWithInfo(perm)) { + attachedTo.removeAttachment(perm.getId(), this); + somethingHappened = true; + } + } } } } diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 34c8357a0dc..c4997302d54 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -233,7 +233,8 @@ public class GameEvent implements Serializable { PAID_CUMULATIVE_UPKEEP, DIDNT_PAY_CUMULATIVE_UPKEEP, //permanent events - ENTERS_THE_BATTLEFIELD_SELF, // 616.1a If any of the replacement and/or prevention effects are self-replacement effects (see rule 614.15), one of them must be chosen. If not, proceed to rule 616.1b. + ENTERS_THE_BATTLEFIELD_SELF, /* 616.1a If any of the replacement and/or prevention effects are self-replacement effects (see rule 614.15), + one of them must be chosen. If not, proceed to rule 616.1b. */ ENTERS_THE_BATTLEFIELD_CONTROL, // 616.1b ENTERS_THE_BATTLEFIELD_COPY, // 616.1c ENTERS_THE_BATTLEFIELD, // 616.1d diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index 45e8226439f..b361f3abe55 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -116,10 +116,11 @@ public interface Permanent extends Card, Controllable { void attachTo(UUID permanentId, Game game); - boolean addAttachment(UUID permanentId, Game game); - - boolean removeAttachment(UUID permanentId, Game game); + void unattach(Game game); +// boolean addAttachment(UUID permanentId, Game game); +// +// boolean removeAttachment(UUID permanentId, Game game); boolean canBeTargetedBy(MageObject source, UUID controllerId, Game game); boolean hasProtectionFrom(MageObject source, Game game); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 95c27b3c634..b3beae95df6 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -56,6 +56,7 @@ import mage.game.permanent.token.SquirrelToken; import mage.game.stack.Spell; import mage.game.stack.StackObject; import mage.players.Player; +import mage.target.TargetCard; import mage.util.CardUtil; import mage.util.GameLog; import mage.util.ThreadLocalStringBuilder; @@ -103,7 +104,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { protected int maxBlockedBy = 0; protected boolean removedFromCombat; protected boolean deathtouched; - protected List attachments = new ArrayList<>(); + protected Map> connectedCards = new HashMap<>(); protected Set dealtDamageByThisTurn; protected UUID attachedTo; @@ -147,7 +148,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { this.blocking = permanent.blocking; this.maxBlocks = permanent.maxBlocks; this.deathtouched = permanent.deathtouched; - this.attachments.addAll(permanent.attachments); +// this.attachments.addAll(permanent.attachments); for (Map.Entry> entry : permanent.connectedCards.entrySet()) { this.connectedCards.put(entry.getKey(), entry.getValue()); } @@ -626,47 +627,46 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } return false; } +// +// @Override +// public List getAttachments() { +// return attachments; +// } - @Override - public List getAttachments() { - return attachments; - } - - @Override - public boolean addAttachment(UUID permanentId, Game game) { - if (!this.attachments.contains(permanentId)) { - if (!game.replaceEvent(new GameEvent(GameEvent.EventType.ATTACH, objectId, permanentId, controllerId))) { - this.attachments.add(permanentId); - Permanent attachment = game.getPermanent(permanentId); - if (attachment == null) { - attachment = game.getPermanentEntering(permanentId); - } - if (attachment != null) { - attachment.attachTo(objectId, game); - game.fireEvent(new GameEvent(GameEvent.EventType.ATTACHED, objectId, permanentId, controllerId)); - return true; - } - } - } - return false; - } - - @Override - public boolean removeAttachment(UUID permanentId, Game game) { - if (this.attachments.contains(permanentId)) { - if (!game.replaceEvent(new GameEvent(GameEvent.EventType.UNATTACH, objectId, permanentId, controllerId))) { - this.attachments.remove(permanentId); - Permanent attachment = game.getPermanent(permanentId); - if (attachment != null) { - attachment.attachTo(null, game); - } - game.fireEvent(new GameEvent(GameEvent.EventType.UNATTACHED, objectId, permanentId, controllerId)); - return true; - } - } - return false; - } - +// @Override +// public boolean addAttachment(UUID permanentId, Game game) { +// if (!this.attachments.contains(permanentId)) { +// if (!game.replaceEvent(new GameEvent(GameEvent.EventType.ATTACH, objectId, permanentId, controllerId))) { +// this.attachments.add(permanentId); +// Permanent attachment = game.getPermanent(permanentId); +// if (attachment == null) { +// attachment = game.getPermanentEntering(permanentId); +// } +// if (attachment != null) { +// attachment.attachTo(objectId, game); +// game.fireEvent(new GameEvent(GameEvent.EventType.ATTACHED, objectId, permanentId, controllerId)); +// return true; +// } +// } +// } +// return false; +// } +// +// @Override +// public boolean removeAttachment(UUID permanentId, Game game) { +// if (this.attachments.contains(permanentId)) { +// if (!game.replaceEvent(new GameEvent(GameEvent.EventType.UNATTACH, objectId, permanentId, controllerId))) { +// this.attachments.remove(permanentId); +// Permanent attachment = game.getPermanent(permanentId); +// if (attachment != null) { +// attachment.attachTo(null, game); +// } +// game.fireEvent(new GameEvent(GameEvent.EventType.UNATTACHED, objectId, permanentId, controllerId)); +// return true; +// } +// } +// return false; +// } @Override public UUID getAttachedTo() { return attachedTo; @@ -705,15 +705,27 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } @Override - public void attachTo(UUID permanentId, Game game) { - if (this.attachedTo != null && !Objects.equals(this.attachedTo, permanentId)) { - Permanent attachment = game.getPermanent(this.attachedTo); - if (attachment != null) { - attachment.removeAttachment(this.objectId, game); + public void unattach(Game game) { + this.attachedTo = null; + this.addInfo("attachedToCard", null, game); + } + + @Override + public void attachTo(UUID attachToObjectId, Game game) { + if (this.attachedTo != null && !Objects.equals(this.attachedTo, attachToObjectId)) { + Permanent attachedToUntilNowObject = game.getPermanent(this.attachedTo); + if (attachedToUntilNowObject != null) { + attachedToUntilNowObject.removeAttachment(this.objectId, game); + } else { + Card attachedToUntilNowCard = game.getCard(this.attachedTo); + if (attachedToUntilNowCard != null) { + attachedToUntilNowCard.removeAttachment(this.objectId, game); + } } + } - this.attachedTo = permanentId; - this.attachedToZoneChangeCounter = game.getState().getZoneChangeCounter(permanentId); + this.attachedTo = attachToObjectId; + this.attachedToZoneChangeCounter = game.getState().getZoneChangeCounter(attachToObjectId); for (Ability ability : this.getAbilities()) { for (Iterator ite = ability.getEffects(game, EffectType.CONTINUOUS).iterator(); ite.hasNext();) { ContinuousEffect effect = (ContinuousEffect) ite.next(); @@ -726,6 +738,13 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } } } + if (!getSpellAbility().getTargets().isEmpty() && (getSpellAbility().getTargets().get(0) instanceof TargetCard)) { + Card attachedToCard = game.getCard(this.getAttachedTo()); + if (attachedToCard != null) { + // Because cards are not on the battlefield, the relation has to be shown in the card tooltip (e.g. the enchanted card in graveyard) + this.addInfo("attachedToCard", CardUtil.addToolTipMarkTags("Enchanted card: " + attachedToCard.getIdName()), game); + } + } } @Override diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 0b76841eab2..c342549d36a 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -955,4 +955,19 @@ public class Spell extends StackObjImpl implements Card { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } + @Override + public List getAttachments() { + throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean addAttachment(UUID permanentId, Game game) { + throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public boolean removeAttachment(UUID permanentId, Game game) { + throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates. + } + } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 1301f196972..bf43c6ad758 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -817,6 +817,11 @@ public abstract class PlayerImpl implements Player, Serializable { Player attachedToPlayer = game.getPlayer(permanent.getAttachedTo()); if (attachedToPlayer != null) { attachedToPlayer.removeAttachment(permanent, game); + } else { + Card attachedToCard = game.getCard(permanent.getAttachedTo()); + if (attachedToCard != null) { + attachedToCard.removeAttachment(permanent.getId(), game); + } } } @@ -2326,7 +2331,7 @@ public abstract class PlayerImpl implements Player, Serializable { newTarget.setCardLimit(Math.min(librarySearchLimit, cardsFromTop.size())); count = Math.min(searchedLibrary.count(target.getFilter(), game), librarySearchLimit); } - + if (count < target.getNumberOfTargets()) { newTarget.setMinNumberOfTargets(count); } @@ -3278,9 +3283,7 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public boolean moveCards(Card card, Zone toZone, - Ability source, Game game - ) { + public boolean moveCards(Card card, Zone toZone, Ability source, Game game) { return moveCards(card, toZone, source, game, false, false, false, null); } diff --git a/Utils/gen-existing-cards-by-set.pl b/Utils/gen-existing-cards-by-set.pl index 9c6e0a4f7ff..93462c3ad92 100755 --- a/Utils/gen-existing-cards-by-set.pl +++ b/Utils/gen-existing-cards-by-set.pl @@ -1,288 +1,298 @@ -#!/usr/bin/perl -w - -#author: North - -use Text::Template; -use strict; - - -my $authorFile = 'author.txt'; -my $dataFile = "mtg-cards-data.txt"; -my $setsFile = "mtg-sets-data.txt"; -my $knownSetsFile = "known-sets.txt"; - - -my %cards; -my %sets; -my %knownSets; - -my @setCards; - -# gets the set name -my $setName = $ARGV[0]; -if(!$setName) { - print 'Enter a set name: '; - $setName = ; - chomp $setName; -} - -my $template = Text::Template->new(TYPE => 'FILE', SOURCE => 'cardExtendedClass.tmpl', DELIMITERS => [ '[=', '=]' ]); -my $templateBasicLand = Text::Template->new(TYPE => 'FILE', SOURCE => 'cardExtendedLandClass.tmpl', DELIMITERS => [ '[=', '=]' ]); - -sub toCamelCase { - my $string = $_[0]; - $string =~ s/\b([\w']+)\b/ucfirst($1)/ge; - $string =~ s/[-,\s\'\/]//g; - $string; -} - -my $author; -if (-e $authorFile) { - open (DATA, $authorFile); - $author = ; - close(DATA); -} else { - $author = 'anonymous'; -} - -my $cardsFound = 0; -my %all_sets; - -print ("Opening $dataFile\n"); -open (DATA, $dataFile) || die "can't open $dataFile"; -while(my $line = ) { - my @data = split('\\|', $line); - $cards{$data[0]}{$data[1]} = \@data; - - if ($data[1] eq $setName) { - my $cardInfo = "$data[0],,,$data[2]"; - - push(@setCards, $cardInfo); - } else { - $all_sets {$data[1]} = 1; - } -} - -# Fix up split cards -my $potentialSideA; -my @tempSetCards; - -foreach $potentialSideA (sort @setCards) { - #print (">>$potentialSideA\n"); - if ($potentialSideA =~ m/.*,,,(\d+)(a)$/) { - my $cardNumSideB = $1 . "b"; - my $orig_cardNumSideB = $1 . "b"; - my $val; - foreach $val (sort @setCards) { - if ($val =~ m/$cardNumSideB$/) { - $potentialSideA =~ s/,,,.*//; - $val =~ s/,,,.*//; - - # Add 'SideaSideb' to %cards - my $ds = $cards{$val}{$setName}; - print ("$potentialSideA$val,,,$cardNumSideB\n"); - my @newCard; - push (@newCard, "$potentialSideA$val"); - push (@newCard, "$setName"); - push (@newCard, "SPLIT"); - push (@newCard, @$ds[3]); - push (@newCard, "$potentialSideA // $val"); - $cards{$newCard[0]}{$newCard[1]} = \@newCard; - - $cardNumSideB =~ s/.$//; - push (@tempSetCards, "$potentialSideA$val,,,$cardNumSideB"); - - print ("Adding in: $potentialSideA \/\/ $val,,,$cardNumSideB\n"); - $cardsFound = $cardsFound - 1; - $cardNumSideB = $orig_cardNumSideB; - } - } - } - elsif ($potentialSideA =~ m/.*,,,(\d+)(b)$/) { - next; - } - else { - $cardsFound = $cardsFound + 1; - push (@tempSetCards, $potentialSideA); - } -} -@setCards = @tempSetCards; - -close(DATA); -print "Number of cards found for set " . $setName . ": " . $cardsFound . "\n"; - - -if ($cardsFound == 0) { - $setName =~ s/^(...).*/$1/; - my $poss; - my $foundPossibleSet = 0; - my $numPossibleSets = 0; - foreach $poss (sort keys (%all_sets)) { - $numPossibleSets++; - if ($poss =~ m/^$setName/i) { - print ("Did you possibly mean: $poss ?\n"); - $foundPossibleSet = 1; - } - } - if (!$foundPossibleSet) { - print ("Couldn't find any matching set for '$setName'. \n"); - } - - print "(Note: Looked at $numPossibleSets sets in total).\nPress the enter key to exit."; - $setName = ; - exit; -} - -open (DATA, $setsFile) || die "can't open $setsFile"; - -while(my $line = ) { - my @data = split('\\|', $line); - $sets{$data[0]}= $data[1]; -} -close(DATA); - -open (DATA, $knownSetsFile) || die "can't open $knownSetsFile"; -while(my $line = ) { - my @data = split('\\|', $line); - $knownSets{$data[0]}= $data[1]; -} -close(DATA); - -my %raritiesConversion; -$raritiesConversion{'C'} = 'COMMON'; -$raritiesConversion{'U'} = 'UNCOMMON'; -$raritiesConversion{'R'} = 'RARE'; -$raritiesConversion{'M'} = 'MYTHIC'; -$raritiesConversion{'Special'} = 'SPECIAL'; -$raritiesConversion{'Bonus'} = 'BONUS'; -sub getRarity -{ - my $val = $_ [0]; - if (exists ($raritiesConversion {$val})) - { - return $raritiesConversion {$val}; - } - print ("ERROR DETECTED! - Incorrect rarity.. --- $val,,,$_[1]\n"); - sleep (10); - print "Press the enter key to exit."; - $setName = ; - exit; -} - -# Generate the cards - -my %vars; -$vars{'author'} = $author; -$vars{'set'} = $knownSets{$setName}; -$vars{'expansionSetCode'} = $sets{$setName}; - -my $landForest = 0; -my $landMountain = 0; -my $landSwamp = 0; -my $landPlains = 0; -my $landIsland = 0; - -print ("Reading in existing cards in set\n"); -open (SET_FILE, "../../mage/Mage.Sets/src/mage/sets/$knownSets{$setName}.java") || die "can't open $dataFile"; -my %alreadyIn; -while () { - my $line = $_; - if ($line =~ m/SetCardInfo.*\("(.*)", (\d+).*/) - { - $alreadyIn {$2} = $1; - } -} -close SET_FILE; - -my $name_collectorid; -my %implemented; -my %implementedButNotInSetYet; -my %unimplemented; - - -my %githubTask; - -foreach $name_collectorid (sort @setCards) -{ - my $cardName; - my $cardNr; - $name_collectorid =~ m/^(.*),,,(.*)$/; - $cardName = $1; - $cardNr = $2; - { - if($cardName eq "Forest" || $cardName eq "Island" || $cardName eq "Plains" || $cardName eq "Swamp" || $cardName eq "Mountain") { - my $found = 0; - if ($cardName eq "Forest") { - $landForest++; - } - if ($cardName eq "Mountain") { - $landMountain++; - } - if ($cardName eq "Swamp") { - $landSwamp++; - } - if ($cardName eq "Plains") { - $landPlains++; - } - if ($cardName eq "Island") { - $landIsland++; - } - if (!exists ($alreadyIn{$cardNr})) { - print (" cards.add(new SetCardInfo(\"$cardName\", $cardNr, Rarity.LAND, mage.cards.basiclands.$cardName.class, USE_RANDOM_ART));\n"); - } - } - else { - my $ds; - $ds = $cards{$cardName}{$setName}; - my $className = toCamelCase($cardName); - my $setId = lc($cardName); - $setId =~ s/^(.).*/$1/; - my $googleSetName = $setName; - $googleSetName =~ s/ /+/img; - my $fn = "..\\Mage.Sets\\src\\mage\\cards\\$setId\\$className.java"; - my $str = " cards.add(new SetCardInfo(\"$cardName\", $cardNr, Rarity." . getRarity ($cards{$cardName}{$setName}[3], $cardName) . ", mage.cards.$setId.$className.class));\n"; - - if (@$ds[2] eq "SPLIT") { - my $oldCardName = $cardName; - $cardName = @$ds[4]; - $str = " cards.add(new SetCardInfo(\"$cardName\", $cardNr, Rarity." . getRarity ($cards{$oldCardName}{$setName}[3], $oldCardName) . ", mage.cards.$setId.$className.class));\n"; - } - - my $plus_cardName = $cardName; - $plus_cardName =~ s/ /+/img; - $plus_cardName =~ s/,/+/img; - $plus_cardName = "intext:\"$plus_cardName\""; - - if (!exists ($alreadyIn{$cardNr})) { -# Go Looking for the existing implementation.. - if (-e $fn) { - $implementedButNotInSetYet {$str} = 1; - $githubTask {"- [ ] Implemented but have to add to set -- [$cardName](https://www.google.com.au/search?q=$plus_cardName+$googleSetName+mtg&source=lnms&tbm=isch)\n"} = 1; - } else { - $unimplemented {"$str"} = 1; - $githubTask {"- [ ] Not done -- [$cardName](https://www.google.com.au/search?q=$plus_cardName+$googleSetName+mtg&source=lnms&tbm=isch)\n"} = 1; - } - } else { - if (-e $fn) { - $implemented {$str} = 1; - $githubTask {"- [x] Done -- [$cardName](https://www.google.com.au/search?q=$plus_cardName+$googleSetName+mtg&source=lnms&tbm=isch)\n"} = 1; - } else { - $unimplemented {$str} = 1; - $githubTask {"- [ ] Not done -- [$cardName](https://www.google.com.au/search?q=$plus_cardName+$googleSetName+mtg&source=lnms&tbm=isch)\n"} = 1; - } - } - } - } -} - -print "Implemented cards:\n"; -print (join ("", sort keys (%implemented))); -print "\n\n\nImplemented but-not-yet-added-to-set cards:\n"; -print (join ("", sort keys (%implementedButNotInSetYet))); -print "\n\n\nUnimplemented cards:\n"; -print (join ("", sort keys (%unimplemented))); -print "\n\n\nGithub Task:\n"; -print (join ("", sort keys (%githubTask))); -print ("\nData from reading: ../../mage/Mage.Sets/src/mage/sets/$knownSets{$setName}.java\n"); -print "\n\nYou are done. Press the enter key to exit."; -$setName = ; +#!/usr/bin/perl -w + +#author: North + +use Text::Template; +use strict; + + +my $authorFile = 'author.txt'; +my $dataFile = "mtg-cards-data.txt"; +my $setsFile = "mtg-sets-data.txt"; +my $knownSetsFile = "known-sets.txt"; + + +my %cards; +my %sets; +my %knownSets; + +my @setCards; + +# gets the set name +my $setName = $ARGV[0]; +if(!$setName) { + print 'Enter a set name: '; + $setName = ; + chomp $setName; +} + +my $template = Text::Template->new(TYPE => 'FILE', SOURCE => 'cardExtendedClass.tmpl', DELIMITERS => [ '[=', '=]' ]); +my $templateBasicLand = Text::Template->new(TYPE => 'FILE', SOURCE => 'cardExtendedLandClass.tmpl', DELIMITERS => [ '[=', '=]' ]); + +sub toCamelCase { + my $string = $_[0]; + $string =~ s/\b([\w']+)\b/ucfirst($1)/ge; + $string =~ s/[-,\s\'\/]//g; + $string; +} + +my $author; +if (-e $authorFile) { + open (DATA, $authorFile); + $author = ; + close(DATA); +} else { + $author = 'anonymous'; +} + +my $cardsFound = 0; +my %all_sets; + +print ("Opening $dataFile\n"); +open (DATA, $dataFile) || die "can't open $dataFile"; +while(my $line = ) { + my @data = split('\\|', $line); + $cards{$data[0]}{$data[1]} = \@data; + + if ($data[1] eq $setName) { + my $cardInfo = "$data[0],,,$data[2]"; + + push(@setCards, $cardInfo); + } else { + $all_sets {$data[1]} = 1; + } +} + +# Fix up split cards +my $potentialSideA; +my @tempSetCards; + +foreach $potentialSideA (sort @setCards) { + #print (">>$potentialSideA\n"); + if ($potentialSideA =~ m/.*,,,(\d+)(a)$/) { + my $cardNumSideB = $1 . "b"; + my $orig_cardNumSideB = $1 . "b"; + my $val; + foreach $val (sort @setCards) { + if ($val =~ m/$cardNumSideB$/) { + $potentialSideA =~ s/,,,.*//; + $val =~ s/,,,.*//; + + # Add 'SideaSideb' to %cards + my $ds = $cards{$val}{$setName}; + print ("$potentialSideA$val,,,$cardNumSideB\n"); + my @newCard; + push (@newCard, "$potentialSideA$val"); + push (@newCard, "$setName"); + push (@newCard, "SPLIT"); + push (@newCard, @$ds[3]); + push (@newCard, "$potentialSideA // $val"); + $cards{$newCard[0]}{$newCard[1]} = \@newCard; + + $cardNumSideB =~ s/.$//; + push (@tempSetCards, "$potentialSideA$val,,,$cardNumSideB"); + + print ("Adding in: $potentialSideA \/\/ $val,,,$cardNumSideB\n"); + $cardsFound = $cardsFound - 1; + $cardNumSideB = $orig_cardNumSideB; + } + } + } + elsif ($potentialSideA =~ m/.*,,,(\d+)(b)$/) { + next; + } + else { + $cardsFound = $cardsFound + 1; + push (@tempSetCards, $potentialSideA); + } +} +@setCards = @tempSetCards; + +close(DATA); +print "Number of cards found for set " . $setName . ": " . $cardsFound . "\n"; + + +if ($cardsFound == 0) { + $setName =~ s/^(...).*/$1/; + my $poss; + my $foundPossibleSet = 0; + my $numPossibleSets = 0; + foreach $poss (sort keys (%all_sets)) { + $numPossibleSets++; + if ($poss =~ m/^$setName/i) { + print ("Did you possibly mean: $poss ?\n"); + $foundPossibleSet = 1; + } + } + if (!$foundPossibleSet) { + print ("Couldn't find any matching set for '$setName'. \n"); + } + + print "(Note: Looked at $numPossibleSets sets in total).\nPress the enter key to exit."; + $setName = ; + exit; +} + +open (DATA, $setsFile) || die "can't open $setsFile"; + +while(my $line = ) { + my @data = split('\\|', $line); + $sets{$data[0]}= $data[1]; +} +close(DATA); + +open (DATA, $knownSetsFile) || die "can't open $knownSetsFile"; +while(my $line = ) { + my @data = split('\\|', $line); + $knownSets{$data[0]}= $data[1]; +} +close(DATA); + +my %raritiesConversion; +$raritiesConversion{'C'} = 'COMMON'; +$raritiesConversion{'U'} = 'UNCOMMON'; +$raritiesConversion{'R'} = 'RARE'; +$raritiesConversion{'M'} = 'MYTHIC'; +$raritiesConversion{'Special'} = 'SPECIAL'; +$raritiesConversion{'Bonus'} = 'BONUS'; +sub getRarity +{ + my $val = $_ [0]; + if (exists ($raritiesConversion {$val})) + { + return $raritiesConversion {$val}; + } + print ("ERROR DETECTED! - Incorrect rarity.. --- $val,,,$_[1]\n"); + sleep (10); + print "Press the enter key to exit."; + $setName = ; + exit; +} + +# Generate the cards + +my %vars; +$vars{'author'} = $author; +$vars{'set'} = $knownSets{$setName}; +$vars{'expansionSetCode'} = $sets{$setName}; + +my $landForest = 0; +my $landMountain = 0; +my $landSwamp = 0; +my $landPlains = 0; +my $landIsland = 0; + +print ("Reading in existing cards in set\n"); +open (SET_FILE, "../../mage/Mage.Sets/src/mage/sets/$knownSets{$setName}.java") || die "can't open $dataFile"; +my %alreadyIn; +while () { + my $line = $_; + if ($line =~ m/SetCardInfo.*\("(.*)", (\d+).*/) + { + $alreadyIn {$2} = $1; + } +} +close SET_FILE; + +my $name_collectorid; +my %implemented; +my %implementedButNotInSetYet; +my %unimplemented; + + +my %githubTask; + +foreach $name_collectorid (sort @setCards) +{ + my $cardName; + my $cardNr; + $name_collectorid =~ m/^(.*),,,(.*)$/; + $cardName = $1; + $cardNr = $2; + { + if($cardName eq "Forest" || $cardName eq "Island" || $cardName eq "Plains" || $cardName eq "Swamp" || $cardName eq "Mountain") { + my $found = 0; + if ($cardName eq "Forest") { + $landForest++; + } + if ($cardName eq "Mountain") { + $landMountain++; + } + if ($cardName eq "Swamp") { + $landSwamp++; + } + if ($cardName eq "Plains") { + $landPlains++; + } + if ($cardName eq "Island") { + $landIsland++; + } + if (!exists ($alreadyIn{$cardNr})) { + print (" cards.add(new SetCardInfo(\"$cardName\", $cardNr, Rarity.LAND, mage.cards.basiclands.$cardName.class, USE_RANDOM_ART));\n"); + } + } + else { + my $ds; + $ds = $cards{$cardName}{$setName}; + my $className = toCamelCase($cardName); + my $setId = lc($cardName); + $setId =~ s/^(.).*/$1/; + my $googleSetName = $setName; + $googleSetName =~ s/ /+/img; + my $fn = "..\\Mage.Sets\\src\\mage\\cards\\$setId\\$className.java"; + my $str = " cards.add(new SetCardInfo(\"$cardName\", $cardNr, Rarity." . getRarity ($cards{$cardName}{$setName}[3], $cardName) . ", mage.cards.$setId.$className.class));\n"; + + if (@$ds[2] eq "SPLIT") { + my $oldCardName = $cardName; + $cardName = @$ds[4]; + $str = " cards.add(new SetCardInfo(\"$cardName\", $cardNr, Rarity." . getRarity ($cards{$oldCardName}{$setName}[3], $oldCardName) . ", mage.cards.$setId.$className.class));\n"; + } + + my $plus_cardName = $cardName; + $plus_cardName =~ s/ /+/img; + $plus_cardName =~ s/,/+/img; + $plus_cardName = "intext:\"$plus_cardName\""; + + if (!exists ($alreadyIn{$cardNr})) { +# Go Looking for the existing implementation.. + if (-e $fn) { + $implementedButNotInSetYet {$str} = 1; + $githubTask {"- [ ] Implemented but have to add to set -- [$cardName](https://www.google.com.au/search?q=$plus_cardName+$googleSetName+mtg&source=lnms&tbm=isch)\n"} = 1; + } else { + $unimplemented {"$str"} = 1; + $githubTask {"- [ ] Not done -- [$cardName](https://www.google.com.au/search?q=$plus_cardName+$googleSetName+mtg&source=lnms&tbm=isch)\n"} = 1; + } + } else { + if (-e $fn) { + $implemented {$str} = 1; + $githubTask {"- [x] Done -- [$cardName](https://www.google.com.au/search?q=$plus_cardName+$googleSetName+mtg&source=lnms&tbm=isch)\n"} = 1; + } else { + $unimplemented {$str} = 1; + $githubTask {"- [ ] Not done -- [$cardName](https://www.google.com.au/search?q=$plus_cardName+$googleSetName+mtg&source=lnms&tbm=isch)\n"} = 1; + } + } + } + } +} + +# Add logic to add the missing card lines to set file automatically +#my $setFileName = "../Mage.Sets/src/mage/sets/".$knownSets{$setName}.".java"; +#print (join("","Add already implemented cards to set file: ", $setFileName,"\n")); +#foreach my $line (sort keys (%implementedButNotInSetYet)) { +# - Do action to add the line +# print $line; +#} + + +print "Implemented cards:\n"; +print (join ("", sort keys (%implemented))); +print "\n\n\nImplemented but-not-yet-added-to-set cards:\n"; +print (join ("", sort keys (%implementedButNotInSetYet))); +print "\n\n\nUnimplemented cards:\n"; +print (join ("", sort keys (%unimplemented))); +print "\n\n\nGithub Task:\n"; +print (join ("", sort keys (%githubTask))); +print ("\nData from reading: ../../mage/Mage.Sets/src/mage/sets/$knownSets{$setName}.java\n"); + +print "\n\nYou are done. Press the enter key to exit."; +$setName = ;