Added Spellweaver Volute.

This commit is contained in:
LevelX2 2018-01-02 23:48:07 +01:00
parent bec07fb530
commit bc490ef91a
14 changed files with 835 additions and 353 deletions

View file

@ -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;
}
}

View file

@ -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("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("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("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("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("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)); cards.add(new SetCardInfo("Spirit en-Dal", 17, Rarity.UNCOMMON, mage.cards.s.SpiritEnDal.class));

View file

@ -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);
}
}

View file

@ -29,10 +29,12 @@ package mage.abilities.effects.common;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetCard;
/** /**
* *
@ -72,9 +74,16 @@ public class AttachEffect extends OneShotEffect {
if (player != null) { if (player != null) {
return player.addAttachment(source.getSourceId(), game); 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; return false;
} }

View file

@ -27,14 +27,11 @@
*/ */
package mage.abilities.effects.common; package mage.abilities.effects.common;
import java.util.LinkedHashSet;
import java.util.stream.Collectors;
import mage.MageObject; import mage.MageObject;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.choices.Choice; import mage.choices.Choice;
import mage.choices.ChoiceCreatureType; import mage.choices.ChoiceCreatureType;
import mage.choices.ChoiceImpl;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.SubType; import mage.constants.SubType;
import mage.game.Game; import mage.game.Game;

View file

@ -27,6 +27,8 @@
*/ */
package mage.cards; package mage.cards;
import java.util.List;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.Mana; import mage.Mana;
import mage.ObjectColor; import mage.ObjectColor;
@ -42,19 +44,14 @@ import mage.game.Game;
import mage.game.GameState; import mage.game.GameState;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import java.util.List;
import java.util.UUID;
public interface Card extends MageObject { public interface Card extends MageObject {
final String regexBlack = ".*\\x7b.{0,2}B.{0,2}\\x7d.*"; final String regexBlack = ".*\\x7b.{0,2}B.{0,2}\\x7d.*";
final String regexBlue = ".*\\x7b.{0,2}U.{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 regexRed = ".*\\x7b.{0,2}R.{0,2}\\x7d.*";
final String regexGreen = ".*\\x7b.{0,2}G.{0,2}\\x7d.*"; final String regexGreen = ".*\\x7b.{0,2}G.{0,2}\\x7d.*";
final String regexWhite = ".*\\x7b.{0,2}W.{0,2}\\x7d.*"; final String regexWhite = ".*\\x7b.{0,2}W.{0,2}\\x7d.*";
UUID getOwnerId(); UUID getOwnerId();
String getCardNumber(); String getCardNumber();
@ -248,4 +245,9 @@ public interface Card extends MageObject {
return mana; return mana;
} }
List<UUID> getAttachments();
boolean addAttachment(UUID permanentId, Game game);
boolean removeAttachment(UUID permanentId, Game game);
} }

View file

@ -94,6 +94,8 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
protected boolean morphCard; protected boolean morphCard;
protected boolean allCreatureTypes; protected boolean allCreatureTypes;
protected List<UUID> attachments = new ArrayList<>();
public CardImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs) { public CardImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs) {
this(ownerId, setInfo, cardTypes, costs, SpellAbilityType.BASE); this(ownerId, setInfo, cardTypes, costs, SpellAbilityType.BASE);
} }
@ -169,6 +171,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
flipCardName = card.flipCardName; flipCardName = card.flipCardName;
splitCard = card.splitCard; splitCard = card.splitCard;
usesVariousArt = card.usesVariousArt; usesVariousArt = card.usesVariousArt;
this.attachments.addAll(card.attachments);
} }
@Override @Override
@ -840,11 +843,53 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
return super.getSubtype(game); return super.getSubtype(game);
} }
@Override
public boolean isAllCreatureTypes() { public boolean isAllCreatureTypes() {
return allCreatureTypes; return allCreatureTypes;
} }
@Override
public void setIsAllCreatureTypes(boolean value) { public void setIsAllCreatureTypes(boolean value) {
allCreatureTypes = value; allCreatureTypes = value;
} }
@Override
public List<UUID> 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;
}
} }

View file

@ -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;
}
}
} }
} }
} }

View file

@ -233,7 +233,8 @@ public class GameEvent implements Serializable {
PAID_CUMULATIVE_UPKEEP, PAID_CUMULATIVE_UPKEEP,
DIDNT_PAY_CUMULATIVE_UPKEEP, DIDNT_PAY_CUMULATIVE_UPKEEP,
//permanent events //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_CONTROL, // 616.1b
ENTERS_THE_BATTLEFIELD_COPY, // 616.1c ENTERS_THE_BATTLEFIELD_COPY, // 616.1c
ENTERS_THE_BATTLEFIELD, // 616.1d ENTERS_THE_BATTLEFIELD, // 616.1d

View file

@ -116,10 +116,11 @@ public interface Permanent extends Card, Controllable {
void attachTo(UUID permanentId, Game game); void attachTo(UUID permanentId, Game game);
boolean addAttachment(UUID permanentId, Game game); void unattach(Game game);
boolean removeAttachment(UUID permanentId, Game game);
// boolean addAttachment(UUID permanentId, Game game);
//
// boolean removeAttachment(UUID permanentId, Game game);
boolean canBeTargetedBy(MageObject source, UUID controllerId, Game game); boolean canBeTargetedBy(MageObject source, UUID controllerId, Game game);
boolean hasProtectionFrom(MageObject source, Game game); boolean hasProtectionFrom(MageObject source, Game game);

View file

@ -56,6 +56,7 @@ import mage.game.permanent.token.SquirrelToken;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.game.stack.StackObject; import mage.game.stack.StackObject;
import mage.players.Player; import mage.players.Player;
import mage.target.TargetCard;
import mage.util.CardUtil; import mage.util.CardUtil;
import mage.util.GameLog; import mage.util.GameLog;
import mage.util.ThreadLocalStringBuilder; import mage.util.ThreadLocalStringBuilder;
@ -103,7 +104,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected int maxBlockedBy = 0; protected int maxBlockedBy = 0;
protected boolean removedFromCombat; protected boolean removedFromCombat;
protected boolean deathtouched; protected boolean deathtouched;
protected List<UUID> attachments = new ArrayList<>();
protected Map<String, List<UUID>> connectedCards = new HashMap<>(); protected Map<String, List<UUID>> connectedCards = new HashMap<>();
protected Set<MageObjectReference> dealtDamageByThisTurn; protected Set<MageObjectReference> dealtDamageByThisTurn;
protected UUID attachedTo; protected UUID attachedTo;
@ -147,7 +148,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.blocking = permanent.blocking; this.blocking = permanent.blocking;
this.maxBlocks = permanent.maxBlocks; this.maxBlocks = permanent.maxBlocks;
this.deathtouched = permanent.deathtouched; this.deathtouched = permanent.deathtouched;
this.attachments.addAll(permanent.attachments); // this.attachments.addAll(permanent.attachments);
for (Map.Entry<String, List<UUID>> entry : permanent.connectedCards.entrySet()) { for (Map.Entry<String, List<UUID>> entry : permanent.connectedCards.entrySet()) {
this.connectedCards.put(entry.getKey(), entry.getValue()); this.connectedCards.put(entry.getKey(), entry.getValue());
} }
@ -626,47 +627,46 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
} }
return false; return false;
} }
//
// @Override
// public List<UUID> getAttachments() {
// return attachments;
// }
@Override // @Override
public List<UUID> getAttachments() { // public boolean addAttachment(UUID permanentId, Game game) {
return attachments; // if (!this.attachments.contains(permanentId)) {
} // if (!game.replaceEvent(new GameEvent(GameEvent.EventType.ATTACH, objectId, permanentId, controllerId))) {
// this.attachments.add(permanentId);
@Override // Permanent attachment = game.getPermanent(permanentId);
public boolean addAttachment(UUID permanentId, Game game) { // if (attachment == null) {
if (!this.attachments.contains(permanentId)) { // attachment = game.getPermanentEntering(permanentId);
if (!game.replaceEvent(new GameEvent(GameEvent.EventType.ATTACH, objectId, permanentId, controllerId))) { // }
this.attachments.add(permanentId); // if (attachment != null) {
Permanent attachment = game.getPermanent(permanentId); // attachment.attachTo(objectId, game);
if (attachment == null) { // game.fireEvent(new GameEvent(GameEvent.EventType.ATTACHED, objectId, permanentId, controllerId));
attachment = game.getPermanentEntering(permanentId); // return true;
} // }
if (attachment != null) { // }
attachment.attachTo(objectId, game); // }
game.fireEvent(new GameEvent(GameEvent.EventType.ATTACHED, objectId, permanentId, controllerId)); // return false;
return true; // }
} //
} // @Override
} // public boolean removeAttachment(UUID permanentId, Game game) {
return false; // if (this.attachments.contains(permanentId)) {
} // if (!game.replaceEvent(new GameEvent(GameEvent.EventType.UNATTACH, objectId, permanentId, controllerId))) {
// this.attachments.remove(permanentId);
@Override // Permanent attachment = game.getPermanent(permanentId);
public boolean removeAttachment(UUID permanentId, Game game) { // if (attachment != null) {
if (this.attachments.contains(permanentId)) { // attachment.attachTo(null, game);
if (!game.replaceEvent(new GameEvent(GameEvent.EventType.UNATTACH, objectId, permanentId, controllerId))) { // }
this.attachments.remove(permanentId); // game.fireEvent(new GameEvent(GameEvent.EventType.UNATTACHED, objectId, permanentId, controllerId));
Permanent attachment = game.getPermanent(permanentId); // return true;
if (attachment != null) { // }
attachment.attachTo(null, game); // }
} // return false;
game.fireEvent(new GameEvent(GameEvent.EventType.UNATTACHED, objectId, permanentId, controllerId)); // }
return true;
}
}
return false;
}
@Override @Override
public UUID getAttachedTo() { public UUID getAttachedTo() {
return attachedTo; return attachedTo;
@ -705,15 +705,27 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
} }
@Override @Override
public void attachTo(UUID permanentId, Game game) { public void unattach(Game game) {
if (this.attachedTo != null && !Objects.equals(this.attachedTo, permanentId)) { this.attachedTo = null;
Permanent attachment = game.getPermanent(this.attachedTo); this.addInfo("attachedToCard", null, game);
if (attachment != null) { }
attachment.removeAttachment(this.objectId, 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 (Ability ability : this.getAbilities()) {
for (Iterator<Effect> ite = ability.getEffects(game, EffectType.CONTINUOUS).iterator(); ite.hasNext();) { for (Iterator<Effect> ite = ability.getEffects(game, EffectType.CONTINUOUS).iterator(); ite.hasNext();) {
ContinuousEffect effect = (ContinuousEffect) ite.next(); 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 @Override

View file

@ -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. throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
} }
@Override
public List<UUID> 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.
}
} }

View file

@ -817,6 +817,11 @@ public abstract class PlayerImpl implements Player, Serializable {
Player attachedToPlayer = game.getPlayer(permanent.getAttachedTo()); Player attachedToPlayer = game.getPlayer(permanent.getAttachedTo());
if (attachedToPlayer != null) { if (attachedToPlayer != null) {
attachedToPlayer.removeAttachment(permanent, game); attachedToPlayer.removeAttachment(permanent, game);
} else {
Card attachedToCard = game.getCard(permanent.getAttachedTo());
if (attachedToCard != null) {
attachedToCard.removeAttachment(permanent.getId(), game);
}
} }
} }
@ -3278,9 +3283,7 @@ public abstract class PlayerImpl implements Player, Serializable {
} }
@Override @Override
public boolean moveCards(Card card, Zone toZone, public boolean moveCards(Card card, Zone toZone, Ability source, Game game) {
Ability source, Game game
) {
return moveCards(card, toZone, source, game, false, false, false, null); return moveCards(card, toZone, source, game, false, false, false, null);
} }

View file

@ -275,6 +275,15 @@ foreach $name_collectorid (sort @setCards)
} }
} }
# 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 "Implemented cards:\n";
print (join ("", sort keys (%implemented))); print (join ("", sort keys (%implemented)));
print "\n\n\nImplemented but-not-yet-added-to-set cards:\n"; print "\n\n\nImplemented but-not-yet-added-to-set cards:\n";
@ -284,5 +293,6 @@ print (join ("", sort keys (%unimplemented)));
print "\n\n\nGithub Task:\n"; print "\n\n\nGithub Task:\n";
print (join ("", sort keys (%githubTask))); print (join ("", sort keys (%githubTask)));
print ("\nData from reading: ../../mage/Mage.Sets/src/mage/sets/$knownSets{$setName}.java\n"); 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."; print "\n\nYou are done. Press the enter key to exit.";
$setName = <STDIN>; $setName = <STDIN>;