From df98b4e0af78d52aa2344a7a217a76b5280e7f06 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 25 Feb 2016 17:08:17 +0100 Subject: [PATCH 01/67] * Fixed a bug with time stamp order and change of basic land type (e.g. Blood Moon and Spreading Seas). --- .../plugins/card/images/DownloadPictures.java | 14 ++- .../cards/abilities/enters/BloodMoonTest.java | 95 ++++++++++++++++--- .../BecomesBasicLandEnchantedEffect.java | 20 +++- Mage/src/main/java/mage/players/Library.java | 2 +- 4 files changed, 106 insertions(+), 25 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java index 0c61f379f4..54efd800b3 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/images/DownloadPictures.java @@ -312,14 +312,12 @@ public class DownloadPictures extends DefaultBoundedRangeModel implements Runnab url.setFlippedSide(true); allCardsUrls.add(url); } - } else { - if (card.getCardNumber() < 1) { - System.err.println("There was a critical error!"); - logger.error("Card has no collector ID and won't be sent to client: " + card); - } else if (card.getSetCode().isEmpty()) { - System.err.println("There was a critical error!"); - logger.error("Card has no set name and won't be sent to client:" + card); - } + } else if (card.getCardNumber() < 1) { + System.err.println("There was a critical error!"); + logger.error("Card has no collector ID and won't be sent to client: " + card); + } else if (card.getSetCode().isEmpty()) { + System.err.println("There was a critical error!"); + logger.error("Card has no set name and won't be sent to client:" + card); } } allCardsUrls.addAll(getTokenCardUrls()); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java index 88e80dbee8..276f8ee438 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java @@ -3,7 +3,6 @@ * To change this template file, choose Tools | Templates * and open the template in the editor. */ - package org.mage.test.cards.abilities.enters; import mage.constants.PhaseStep; @@ -28,7 +27,6 @@ public class BloodMoonTest extends CardTestPlayerBase { // or abilities that changed the permanent’s characteristics on the stack (see rule 400.7a), and continuous // effects from the permanent’s own static abilities, but ignoring continuous effects from any other source // that would affect it. - // Grassland has to enter the battlefield tapped, because // the Blood Moon does not prevent ETB Replacement Effects @Test @@ -39,10 +37,9 @@ public class BloodMoonTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Blood Moon"); /** - * Grasslands - * Land - * Grasslands enters the battlefield tapped. - * {T}, Sacrifice Grasslands: Search your library for a Forest or Plains card and put it onto the battlefield. Then shuffle your library. + * Grasslands Land Grasslands enters the battlefield tapped. {T}, + * Sacrifice Grasslands: Search your library for a Forest or Plains card + * and put it onto the battlefield. Then shuffle your library. */ addCard(Zone.HAND, playerA, "Grasslands"); @@ -67,11 +64,9 @@ public class BloodMoonTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Blood Moon"); /** - * Kabira Crossroads - * Land - * Kabira Crossroads enters the battlefield tapped. - * When Kabira Crossroads enters the battlefield, you gain 2 life. - * {W}: Add to your mana pool. + * Kabira Crossroads Land Kabira Crossroads enters the battlefield + * tapped. When Kabira Crossroads enters the battlefield, you gain 2 + * life. {W}: Add to your mana pool. * */ addCard(Zone.HAND, playerA, "Kabira Crossroads"); @@ -87,6 +82,84 @@ public class BloodMoonTest extends CardTestPlayerBase { assertLife(playerA, 20); // Trigger may not trigger because of Blood Moon so the 2 life were not added assertLife(playerB, 20); + } + + /** + * There's a bug with Spreading Seas and Blood Moon + * + * Spreading Seas was played turn 3 in a Steam Vents, Blood Moon turn 7 or + * something + * + * The enchanted Steam Vents was producing only U when ir should produce + * only R because of blood moon's time stamp. + * + * http://blogs.magicjudges.org/articles/2013/06/18/blood-moon-in-a-modern-environment/ + * Spreading Seas, which has started to see play in Modern, also functions + * similar to Prismatic Omen. If the Seas enters the battlefield before + * Blood Moon, Blood Moon wins since it has a later timestamp and the land + * will just be a Mountain. If the Seas enters the battlefield after Blood + * Moon, then the effect of the Seas wins and the land will be an Island. + */ + @Test + public void testBloodMoonAfterSpreadingSea() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); + addCard(Zone.BATTLEFIELD, playerA, "Island", 1); + // Blood Moon 2R + // Enchantment + // Nonbasic lands are Mountains + addCard(Zone.HAND, playerA, "Blood Moon"); + + // Enchant land + // When Spreading Seas enters the battlefield, draw a card. + // Enchanted land is an Island. + addCard(Zone.HAND, playerA, "Spreading Seas"); // {1}{U} + + // {T}: Add {C} to your mana pool. + // {T}: Add {B} or {W} to your mana pool. Caves of Koilos deals 1 damage to you. + addCard(Zone.BATTLEFIELD, playerB, "Steam Vents"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spreading Seas", "Steam Vents"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Blood Moon"); + setStopAt(2, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Spreading Seas", 1); + assertPermanentCount(playerA, "Blood Moon", 1); + assertHandCount(playerA, 1); + // Check that the Steam Vents produces only {R} + Assert.assertTrue("The mana the land can produce should be [{R}] but it's " + playerB.getManaAvailable(currentGame).toString(), playerB.getManaAvailable(currentGame).toString().equals("[{R}]")); } + + @Test + public void testBloodMoonBeforeSpreadingSea() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mox Sapphire", 4); + // Blood Moon 2R + // Enchantment + // Nonbasic lands are Mountains + addCard(Zone.HAND, playerA, "Blood Moon"); + + // Enchant land + // When Spreading Seas enters the battlefield, draw a card. + // Enchanted land is an Island. + addCard(Zone.HAND, playerA, "Spreading Seas"); // {1}{U} + + // {T}: Add {C} to your mana pool. + // {T}: Add {B} or {W} to your mana pool. Caves of Koilos deals 1 damage to you. + addCard(Zone.BATTLEFIELD, playerB, "Steam Vents"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blood Moon"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Spreading Seas", "Steam Vents"); + setStopAt(2, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Spreading Seas", 1); + assertPermanentCount(playerA, "Blood Moon", 1); + assertHandCount(playerA, 1); + // Check that the Steam Vents produces only {R} + Assert.assertTrue("The mana the land can produce should be [{U}] but it's " + playerB.getManaAvailable(currentGame).toString(), playerB.getManaAvailable(currentGame).toString().equals("[{U}]")); + + } + } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBasicLandEnchantedEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBasicLandEnchantedEffect.java index 06f4388a58..99d070f089 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBasicLandEnchantedEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesBasicLandEnchantedEffect.java @@ -98,19 +98,29 @@ public class BecomesBasicLandEnchantedEffect extends ContinuousEffectImpl { for (String landType : landTypes) { switch (landType) { case "Swamp": - permanent.addAbility(new BlackManaAbility(), source.getSourceId(), game); + if (permanent.getSubtype().contains("Swamp")) { // type can be removed by other effect with newer timestamp, so no ability adding + permanent.addAbility(new BlackManaAbility(), source.getSourceId(), game); + } break; case "Mountain": - permanent.addAbility(new RedManaAbility(), source.getSourceId(), game); + if (permanent.getSubtype().contains("Mountain")) { + permanent.addAbility(new RedManaAbility(), source.getSourceId(), game); + } break; case "Forest": - permanent.addAbility(new GreenManaAbility(), source.getSourceId(), game); + if (permanent.getSubtype().contains("Forest")) { + permanent.addAbility(new GreenManaAbility(), source.getSourceId(), game); + } break; case "Island": - permanent.addAbility(new BlueManaAbility(), source.getSourceId(), game); + if (permanent.getSubtype().contains("Island")) { + permanent.addAbility(new BlueManaAbility(), source.getSourceId(), game); + } break; case "Plains": - permanent.addAbility(new WhiteManaAbility(), source.getSourceId(), game); + if (permanent.getSubtype().contains("Plains")) { + permanent.addAbility(new WhiteManaAbility(), source.getSourceId(), game); + } break; } } diff --git a/Mage/src/main/java/mage/players/Library.java b/Mage/src/main/java/mage/players/Library.java index 1b72dd544a..94db01427e 100644 --- a/Mage/src/main/java/mage/players/Library.java +++ b/Mage/src/main/java/mage/players/Library.java @@ -180,7 +180,7 @@ public class Library implements Serializable { } public List getCardList() { - return new ArrayList(library); + return new ArrayList<>(library); } public List getCards(Game game) { From d9013346b33ab013835111ad61dc2079095d50bf Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 25 Feb 2016 17:16:37 +0100 Subject: [PATCH 02/67] * Reviving Vapors - Fixed that the chosen card did not go to hand. --- .../src/mage/sets/invasion/RevivingVapors.java | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/Mage.Sets/src/mage/sets/invasion/RevivingVapors.java b/Mage.Sets/src/mage/sets/invasion/RevivingVapors.java index 5d99048700..fc65747006 100644 --- a/Mage.Sets/src/mage/sets/invasion/RevivingVapors.java +++ b/Mage.Sets/src/mage/sets/invasion/RevivingVapors.java @@ -83,21 +83,12 @@ class RevivingVaporsEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source.getSourceId()); - if (controller == null || sourceObject == null) { + if (controller == null || sourceObject == null) { return false; } Cards cards = new CardsImpl(); - int count = Math.min(controller.getLibrary().size(), 3); - for (int i = 0; i < count; i++) { - Card card = controller.getLibrary().removeFromTop(game); - if (card != null) { - cards.add(card); - } else { - return false; - } - } - + cards.addAll(controller.getLibrary().getTopCards(game, 3)); if (!cards.isEmpty()) { controller.revealCards(sourceObject.getName(), cards, game); Card card = null; @@ -112,9 +103,9 @@ class RevivingVaporsEffect extends OneShotEffect { } if (card != null) { cards.remove(card); - controller.moveCards(card, Zone.LIBRARY, Zone.GRAVEYARD, source, game); + controller.moveCards(card, Zone.HAND, source, game); } - controller.moveCards(cards, Zone.LIBRARY, Zone.GRAVEYARD, source, game); + controller.moveCards(cards, Zone.GRAVEYARD, source, game); } return true; } From ad62546807b6b11de759b5a0b632c2efc0940bc6 Mon Sep 17 00:00:00 2001 From: Goesta Date: Thu, 25 Feb 2016 19:30:20 +0100 Subject: [PATCH 03/67] Improved download speed of foreign language cards --- .../dl/sources/WizardCardsImageSource.java | 58 +++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java index e964ed8d41..f4b27a984c 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java @@ -38,6 +38,9 @@ import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.prefs.Preferences; import mage.client.MageFrame; import mage.client.dialog.PreferencesDialog; @@ -246,11 +249,11 @@ public class WizardCardsImageSource implements CardImageSource { languageAliases.put("fr", "French"); languageAliases.put("cn", "Chinese Simplified"); languageAliases.put("de", "German"); - } private Map getSetLinks(String cardSet) { - Map setLinks = new HashMap<>(); + ConcurrentHashMap setLinks = new ConcurrentHashMap<>(); + ExecutorService executor = Executors.newFixedThreadPool(10); try { String setNames = setsAliases.get(cardSet); String preferedLanguage = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PREF_LANGUAGE, "en"); @@ -262,12 +265,10 @@ public class WizardCardsImageSource implements CardImageSource { while (page < 999) { String searchUrl = "http://gatherer.wizards.com/Pages/Search/Default.aspx?page=" + page +"&output=spoiler&method=visual&action=advanced&set=+[%22" + URLSetName + "%22]"; Document doc = getDocument(searchUrl); - Elements cardsImages = doc.select("img[src^=../../Handlers/]"); if (cardsImages.isEmpty()) { break; } - for (int i = 0; i < cardsImages.size(); i++) { Integer multiverseId = Integer.parseInt(cardsImages.get(i).attr("src").replaceAll("[^\\d]", "")); if (i == 0) { @@ -278,12 +279,8 @@ public class WizardCardsImageSource implements CardImageSource { } String cardName = normalizeName(cardsImages.get(i).attr("alt")); if (cardName != null && !cardName.isEmpty()) { - if (cardName.equals("Forest") || cardName.equals("Swamp") || cardName.equals("Mountain") || cardName.equals("Island") || cardName.equals("Plains")) { - setLinks.putAll(getLandVariations(multiverseId, cardName)); - } else { - Integer preferedMultiverseId = getLocalizedMultiverseId(preferedLanguage, multiverseId); - setLinks.put(cardName.toLowerCase(), generateLink(preferedMultiverseId)); - } + Runnable task = new GetImageLinkTask(multiverseId, cardName, preferedLanguage, setLinks); + executor.execute(task); } } page++; @@ -292,6 +289,16 @@ public class WizardCardsImageSource implements CardImageSource { } catch (IOException ex) { System.out.println("Exception when parsing the wizards page: " + ex.getMessage()); } + + executor.shutdown(); + + while (!executor.isTerminated()) { + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + } + } + return setLinks; } @@ -440,4 +447,35 @@ public class WizardCardsImageSource implements CardImageSource { public Float getAverageSize() { return 60.0f; } + + private final class GetImageLinkTask implements Runnable { + + private final Integer multiverseId; + private final String cardName; + private final String preferedLanguage; + private final ConcurrentHashMap setLinks; + + public GetImageLinkTask(Integer multiverseId, String cardName, String preferedLanguage, ConcurrentHashMap setLinks) { + this.multiverseId = multiverseId; + this.cardName = cardName; + this.preferedLanguage = preferedLanguage; + this.setLinks = setLinks; + } + + @Override + public void run() { + try { + if (cardName.equals("Forest") || cardName.equals("Swamp") || cardName.equals("Mountain") || cardName.equals("Island") || cardName.equals("Plains")) { + setLinks.putAll(getLandVariations(multiverseId, cardName)); + } else { + Integer preferedMultiverseId = getLocalizedMultiverseId(preferedLanguage, multiverseId); + setLinks.put(cardName.toLowerCase(), generateLink(preferedMultiverseId)); + } + } catch (IOException | NumberFormatException ex) { + System.out.println("Exception when parsing the wizards page: " + ex.getMessage()); + } + } + + } + } From 9f35637e6db4f67382c2d63a7cc0888f2563e78d Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 25 Feb 2016 21:41:38 +0100 Subject: [PATCH 04/67] Fixed that a planeswalker that left the battlefield was not correctly removed from combat. --- .../test/cards/control/ItThatBetraysTest.java | 37 +++++++++++++++++++ .../main/java/mage/game/combat/Combat.java | 11 ++++++ .../java/mage/game/combat/CombatGroup.java | 22 +++++++---- .../mage/game/permanent/PermanentImpl.java | 10 +++-- 4 files changed, 68 insertions(+), 12 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/ItThatBetraysTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/ItThatBetraysTest.java index 4a6a6077a0..df4398a683 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/control/ItThatBetraysTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/ItThatBetraysTest.java @@ -127,4 +127,41 @@ public class ItThatBetraysTest extends CardTestPlayerBase { assertGraveyardCount(playerA, "Spreading Seas", 0); assertPermanentCount(playerB, "Spreading Seas", 1); } + + /** + * It That Betrays had a strange bug. Attacked opponent's planeswalker with + * him (I think it was Venser, the Sojourner), then opponent sacrificed said + * planeswalker to ITB (It That Betrays) annihilator ability, ITB ability + * triggered and Venser came over to my control, but ITB was still attacking + * my own planeswalker and killed it. Shouldn't happen because that's an + * entirely new planeswalker, not the one I was attacking. That one died, + * therefore the attack was invalid. + */ + @Test + public void testExileAttackedPlaneswalker() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + + // +2: Exile target permanent you own. Return it to the battlefield under your control at the beginning of the next end step. + // -1: Creatures can't be blocked this turn. + // -8: You get an emblem with "Whenever you cast a spell, exile target permanent." + addCard(Zone.BATTLEFIELD, playerA, "Venser, the Sojourner", 1); + + // Annihilator 2 (Whenever this creature attacks, defending player sacrifices two permanents.) + // Whenever an opponent sacrifices a nontoken permanent, put that card onto the battlefield under your control. + addCard(Zone.BATTLEFIELD, playerB, "It That Betrays"); // 11/11 + + attack(2, playerB, "It That Betrays", "Venser, the Sojourner"); + setChoice(playerA, "Venser, the Sojourner"); + setChoice(playerA, "Mountain"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + // Player B now controls a Silvercoat Lion and Spreading Seas + assertPermanentCount(playerB, "Venser, the Sojourner", 1); + assertPermanentCount(playerB, "Mountain", 1); + } } diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java index 36c055fd18..a78dc1738c 100644 --- a/Mage/src/main/java/mage/game/combat/Combat.java +++ b/Mage/src/main/java/mage/game/combat/Combat.java @@ -1110,6 +1110,17 @@ public class Combat implements Serializable, Copyable { } } + public boolean removePlaneswalkerFromCombat(UUID planeswalkerId, Game game, boolean withInfo) { + boolean result = false; + for (CombatGroup group : groups) { + if (group.getDefenderId().equals(planeswalkerId)) { + group.removeAttackedPlaneswalker(planeswalkerId); + result = true; + } + } + return result; + } + public boolean removeFromCombat(UUID creatureId, Game game, boolean withInfo) { boolean result = false; Permanent creature = game.getPermanent(creatureId); diff --git a/Mage/src/main/java/mage/game/combat/CombatGroup.java b/Mage/src/main/java/mage/game/combat/CombatGroup.java index 51c1c19625..d1569afb6f 100644 --- a/Mage/src/main/java/mage/game/combat/CombatGroup.java +++ b/Mage/src/main/java/mage/game/combat/CombatGroup.java @@ -503,19 +503,25 @@ public class CombatGroup implements Serializable, Copyable { return defenderIsPlaneswalker; } + public boolean removeAttackedPlaneswalker(UUID planeswalkerId) { + if (defenderIsPlaneswalker && defenderId.equals(planeswalkerId)) { + defenderId = null; + return true; + } + return false; + } + public boolean remove(UUID creatureId) { boolean result = false; if (attackers.contains(creatureId)) { attackers.remove(creatureId); result = true; - } else { - if (blockers.contains(creatureId)) { - blockers.remove(creatureId); - result = true; - //20100423 - 509.2a - if (blockerOrder.contains(creatureId)) { - blockerOrder.remove(creatureId); - } + } else if (blockers.contains(creatureId)) { + blockers.remove(creatureId); + result = true; + //20100423 - 509.2a + if (blockerOrder.contains(creatureId)) { + blockerOrder.remove(creatureId); } } return result; diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index c98db5750b..02e0755577 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -1091,10 +1091,8 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { if (!oneCanBeAttacked) { return false; } - } else { - if (!canAttackCheckRestrictionEffects(defenderId, game)) { - return false; - } + } else if (!canAttackCheckRestrictionEffects(defenderId, game)) { + return false; } return !abilities.containsKey(DefenderAbility.getInstance().getId()) @@ -1223,6 +1221,10 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { public boolean removeFromCombat(Game game, boolean withInfo) { if (this.isAttacking() || this.blocking > 0) { return game.getCombat().removeFromCombat(objectId, game, withInfo); + } else if (getCardType().contains(CardType.PLANESWALKER)) { + if (game.getCombat().getDefenders().contains(getId())) { + game.getCombat().removePlaneswalkerFromCombat(objectId, game, withInfo); + } } return false; } From a9617f8e54237b67a9ad2b889f2c329ed4f8b524 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 25 Feb 2016 22:05:33 +0100 Subject: [PATCH 05/67] * Removed the commander rule 4 (mana generation restriction). --- .../common/continuous/CommanderManaReplacementEffect.java | 4 +++- Mage/src/main/java/mage/game/GameCommanderImpl.java | 5 ++--- Mage/src/main/java/mage/game/GameTinyLeadersImpl.java | 5 ++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/CommanderManaReplacementEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/CommanderManaReplacementEffect.java index c8f0656e37..7ec8ae2bb5 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/CommanderManaReplacementEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/CommanderManaReplacementEffect.java @@ -42,11 +42,13 @@ import mage.game.events.ManaEvent; * * @author LevelX */ - //20130711 /* * 903.9. If mana would be added to a player's mana pool of a color that isn't in the color identity * of that player's commander, that amount of colorless mana is added to that player's mana pool instead. + * + * Commander rule #4 was removed Jan. 18, 2016 + * */ public class CommanderManaReplacementEffect extends ReplacementEffectImpl { diff --git a/Mage/src/main/java/mage/game/GameCommanderImpl.java b/Mage/src/main/java/mage/game/GameCommanderImpl.java index ca1631bd33..a875b5fd3d 100644 --- a/Mage/src/main/java/mage/game/GameCommanderImpl.java +++ b/Mage/src/main/java/mage/game/GameCommanderImpl.java @@ -35,7 +35,6 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.InfoEffect; -import mage.abilities.effects.common.continuous.CommanderManaReplacementEffect; import mage.abilities.effects.common.continuous.CommanderReplacementEffect; import mage.abilities.effects.common.cost.CommanderCostModification; import mage.cards.Card; @@ -50,7 +49,6 @@ import mage.filter.FilterCard; import mage.game.turn.TurnMod; import mage.players.Player; import mage.target.common.TargetCardInHand; -import mage.util.CardUtil; import mage.watchers.common.CommanderInfoWatcher; public abstract class GameCommanderImpl extends GameImpl { @@ -90,7 +88,8 @@ public abstract class GameCommanderImpl extends GameImpl { commander.getAbilities().setControllerId(player.getId()); ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary)); ability.addEffect(new CommanderCostModification(commander.getId())); - ability.addEffect(new CommanderManaReplacementEffect(player.getId(), CardUtil.getColorIdentity(commander))); + // Commander rule #4 was removed Jan. 18, 2016 + // ability.addEffect(new CommanderManaReplacementEffect(player.getId(), CardUtil.getColorIdentity(commander))); getState().setValue(commander.getId() + "_castCount", 0); CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), CHECK_COMMANDER_DAMAGE); getState().getWatchers().add(watcher); diff --git a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java index 98460aa9a2..7eaa58a27d 100644 --- a/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java +++ b/Mage/src/main/java/mage/game/GameTinyLeadersImpl.java @@ -34,7 +34,6 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.InfoEffect; -import mage.abilities.effects.common.continuous.CommanderManaReplacementEffect; import mage.abilities.effects.common.continuous.CommanderReplacementEffect; import mage.abilities.effects.common.cost.CommanderCostModification; import mage.cards.Card; @@ -49,7 +48,6 @@ import mage.constants.Rarity; import mage.constants.Zone; import mage.game.turn.TurnMod; import mage.players.Player; -import mage.util.CardUtil; import mage.watchers.common.CommanderInfoWatcher; /** @@ -88,7 +86,8 @@ public abstract class GameTinyLeadersImpl extends GameImpl { commander.moveToZone(Zone.COMMAND, null, this, true); ability.addEffect(new CommanderReplacementEffect(commander.getId(), alsoHand, alsoLibrary)); ability.addEffect(new CommanderCostModification(commander.getId())); - ability.addEffect(new CommanderManaReplacementEffect(player.getId(), CardUtil.getColorIdentity(commander))); + // Commander rule #4 was removed Jan. 18, 2016 + // ability.addEffect(new CommanderManaReplacementEffect(player.getId(), CardUtil.getColorIdentity(commander))); getState().setValue(commander.getId() + "_castCount", 0); CommanderInfoWatcher watcher = new CommanderInfoWatcher(commander.getId(), false); getState().getWatchers().add(watcher); From 288d951ae7154a0cf9c6e6ab3d93ad38ea824a06 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 25 Feb 2016 22:29:28 +0100 Subject: [PATCH 06/67] * Fixed Eldrazi Mimic test failing sometimes. --- .../mage/test/cards/copy/EldraziMimicTest.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/EldraziMimicTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/EldraziMimicTest.java index e07aedd5ec..d880482b64 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/EldraziMimicTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/EldraziMimicTest.java @@ -45,11 +45,11 @@ public class EldraziMimicTest extends CardTestPlayerBase { */ @Test public void testCopyIfPermanentIsGone() { - // Devoid - // When Drowner of Hope enters the battlefield, put two 1/1 colorless Eldrazi Scion creature tokens onto the battlefield. They have "Sacrifice this creature: Add {C} to your mana pool." - // Sacrifice an Eldrazi Scion: Tap target creature. - addCard(Zone.HAND, playerA, "Drowner of Hope", 1); // {5}{U} 5/5 - addCard(Zone.BATTLEFIELD, playerA, "Island", 6); + // Devoid (This card has no color.) + // Flying + // Whenever you cast a colorless spell, target opponent exiles the top card of his or her library. + addCard(Zone.HAND, playerA, "Thought Harvester", 1); // {3}{U} 2/4 + addCard(Zone.BATTLEFIELD, playerA, "Island", 4); // Whenever another colorless creature enters the battlefield under your control, you may have the base power and toughness of Eldrazi Mimic // become that creature's power and toughness until end of turn. addCard(Zone.BATTLEFIELD, playerA, "Eldrazi Mimic", 1); // 2/1 @@ -58,17 +58,17 @@ public class EldraziMimicTest extends CardTestPlayerBase { addCard(Zone.HAND, playerB, "Terror", 1); // {1}{B} addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Drowner of Hope"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Terror", "Drowner of Hope"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thought Harvester"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Terror", "Thought Harvester"); setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); assertGraveyardCount(playerB, "Terror", 1); - assertGraveyardCount(playerA, "Drowner of Hope", 1); + assertGraveyardCount(playerA, "Thought Harvester", 1); assertPermanentCount(playerA, "Eldrazi Mimic", 1); - assertPowerToughness(playerA, "Eldrazi Mimic", 5, 5); + assertPowerToughness(playerA, "Eldrazi Mimic", 2, 4); } From 2613e3113f4fcec08cd8812ef24060e04e9e9f2c Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 26 Feb 2016 00:55:31 +0100 Subject: [PATCH 07/67] Added some tests. --- .../test/commander/FFA3/PrimordialTest.java | 72 +++++++++++++++++++ .../mage/test/multiplayer/PrimordialTest.java | 51 ++++++++++--- .../base/CardTestCommander3PlayersFFA.java | 60 ++++++++++++++++ .../base/impl/CardTestPlayerAPIImpl.java | 10 +++ 4 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/FFA3/PrimordialTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander3PlayersFFA.java diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/FFA3/PrimordialTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/FFA3/PrimordialTest.java new file mode 100644 index 0000000000..d635ac019d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/FFA3/PrimordialTest.java @@ -0,0 +1,72 @@ +/* + * 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.commander.FFA3; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander3PlayersFFA; + +/** + * + * @author LevelX2 + */ +public class PrimordialTest extends CardTestCommander3PlayersFFA { + + /** + * Diluvian Primordial ETB trigger never happened in a 3 player FFA + * commander game. He just resolved, no ETB trigger occurred. + */ + @Test + public void DiluvianPrimordialTest() { + // Flying + // When Diluvian Primordial enters the battlefield, for each opponent, you may cast up to one target instant or sorcery card from that player's graveyard without paying its mana cost. If a card cast this way would be put into a graveyard this turn, exile it instead. + addCard(Zone.HAND, playerA, "Diluvian Primordial"); // {5}{U}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + + addCard(Zone.GRAVEYARD, playerB, "Lightning Bolt"); + addCard(Zone.GRAVEYARD, playerC, "Lightning Bolt"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Diluvian Primordial"); + addTarget(playerA, "Lightning Bolt"); + addTarget(playerA, "Lightning Bolt"); + + addTarget(playerA, playerB); + addTarget(playerA, playerC); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Diluvian Primordial", 1); + assertExileCount("Lightning Bolt", 2); + assertLife(playerA, 40); + assertLife(playerB, 37); + assertLife(playerC, 37); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PrimordialTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PrimordialTest.java index fec0313fc0..8375ab969e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/multiplayer/PrimordialTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/PrimordialTest.java @@ -36,26 +36,26 @@ import org.mage.test.serverside.base.CardTestMultiPlayerBase; * * @author LevelX2 */ - public class PrimordialTest extends CardTestMultiPlayerBase { /** * Tests Primordial cards with multiplayer effects - * + * */ @Test public void SepulchralPrimordialTest() { // When Sepulchral Primordial enters the battlefield, for each opponent, you may put up to one // target creature card from that player's graveyard onto the battlefield under your control. - addCard(Zone.HAND, playerA, "Sepulchral Primordial"); - addCard(Zone.BATTLEFIELD, playerA, "Swamp",7); - + addCard(Zone.HAND, playerA, "Sepulchral Primordial"); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7); + + // Player order: A -> D -> C -> B addCard(Zone.GRAVEYARD, playerB, "Silvercoat Lion"); addCard(Zone.GRAVEYARD, playerC, "Walking Corpse"); addCard(Zone.GRAVEYARD, playerD, "Pillarfield Ox"); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sepulchral Primordial"); - + setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -67,4 +67,39 @@ public class PrimordialTest extends CardTestMultiPlayerBase { assertGraveyardCount(playerD, "Pillarfield Ox", 0); } -} \ No newline at end of file + /** + * Diluvian Primordial ETB trigger never happened in a 3 player FFA + * commander game. He just resolved, no ETB trigger occurred. + */ + @Test + public void DiluvianPrimordialTest() { + // Flying + // When Diluvian Primordial enters the battlefield, for each opponent, you may cast up to one target instant or sorcery card from that player's graveyard without paying its mana cost. If a card cast this way would be put into a graveyard this turn, exile it instead. + addCard(Zone.HAND, playerA, "Diluvian Primordial"); // {5}{U}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 7); + + addCard(Zone.GRAVEYARD, playerB, "Lightning Bolt"); + addCard(Zone.GRAVEYARD, playerC, "Lightning Bolt"); + addCard(Zone.GRAVEYARD, playerD, "Lightning Bolt"); + + // Player order: A -> D -> C -> B + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Diluvian Primordial"); + addTarget(playerA, "Lightning Bolt"); + addTarget(playerA, "Lightning Bolt"); + + addTarget(playerA, playerB); + addTarget(playerA, playerD); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Diluvian Primordial", 1); + assertGraveyardCount(playerC, "Lightning Bolt", 1); + assertExileCount("Lightning Bolt", 2); + assertLife(playerA, 20); + assertLife(playerB, 17); + assertLife(playerC, 20); + assertLife(playerD, 17); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander3PlayersFFA.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander3PlayersFFA.java new file mode 100644 index 0000000000..2efd5a4139 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestCommander3PlayersFFA.java @@ -0,0 +1,60 @@ +/* + * 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.serverside.base; + +import java.io.FileNotFoundException; +import mage.constants.MultiplayerAttackOption; +import mage.constants.RangeOfInfluence; +import mage.game.CommanderFreeForAll; +import mage.game.Game; +import mage.game.GameException; +import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; + +/** + * + * @author LevelX2 + */ +public abstract class CardTestCommander3PlayersFFA extends CardTestPlayerAPIImpl { + + public CardTestCommander3PlayersFFA() { + super(); + this.deckNameA = "CommanderDuel.dck"; + this.deckNameB = "CommanderDuel.dck"; + this.deckNameC = "CommanderDuel.dck"; + } + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + Game game = new CommanderFreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 40); + playerA = createPlayer(game, playerA, "PlayerA"); + playerB = createPlayer(game, playerB, "PlayerB"); + playerC = createPlayer(game, playerC, "PlayerC"); + return game; + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index 28b0b0e0ea..b6f13c1257 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -47,6 +47,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement protected String deckNameA; protected String deckNameB; + protected String deckNameC; + protected String deckNameD; protected enum ExpectedType { @@ -1118,6 +1120,14 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement deckNameB = deckname; } + public void setDecknamePlayerC(String deckname) { + deckNameC = deckname; + } + + public void setDecknamePlayerD(String deckname) { + deckNameD = deckname; + } + protected void skipInitShuffling() { gameOptions.skipInitShuffling = true; } From a5236c5879ef197c12b47214d2a5a8aae12034ea Mon Sep 17 00:00:00 2001 From: DjB Date: Thu, 25 Feb 2016 22:56:50 -0600 Subject: [PATCH 08/67] Create Abjure.java --- .../src/mage/sets/weatherlight/Abjure.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/weatherlight/Abjure.java diff --git a/Mage.Sets/src/mage/sets/weatherlight/Abjure.java b/Mage.Sets/src/mage/sets/weatherlight/Abjure.java new file mode 100644 index 0000000000..c2b610e286 --- /dev/null +++ b/Mage.Sets/src/mage/sets/weatherlight/Abjure.java @@ -0,0 +1,74 @@ +/* + * 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.weatherlight; + +import java.util.UUID; +import mage.ObjectColor; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.CounterTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.ColorPredicate; +import mage.target.TargetSpell; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author djbrez + */ +public class Abjure extends CardImpl { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("a blue permanent"); + + static { + filter.add(new ColorPredicate(ObjectColor.BLUE)); + } + + public Abjure(UUID ownerId) { + super(ownerId, 31, "Abjure", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{U}"); + this.expansionSetCode = "WTH"; + + // As an additional cost to cast Abjure, sacrifice a blue permanent. + this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetControlledPermanent(1,1,filter, true))); + + // Counter target spell. + this.getSpellAbility().addEffect(new CounterTargetEffect()); + this.getSpellAbility().addTarget(new TargetSpell()); + } + + public Abjure(final Abjure card) { + super(card); + } + + @Override + public Abjure copy() { + return new Abjure(this); + } +} From b9dc5036ef6fbc4a14ed295f38266d4d6a8d6d7d Mon Sep 17 00:00:00 2001 From: rkfg Date: Fri, 26 Feb 2016 21:05:21 +0300 Subject: [PATCH 09/67] Assembly fix. --- Mage.Client/src/main/assembly/distribution.xml | 1 + Mage.Server/src/main/assembly/distribution.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/Mage.Client/src/main/assembly/distribution.xml b/Mage.Client/src/main/assembly/distribution.xml index 39c50ec1c9..5f8c9aef15 100644 --- a/Mage.Client/src/main/assembly/distribution.xml +++ b/Mage.Client/src/main/assembly/distribution.xml @@ -4,6 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.1 http://maven.apache.org/xsd/assembly-1.1.1.xsd"> false + mage-client zip diff --git a/Mage.Server/src/main/assembly/distribution.xml b/Mage.Server/src/main/assembly/distribution.xml index cf324aec06..20bab51d57 100644 --- a/Mage.Server/src/main/assembly/distribution.xml +++ b/Mage.Server/src/main/assembly/distribution.xml @@ -4,6 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.1 http://maven.apache.org/xsd/assembly-1.1.1.xsd"> false + mage-server zip From 9842199d6fe931a78deab9c9be9b9d7aefca66ff Mon Sep 17 00:00:00 2001 From: rkfg Date: Fri, 26 Feb 2016 21:05:43 +0300 Subject: [PATCH 10/67] Fix NPE after finishing a round. --- .../src/main/java/mage/client/deckeditor/DeckEditorPanel.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index f7420e2613..0b910c5b5c 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -169,8 +169,6 @@ public class DeckEditorPanel extends javax.swing.JPanel { if (deck != null) { this.cardSelector.loadSideboard(new ArrayList<>(deck.getSideboard()), this.bigCard); } - // TODO: take from preferences - this.cardSelector.switchToGrid(); this.btnExit.setVisible(false); this.btnImport.setVisible(false); From a2bc4d705a9c8406e237e71c5dff79253ffc80c6 Mon Sep 17 00:00:00 2001 From: rkfg Date: Fri, 26 Feb 2016 21:02:25 +0300 Subject: [PATCH 11/67] Make perl script working on Linux. --- Utils/gen-simple-cards-by-set.pl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Utils/gen-simple-cards-by-set.pl b/Utils/gen-simple-cards-by-set.pl index f0dc6779ae..ef72241097 100755 --- a/Utils/gen-simple-cards-by-set.pl +++ b/Utils/gen-simple-cards-by-set.pl @@ -46,13 +46,13 @@ if(!exists $knownSets{$setName}) { my $packageName = $knownSets{$setName}; $setName =~ s/"/\\"/g; -system("gen-existing-cards-by-set.pl \"$setName\""); +system("./gen-existing-cards-by-set.pl \"$setName\""); # Generate missing simple cards print "Simple cards generated: \n"; foreach my $cardName (@setCards) { my $fileName = "../Mage.Sets/src/mage/sets/" . $packageName . "/" . toCamelCase(${$cardName}[0]) . ".java"; if(!-e $fileName) { - system("gen-card.pl \"${$cardName}[0]\" true"); + system("./gen-card.pl \"${$cardName}[0]\" true"); } } \ No newline at end of file From c34c19c9e86baffbfae5976657ba3e33008be742 Mon Sep 17 00:00:00 2001 From: Quercitron Date: Sat, 27 Feb 2016 01:29:33 +0300 Subject: [PATCH 12/67] Added Aphetto Dredging --- .../mage/sets/onslaught/AphettoDredging.java | 94 +++++++++++++++++++ .../mage/sets/pdsslivers/AphettoDredging.java | 52 ++++++++++ 2 files changed, 146 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/onslaught/AphettoDredging.java create mode 100644 Mage.Sets/src/mage/sets/pdsslivers/AphettoDredging.java diff --git a/Mage.Sets/src/mage/sets/onslaught/AphettoDredging.java b/Mage.Sets/src/mage/sets/onslaught/AphettoDredging.java new file mode 100644 index 0000000000..dced8e09c6 --- /dev/null +++ b/Mage.Sets/src/mage/sets/onslaught/AphettoDredging.java @@ -0,0 +1,94 @@ +/* + * 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.onslaught; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.SpellAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; +import mage.cards.CardImpl; +import mage.cards.repository.CardRepository; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.filter.common.FilterCreatureCard; +import mage.filter.predicate.mageobject.SubtypePredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCardInYourGraveyard; + +/** + * + * @author Quercitron + */ +public class AphettoDredging extends CardImpl { + + public AphettoDredging(UUID ownerId) { + super(ownerId, 125, "Aphetto Dredging", Rarity.COMMON, new CardType[]{CardType.SORCERY}, "{3}{B}"); + this.expansionSetCode = "ONS"; + + // Return up to three target creature cards of the creature type of your choice from your graveyard to your hand. + Effect effect = new ReturnFromGraveyardToHandTargetEffect(); + effect.setText("Return up to three target creature cards of the creature type of your choice from your graveyard to your hand"); + this.getSpellAbility().addEffect(effect); + } + + @Override + public void adjustTargets(Ability ability, Game game) { + if (ability instanceof SpellAbility) { + Player controller = game.getPlayer(ability.getControllerId()); + if (controller != null) { + Choice typeChoice = new ChoiceImpl(true); + typeChoice.setMessage("Choose a creature type"); + typeChoice.setChoices(CardRepository.instance.getCreatureTypes()); + while (!controller.choose(Outcome.PutCreatureInPlay, typeChoice, game)) { + if (!controller.canRespond()) { + return; + } + } + String chosenType = typeChoice.getChoice(); + + FilterCreatureCard filter = new FilterCreatureCard(chosenType + " cards"); + filter.add(new SubtypePredicate(chosenType)); + ability.addTarget(new TargetCardInYourGraveyard(0, 3, filter)); + } + } + } + + public AphettoDredging(final AphettoDredging card) { + super(card); + } + + @Override + public AphettoDredging copy() { + return new AphettoDredging(this); + } +} diff --git a/Mage.Sets/src/mage/sets/pdsslivers/AphettoDredging.java b/Mage.Sets/src/mage/sets/pdsslivers/AphettoDredging.java new file mode 100644 index 0000000000..3b6015c50e --- /dev/null +++ b/Mage.Sets/src/mage/sets/pdsslivers/AphettoDredging.java @@ -0,0 +1,52 @@ +/* + * 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.pdsslivers; + +import java.util.UUID; + +/** + * + * @author Quercitron + */ +public class AphettoDredging extends mage.sets.onslaught.AphettoDredging { + + public AphettoDredging(UUID ownerId) { + super(ownerId); + this.cardNumber = 28; + this.expansionSetCode = "PDS"; + } + + public AphettoDredging(final AphettoDredging card) { + super(card); + } + + @Override + public AphettoDredging copy() { + return new AphettoDredging(this); + } +} From aa015b52e6ac03acca076c2abe28732ce2a4e7e9 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 27 Feb 2016 09:15:17 +0100 Subject: [PATCH 13/67] GUI Size - Fixed that table header lines were not horizontally moved. --- Mage.Client/src/main/java/mage/client/MageFrame.java | 9 ++++++++- .../src/main/java/mage/client/cards/CardsList.java | 1 - .../main/java/mage/client/deckeditor/CardSelector.java | 2 -- .../main/java/mage/client/dialog/TableWaitingDialog.java | 1 - .../main/java/mage/client/table/PlayersChatPanel.java | 1 - .../src/main/java/mage/client/table/TablesPanel.java | 2 -- .../java/mage/client/tournament/TournamentPanel.java | 2 -- 7 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index d10fb45b48..cc847a88ad 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -682,6 +682,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { gamePane.watchGame(gameId); setActive(gamePane); } catch (PropertyVetoException ex) { + LOGGER.debug("Problem starting watching game " + gameId, ex); } } @@ -1068,9 +1069,11 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { if (setActive) { setActive(tablesPane); } else // if other panel was already shown, mamke sure it's topmost again - if (topPanebefore != null) { + { + if (topPanebefore != null) { setActive(topPanebefore); } + } } public void hideGames() { @@ -1417,6 +1420,10 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { break; case CLIENT_STOP_WATCHING: session.stopWatching(userRequestMessage.getGameId()); + GamePanel gamePanel = getGame(userRequestMessage.getGameId()); + if (gamePanel != null) { + gamePanel.removeGame(); + } removeGame(userRequestMessage.getGameId()); break; case CLIENT_EXIT: diff --git a/Mage.Client/src/main/java/mage/client/cards/CardsList.java b/Mage.Client/src/main/java/mage/client/cards/CardsList.java index c2c2b8fa79..120bc8a49d 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardsList.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardsList.java @@ -149,7 +149,6 @@ public class CardsList extends javax.swing.JPanel implements MouseListener, ICar private void setGUISize() { mainTable.getTableHeader().setFont(GUISizeHelper.tableFont); - mainTable.getTableHeader().setPreferredSize(new Dimension(GUISizeHelper.tableHeaderHeight, GUISizeHelper.tableHeaderHeight)); mainTable.setFont(GUISizeHelper.tableFont); mainTable.setRowHeight(GUISizeHelper.getTableRowHeight()); cardDimension = GUISizeHelper.editorCardDimension; diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index 7d7c2fabe7..d1f4d01a5e 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -35,7 +35,6 @@ package mage.client.deckeditor; import java.awt.Color; import java.awt.Cursor; -import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; @@ -187,7 +186,6 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private void setGUISize() { mainTable.getTableHeader().setFont(GUISizeHelper.tableFont); - mainTable.getTableHeader().setPreferredSize(new Dimension(GUISizeHelper.tableHeaderHeight, GUISizeHelper.tableHeaderHeight)); mainTable.setFont(GUISizeHelper.tableFont); mainTable.setRowHeight(GUISizeHelper.getTableRowHeight()); diff --git a/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java index bea8603e16..bae1bb18ca 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java @@ -104,7 +104,6 @@ public class TableWaitingDialog extends MageDialog { private void setGUISize() { tableSeats.getTableHeader().setFont(GUISizeHelper.tableFont); - tableSeats.getTableHeader().setPreferredSize(new Dimension(GUISizeHelper.tableHeaderHeight, GUISizeHelper.tableHeaderHeight)); tableSeats.setFont(GUISizeHelper.tableFont); tableSeats.setRowHeight(GUISizeHelper.getTableRowHeight()); diff --git a/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java b/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java index 4e11dd3faa..925a8ccf22 100644 --- a/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java @@ -123,7 +123,6 @@ public class PlayersChatPanel extends javax.swing.JPanel { private void setGUISize() { jTablePlayers.getTableHeader().setFont(GUISizeHelper.tableFont); - jTablePlayers.getTableHeader().setPreferredSize(new Dimension((int) jTablePlayers.getTableHeader().getPreferredSize().getWidth(), GUISizeHelper.tableHeaderHeight)); jTablePlayers.setFont(GUISizeHelper.tableFont); jTablePlayers.setRowHeight(GUISizeHelper.getTableRowHeight()); jScrollPanePlayers.getVerticalScrollBar().setPreferredSize(new Dimension(GUISizeHelper.scrollBarSize, 0)); diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 73febdc729..41a7179c1c 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -294,12 +294,10 @@ public class TablesPanel extends javax.swing.JPanel { private void setGUISize() { tableTables.getTableHeader().setFont(GUISizeHelper.tableFont); - tableTables.getTableHeader().setPreferredSize(new Dimension(GUISizeHelper.tableHeaderHeight, GUISizeHelper.tableHeaderHeight)); tableTables.setFont(GUISizeHelper.tableFont); tableTables.setRowHeight(GUISizeHelper.getTableRowHeight()); tableCompleted.getTableHeader().setFont(GUISizeHelper.tableFont); - tableCompleted.getTableHeader().setPreferredSize(new Dimension(GUISizeHelper.tableHeaderHeight, GUISizeHelper.tableHeaderHeight)); tableCompleted.setFont(GUISizeHelper.tableFont); tableCompleted.setRowHeight(GUISizeHelper.getTableRowHeight()); diff --git a/Mage.Client/src/main/java/mage/client/tournament/TournamentPanel.java b/Mage.Client/src/main/java/mage/client/tournament/TournamentPanel.java index 133e50c375..798fb00eb1 100644 --- a/Mage.Client/src/main/java/mage/client/tournament/TournamentPanel.java +++ b/Mage.Client/src/main/java/mage/client/tournament/TournamentPanel.java @@ -154,12 +154,10 @@ public class TournamentPanel extends javax.swing.JPanel { private void setGUISize() { tablePlayers.getTableHeader().setFont(GUISizeHelper.tableFont); - tablePlayers.getTableHeader().setPreferredSize(new Dimension(GUISizeHelper.tableHeaderHeight, GUISizeHelper.tableHeaderHeight)); tablePlayers.setFont(GUISizeHelper.tableFont); tablePlayers.setRowHeight(GUISizeHelper.getTableRowHeight()); tableMatches.getTableHeader().setFont(GUISizeHelper.tableFont); - tableMatches.getTableHeader().setPreferredSize(new Dimension(GUISizeHelper.tableHeaderHeight, GUISizeHelper.tableHeaderHeight)); tableMatches.setFont(GUISizeHelper.tableFont); tableMatches.setRowHeight(GUISizeHelper.getTableRowHeight()); From ac757b6a8812ab35ea51be7d41ffeed9262be9c2 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 27 Feb 2016 09:27:43 +0100 Subject: [PATCH 14/67] Fixed card grid dimension initialisation. --- .../src/main/java/mage/client/cards/CardGrid.java | 12 +++++++++--- .../java/mage/client/deckeditor/CardSelector.java | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/cards/CardGrid.java b/Mage.Client/src/main/java/mage/client/cards/CardGrid.java index 11656e6f16..a2f974f991 100644 --- a/Mage.Client/src/main/java/mage/client/cards/CardGrid.java +++ b/Mage.Client/src/main/java/mage/client/cards/CardGrid.java @@ -78,6 +78,7 @@ public class CardGrid extends javax.swing.JLayeredPane implements MouseListener, public CardGrid() { initComponents(); + setGUISize(); setOpaque(false); } @@ -90,6 +91,14 @@ public class CardGrid extends javax.swing.JLayeredPane implements MouseListener, this.bigCard = null; } + public void changeGUISize() { + setGUISize(); + } + + private void setGUISize() { + cardDimension = GUISizeHelper.editorCardDimension; + } + @Override public void loadCards(CardsView showCards, SortSetting sortSetting, BigCard bigCard, UUID gameId) { this.loadCards(showCards, sortSetting, bigCard, gameId, true); @@ -125,9 +134,6 @@ public class CardGrid extends javax.swing.JLayeredPane implements MouseListener, } private void addCard(CardView card, BigCard bigCard, UUID gameId, boolean drawImage) { - if (cardDimension == null) { - cardDimension = GUISizeHelper.editorCardDimension; - } MageCard cardImg = Plugins.getInstance().getMageCard(card, bigCard, cardDimension, gameId, drawImage); cards.put(card.getId(), cardImg); cardImg.addMouseListener(this); diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index d1f4d01a5e..af2d44eddf 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -182,6 +182,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene public void changeGUISize() { setGUISize(); + cardGrid.changeGUISize(); } private void setGUISize() { From ad4b68dcb582ad48f176c1fdb2b487ac5f90f703 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 27 Feb 2016 09:30:21 +0100 Subject: [PATCH 15/67] Fixed card grid dimension initialisation. --- .../src/main/java/mage/client/deckeditor/DeckEditorPanel.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index 0b910c5b5c..377b69adda 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -169,7 +169,8 @@ public class DeckEditorPanel extends javax.swing.JPanel { if (deck != null) { this.cardSelector.loadSideboard(new ArrayList<>(deck.getSideboard()), this.bigCard); } - + // TODO: take from preferences + this.cardSelector.switchToGrid(); this.btnExit.setVisible(false); this.btnImport.setVisible(false); if (!MageFrame.getSession().isTestMode()) { From f87c5bbbec578928b13102d2ee370d07239a90e2 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 27 Feb 2016 12:33:08 +0100 Subject: [PATCH 16/67] Fixed that the flag on avatar image could be to big. --- .../java/mage/client/game/PlayerPanelExt.java | 2 +- .../client/util/gui/countryBox/CountryUtil.java | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java index 3ec1c661dd..9bcbce7cff 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java @@ -287,7 +287,7 @@ public class PlayerPanelExt extends javax.swing.JPanel { avatar.setText(this.player.getName()); if (!player.getUserData().getFlagName().equals(flagName)) { flagName = player.getUserData().getFlagName(); - this.avatar.setTopTextImage(CountryUtil.getCountryFlagIcon(flagName).getImage()); + this.avatar.setTopTextImage(CountryUtil.getCountryFlagIconSize(flagName, 11).getImage()); } // TODO: Add the wins to the tooltiptext of the avatar String countryname = CountryUtil.getCountryName(flagName); diff --git a/Mage.Client/src/main/java/mage/client/util/gui/countryBox/CountryUtil.java b/Mage.Client/src/main/java/mage/client/util/gui/countryBox/CountryUtil.java index 8fb2f4c2ed..a76a4c508c 100644 --- a/Mage.Client/src/main/java/mage/client/util/gui/countryBox/CountryUtil.java +++ b/Mage.Client/src/main/java/mage/client/util/gui/countryBox/CountryUtil.java @@ -27,7 +27,6 @@ public class CountryUtil { public static ImageIcon getCountryFlagIcon(String countryCode) { ImageIcon flagIcon = FLAG_ICON_CACHE.get(countryCode); if (flagIcon == null) { - // URL url = CountryUtil.class.getResource("/flags/" + countryCode + (countryCode.endsWith(".png") ? "" : ".png")); Image flagImage = ImageHelper.getImageFromResources("/flags/" + countryCode + (countryCode.endsWith(".png") ? "" : ".png")); if (flagImage != null) { if (GUISizeHelper.flagHeight > 11) { @@ -48,6 +47,22 @@ public class CountryUtil { return flagIcon; } + public static ImageIcon getCountryFlagIconSize(String countryCode, int height) { + ImageIcon flagIcon = null; + Image flagImage = ImageHelper.getImageFromResources("/flags/" + countryCode + (countryCode.endsWith(".png") ? "" : ".png")); + if (flagImage != null) { + + if (height > 11) { + int width = Math.round(height * flagImage.getWidth(null) / flagImage.getHeight(null)); + BufferedImage resized = ImageHelper.scale((BufferedImage) flagImage, BufferedImage.TYPE_4BYTE_ABGR, width, height); + flagIcon = new ImageIcon(resized); + } else { + flagIcon = new ImageIcon(flagImage); + } + } + return flagIcon; + } + public static void changeGUISize() { FLAG_ICON_CACHE.clear(); } From 0f1839af5630e627684fb2ad04d40a4c66d53516 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 27 Feb 2016 23:44:59 +0100 Subject: [PATCH 17/67] Reworked some trap cards using old AlternateCosts class. --- .../mage/sets/avacynrestored/SecondGuess.java | 2 +- .../sets/darkascension/CurseOfExhaustion.java | 2 +- .../dragonsoftarkir/HardenedBerserker.java | 4 +- .../src/mage/sets/eventide/DreamThief.java | 64 ++------- .../mage/sets/futuresight/StormEntity.java | 2 +- .../sets/gatecrash/IncursionSpecialist.java | 2 +- .../mage/sets/magic2011/AngelicArbiter.java | 2 +- .../oathofthegatewatch/JoriEnRuinDiver.java | 2 +- .../PyromancersAssault.java | 2 +- .../ErayoSoratamiAscendant.java | 4 +- .../mage/sets/seventhedition/Impatience.java | 2 +- .../mage/sets/worldwake/PermafrostTrap.java | 127 ++++------------- .../mage/sets/worldwake/RefractionTrap.java | 98 ++++--------- .../src/mage/sets/worldwake/RicochetTrap.java | 95 ++++--------- .../src/mage/sets/worldwake/SlingbowTrap.java | 46 +++--- .../mage/sets/zendikar/ArrowVolleyTrap.java | 27 ++-- .../mage/sets/zendikar/BalothCageTrap.java | 81 +++-------- .../src/mage/sets/zendikar/InfernoTrap.java | 8 +- .../src/mage/sets/zendikar/LavaballTrap.java | 105 ++++---------- .../src/mage/sets/zendikar/LethargyTrap.java | 49 +++---- .../src/mage/sets/zendikar/MindbreakTrap.java | 133 +++--------------- .../src/mage/sets/zendikar/PitfallTrap.java | 32 ++--- .../src/mage/sets/zendikar/SummoningTrap.java | 11 +- .../src/mage/sets/zendikar/WhiplashTrap.java | 114 ++++----------- .../cost/modification/DefenseGridTest.java | 78 ++++++++++ .../java/org/mage/test/player/TestPlayer.java | 44 +++--- .../NoSpellsWereCastLastTurnCondition.java | 2 +- ...OrMoreSpellsWereCastLastTurnCondition.java | 2 +- .../costs/AlternativeCostSourceAbility.java | 3 +- .../effects/common/ExileTargetEffect.java | 7 + .../CantCastMoreThanOneSpellEffect.java | 16 +-- ...stOnlyIfYouHaveCastAnotherSpellEffect.java | 27 ++-- .../mage/abilities/keyword/StormAbility.java | 2 +- .../mage/abilities/keyword/SurgeAbility.java | 2 +- Mage/src/main/java/mage/watchers/Watcher.java | 29 ++-- .../common/CastSpellLastTurnWatcher.java | 2 +- .../PermanentsEnteredBattlefieldWatcher.java | 65 +++++++++ .../watchers/common/SpellsCastWatcher.java | 74 ++++++++++ 38 files changed, 547 insertions(+), 820 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/DefenseGridTest.java create mode 100644 Mage/src/main/java/mage/watchers/common/PermanentsEnteredBattlefieldWatcher.java create mode 100644 Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java diff --git a/Mage.Sets/src/mage/sets/avacynrestored/SecondGuess.java b/Mage.Sets/src/mage/sets/avacynrestored/SecondGuess.java index 9d9621a651..3b177bc92e 100644 --- a/Mage.Sets/src/mage/sets/avacynrestored/SecondGuess.java +++ b/Mage.Sets/src/mage/sets/avacynrestored/SecondGuess.java @@ -77,7 +77,7 @@ class SecondSpellPredicate implements Predicate { @Override public boolean apply(Spell input, Game game) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); if (watcher.getSpellOrder(new MageObjectReference(input.getId(), game), game) == 2) { return true; diff --git a/Mage.Sets/src/mage/sets/darkascension/CurseOfExhaustion.java b/Mage.Sets/src/mage/sets/darkascension/CurseOfExhaustion.java index 84cb8b11fc..200eefa2a3 100644 --- a/Mage.Sets/src/mage/sets/darkascension/CurseOfExhaustion.java +++ b/Mage.Sets/src/mage/sets/darkascension/CurseOfExhaustion.java @@ -104,7 +104,7 @@ class CurseOfExhaustionEffect extends ContinuousRuleModifyingEffectImpl { if (enchantment != null && enchantment.getAttachedTo() != null) { Player player = game.getPlayer(enchantment.getAttachedTo()); if (player != null && event.getPlayerId().equals(player.getId())) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(event.getPlayerId()) > 0) { return true; } diff --git a/Mage.Sets/src/mage/sets/dragonsoftarkir/HardenedBerserker.java b/Mage.Sets/src/mage/sets/dragonsoftarkir/HardenedBerserker.java index 383209aa32..16a06295d5 100644 --- a/Mage.Sets/src/mage/sets/dragonsoftarkir/HardenedBerserker.java +++ b/Mage.Sets/src/mage/sets/dragonsoftarkir/HardenedBerserker.java @@ -88,7 +88,7 @@ class HardenedBerserkerSpellsCostReductionEffect extends CostModificationEffectI @Override public void init(Ability source, Game game) { super.init(source, game); - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); if (watcher != null) { spellsCast = watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(source.getControllerId()); } @@ -102,7 +102,7 @@ class HardenedBerserkerSpellsCostReductionEffect extends CostModificationEffectI @Override public boolean applies(Ability abilityToModify, Ability source, Game game) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); if (watcher != null) { if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(source.getControllerId()) > spellsCast) { discard(); // only one use diff --git a/Mage.Sets/src/mage/sets/eventide/DreamThief.java b/Mage.Sets/src/mage/sets/eventide/DreamThief.java index a3dc86a47d..8e2ec575f4 100644 --- a/Mage.Sets/src/mage/sets/eventide/DreamThief.java +++ b/Mage.Sets/src/mage/sets/eventide/DreamThief.java @@ -29,7 +29,6 @@ package mage.sets.eventide; import java.util.UUID; import mage.MageInt; -import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.Condition; @@ -39,14 +38,9 @@ import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.constants.WatcherScope; -import mage.filter.FilterSpell; -import mage.filter.predicate.mageobject.ColorPredicate; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.stack.Spell; -import mage.watchers.Watcher; +import mage.watchers.common.SpellsCastWatcher; /** * @@ -69,7 +63,8 @@ public class DreamThief extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Dream Thief enters the battlefield, draw a card if you've cast another blue spell this turn. - this.addAbility(new EntersBattlefieldTriggeredAbility(new ConditionalOneShotEffect(new DrawCardSourceControllerEffect(1), new CastBlueSpellThisTurnCondition(), rule)), new DreamThiefWatcher(this.getId())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new ConditionalOneShotEffect(new DrawCardSourceControllerEffect(1), new CastBlueSpellThisTurnCondition(), rule)), + new SpellsCastWatcher()); } @@ -87,55 +82,14 @@ class CastBlueSpellThisTurnCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - DreamThiefWatcher watcher = (DreamThiefWatcher) game.getState().getWatchers().get("DreamThiefWatcher", source.getControllerId()); + SpellsCastWatcher watcher = (SpellsCastWatcher) game.getState().getWatchers().get(SpellsCastWatcher.class.getName()); if (watcher != null) { - return watcher.conditionMet(); + for (Spell spell : watcher.getSpellsCastThisTurn(source.getControllerId())) { + if (!spell.getSourceId().equals(source.getSourceId()) && spell.getColor(game).isBlue()) { + return true; + } + } } return false; } } - -class DreamThiefWatcher extends Watcher { - - private static final FilterSpell filter = new FilterSpell(); - - static { - filter.add(new ColorPredicate(ObjectColor.BLUE)); - } - - private UUID cardId; - - public DreamThiefWatcher(UUID cardId) { - super("DreamThiefWatcher", WatcherScope.PLAYER); - this.cardId = cardId; - } - - public DreamThiefWatcher(final DreamThiefWatcher watcher) { - super(watcher); - this.cardId = watcher.cardId; - } - - @Override - public DreamThiefWatcher copy() { - return new DreamThiefWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - if (condition == true) { //no need to check - condition has already occured - return; - } - if (event.getType() == EventType.SPELL_CAST - && controllerId.equals(event.getPlayerId())) { - Spell spell = game.getStack().getSpell(event.getTargetId()); - if (!spell.getSourceId().equals(cardId) && filter.match(spell, game)) { - condition = true; - } - } - } - - @Override - public void reset() { - super.reset(); - } -} diff --git a/Mage.Sets/src/mage/sets/futuresight/StormEntity.java b/Mage.Sets/src/mage/sets/futuresight/StormEntity.java index 820aee7e38..30d3c0a4c7 100644 --- a/Mage.Sets/src/mage/sets/futuresight/StormEntity.java +++ b/Mage.Sets/src/mage/sets/futuresight/StormEntity.java @@ -80,7 +80,7 @@ class OtherSpellsCastThisTurnCount implements DynamicValue { @Override public int calculate(Game game, Ability sourceAbility, Effect effect) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); return watcher.getAmountOfSpellsAllPlayersCastOnCurrentTurn() - 1; } diff --git a/Mage.Sets/src/mage/sets/gatecrash/IncursionSpecialist.java b/Mage.Sets/src/mage/sets/gatecrash/IncursionSpecialist.java index e6e49c66bd..0a5a3b84e5 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/IncursionSpecialist.java +++ b/Mage.Sets/src/mage/sets/gatecrash/IncursionSpecialist.java @@ -95,7 +95,7 @@ class IncursionTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getPlayerId().equals(controllerId)) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(event.getPlayerId()) == 2) { return true; } diff --git a/Mage.Sets/src/mage/sets/magic2011/AngelicArbiter.java b/Mage.Sets/src/mage/sets/magic2011/AngelicArbiter.java index d71cf015bc..c32aa2403f 100644 --- a/Mage.Sets/src/mage/sets/magic2011/AngelicArbiter.java +++ b/Mage.Sets/src/mage/sets/magic2011/AngelicArbiter.java @@ -141,7 +141,7 @@ class AngelicArbiterCantAttackTargetEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { if (game.getActivePlayerId().equals(permanent.getControllerId()) && game.getOpponents(source.getControllerId()).contains(permanent.getControllerId())) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(permanent.getControllerId()) > 0) { return true; } diff --git a/Mage.Sets/src/mage/sets/oathofthegatewatch/JoriEnRuinDiver.java b/Mage.Sets/src/mage/sets/oathofthegatewatch/JoriEnRuinDiver.java index 88396f9c5b..7e0400433b 100644 --- a/Mage.Sets/src/mage/sets/oathofthegatewatch/JoriEnRuinDiver.java +++ b/Mage.Sets/src/mage/sets/oathofthegatewatch/JoriEnRuinDiver.java @@ -92,7 +92,7 @@ class JoriEnTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getPlayerId().equals(controllerId)) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(event.getPlayerId()) == 2) { return true; } diff --git a/Mage.Sets/src/mage/sets/oathofthegatewatch/PyromancersAssault.java b/Mage.Sets/src/mage/sets/oathofthegatewatch/PyromancersAssault.java index 20b0ac73ed..0f6846ae2a 100644 --- a/Mage.Sets/src/mage/sets/oathofthegatewatch/PyromancersAssault.java +++ b/Mage.Sets/src/mage/sets/oathofthegatewatch/PyromancersAssault.java @@ -90,7 +90,7 @@ class PyromancersAssaultTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getPlayerId().equals(controllerId)) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(event.getPlayerId()) == 2) { return true; } diff --git a/Mage.Sets/src/mage/sets/saviorsofkamigawa/ErayoSoratamiAscendant.java b/Mage.Sets/src/mage/sets/saviorsofkamigawa/ErayoSoratamiAscendant.java index 1df3ededb7..b98465fdfe 100644 --- a/Mage.Sets/src/mage/sets/saviorsofkamigawa/ErayoSoratamiAscendant.java +++ b/Mage.Sets/src/mage/sets/saviorsofkamigawa/ErayoSoratamiAscendant.java @@ -103,7 +103,7 @@ class ErayoSoratamiAscendantTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); return watcher != null && watcher.getAmountOfSpellsAllPlayersCastOnCurrentTurn() == 4; } @@ -152,7 +152,7 @@ class ErayosEssenceTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { if (game.getOpponents(getControllerId()).contains(event.getPlayerId())) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(event.getPlayerId()) == 1) { for (Effect effect : getEffects()) { effect.setTargetPointer(new FixedTarget(event.getTargetId())); diff --git a/Mage.Sets/src/mage/sets/seventhedition/Impatience.java b/Mage.Sets/src/mage/sets/seventhedition/Impatience.java index 1e3ae7e849..ea5e75ccf1 100644 --- a/Mage.Sets/src/mage/sets/seventhedition/Impatience.java +++ b/Mage.Sets/src/mage/sets/seventhedition/Impatience.java @@ -73,7 +73,7 @@ class ImpatienceCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); return watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(game.getActivePlayerId()) == 0; } diff --git a/Mage.Sets/src/mage/sets/worldwake/PermafrostTrap.java b/Mage.Sets/src/mage/sets/worldwake/PermafrostTrap.java index 0dd3fc178f..d0a626ad50 100644 --- a/Mage.Sets/src/mage/sets/worldwake/PermafrostTrap.java +++ b/Mage.Sets/src/mage/sets/worldwake/PermafrostTrap.java @@ -27,25 +27,20 @@ */ package mage.sets.worldwake; +import java.util.List; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; -import mage.ObjectColor; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; -import mage.abilities.costs.Cost; -import mage.abilities.costs.mana.ColoredManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; +import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.TapTargetEffect; import mage.cards.CardImpl; import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; -import mage.target.targetpointer.FixedTarget; -import mage.watchers.Watcher; +import mage.watchers.common.PermanentsEnteredBattlefieldWatcher; /** * @@ -58,16 +53,13 @@ public class PermafrostTrap extends CardImpl { this.expansionSetCode = "WWK"; this.subtype.add("Trap"); - // If an opponent had a green creature enter the battlefield under his or her control this turn, you may pay {U} rather than pay Permafrost Trap's mana cost. - this.getSpellAbility().addAlternativeCost(new PermafrostTrapAlternativeCost()); + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{U}"), PermafrostTrapCondition.getInstance()), new PermanentsEnteredBattlefieldWatcher()); // Tap up to two target creatures. Those creatures don't untap during their controller's next untap step. - TargetCreaturePermanent target = new TargetCreaturePermanent(0, 2); - this.getSpellAbility().addTarget(target); - this.getSpellAbility().addEffect(new PermafrostTrapEffect()); - - this.getSpellAbility().addWatcher(new PermafrostTrapWatcher()); + this.getSpellAbility().addEffect(new TapTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent(0, 2)); + this.getSpellAbility().addEffect(new DontUntapInControllersNextUntapStepTargetEffect()); } public PermafrostTrap(final PermafrostTrap card) { @@ -80,99 +72,34 @@ public class PermafrostTrap extends CardImpl { } } -class PermafrostTrapWatcher extends Watcher { +class PermafrostTrapCondition implements Condition { - public PermafrostTrapWatcher() { - super("PermafrostTrapWatcher", WatcherScope.GAME); - } + private static final PermafrostTrapCondition fInstance = new PermafrostTrapCondition(); - public PermafrostTrapWatcher(final PermafrostTrapWatcher watcher) { - super(watcher); - } - - @Override - public PermafrostTrapWatcher copy() { - return new PermafrostTrapWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - if (condition == true) { // no need to check - condition has already occured - return; - } - if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { - Permanent perm = game.getPermanent(event.getTargetId()); - if (perm.getCardType().contains(CardType.CREATURE) && perm.getColor(game).contains(ObjectColor.GREEN) && !perm.getControllerId().equals(controllerId)) { - condition = true; - } - } - } - - @Override - public void reset() { - super.reset(); - condition = false; - } -} - -class PermafrostTrapAlternativeCost extends AlternativeCostImpl { - - public PermafrostTrapAlternativeCost() { - super("you may pay {U} rather than pay Permafrost Trap's mana cost"); - this.add(new ColoredManaCost(ColoredManaSymbol.U)); - } - - public PermafrostTrapAlternativeCost(final PermafrostTrapAlternativeCost cost) { - super(cost); - } - - @Override - public PermafrostTrapAlternativeCost copy() { - return new PermafrostTrapAlternativeCost(this); - } - - @Override - public boolean isAvailable(Game game, Ability source) { - PermafrostTrapWatcher watcher = (PermafrostTrapWatcher) game.getState().getWatchers().get("PermafrostTrapWatcher"); - if (watcher != null && watcher.conditionMet()) { - return true; - } - return false; - } - - @Override - public String getText() { - return "If an opponent had a green creature enter the battlefield under his or her control this turn, you may pay {U} rather than pay Permafrost Trap's mana cost"; - } -} - -class PermafrostTrapEffect extends OneShotEffect { - - public PermafrostTrapEffect() { - super(Outcome.Detriment); - staticText = "Tap up to two target creatures. Those creatures don't untap during their controller's next untap step"; - } - - public PermafrostTrapEffect(final PermafrostTrapEffect effect) { - super(effect); + public static Condition getInstance() { + return fInstance; } @Override public boolean apply(Game game, Ability source) { - for (UUID targetId : this.targetPointer.getTargets(game, source)) { - Permanent creature = game.getPermanent(targetId); - if (creature != null) { - creature.tap(game); - DontUntapInControllersNextUntapStepTargetEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); - effect.setTargetPointer(new FixedTarget(targetId)); - game.addEffect(effect, source); + PermanentsEnteredBattlefieldWatcher watcher = (PermanentsEnteredBattlefieldWatcher) game.getState().getWatchers().get(PermanentsEnteredBattlefieldWatcher.class.getName()); + if (watcher != null) { + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + List permanents = watcher.getThisTurnEnteringPermanents(opponentId); + if (permanents != null) { + for (Permanent permanent : permanents) { + if (permanent.getCardType().contains(CardType.CREATURE) && permanent.getColor(game).isGreen()) { + return true; + } + } + } } } return false; } @Override - public PermafrostTrapEffect copy() { - return new PermafrostTrapEffect(this); + public String toString() { + return "If an opponent had a green creature enter the battlefield under his or her control this turn"; } } diff --git a/Mage.Sets/src/mage/sets/worldwake/RefractionTrap.java b/Mage.Sets/src/mage/sets/worldwake/RefractionTrap.java index 0b00752ac2..eca69e7e9b 100644 --- a/Mage.Sets/src/mage/sets/worldwake/RefractionTrap.java +++ b/Mage.Sets/src/mage/sets/worldwake/RefractionTrap.java @@ -27,13 +27,12 @@ */ package mage.sets.worldwake; -import java.util.HashSet; -import java.util.Set; +import java.util.List; import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; -import mage.abilities.costs.mana.ManaCost; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.PreventionEffectData; import mage.abilities.effects.PreventionEffectImpl; @@ -42,7 +41,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Rarity; -import mage.constants.WatcherScope; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; @@ -51,7 +49,7 @@ import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetSource; import mage.target.common.TargetCreatureOrPlayer; -import mage.watchers.Watcher; +import mage.watchers.common.SpellsCastWatcher; /** * @@ -65,13 +63,11 @@ public class RefractionTrap extends CardImpl { this.subtype.add("Trap"); // If an opponent cast a red instant or sorcery spell this turn, you may pay {W} rather than pay Refraction Trap's mana cost. - this.getSpellAbility().addAlternativeCost(new RefractionTrapAlternativeCost()); + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{W}"), RefractionTrapCondition.getInstance()), new SpellsCastWatcher()); // Prevent the next 3 damage that a source of your choice would deal to you and/or permanents you control this turn. If damage is prevented this way, Refraction Trap deals that much damage to target creature or player. this.getSpellAbility().addEffect(new RefractionTrapPreventDamageEffect(Duration.EndOfTurn, 3)); this.getSpellAbility().addTarget(new TargetCreatureOrPlayer()); - - this.getSpellAbility().addWatcher(new RefractionTrapWatcher()); } public RefractionTrap(final RefractionTrap card) { @@ -84,89 +80,43 @@ public class RefractionTrap extends CardImpl { } } -class RefractionTrapWatcher extends Watcher { +class RefractionTrapCondition implements Condition { - Set playersMetCondition = new HashSet<>(); + private static final RefractionTrapCondition fInstance = new RefractionTrapCondition(); - public RefractionTrapWatcher() { - super("RefractionTrapWatcher", WatcherScope.GAME); - } - - public RefractionTrapWatcher(final RefractionTrapWatcher watcher) { - super(watcher); - this.playersMetCondition.addAll(watcher.playersMetCondition); + public static Condition getInstance() { + return fInstance; } @Override - public RefractionTrapWatcher copy() { - return new RefractionTrapWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.SPELL_CAST) { - Spell spell = game.getStack().getSpell(event.getTargetId()); - if (spell.getColor(game).isRed()) { - if (spell.getCardType().contains(CardType.INSTANT) - || spell.getCardType().contains(CardType.SORCERY)) { - playersMetCondition.add(event.getPlayerId()); - } - } - } - } - - public boolean conditionMetForAnOpponent(UUID controllerId, Game game) { - Player controller = game.getPlayer(controllerId); - if (controller != null) { - for (UUID playerId : playersMetCondition) { - if (controller.hasOpponent(playerId, game)) { - return true; + public boolean apply(Game game, Ability source) { + SpellsCastWatcher watcher = (SpellsCastWatcher) game.getState().getWatchers().get(SpellsCastWatcher.class.getName()); + if (watcher != null) { + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + List spells = watcher.getSpellsCastThisTurn(opponentId); + if (spells != null) { + for (Spell spell : spells) { + if ((spell.getCardType().contains(CardType.SORCERY) || spell.getCardType().contains(CardType.INSTANT)) + && spell.getColor(game).isRed()) { + return true; + } + } } } } return false; - } @Override - public void reset() { - playersMetCondition.clear(); - super.reset(); - } -} - -class RefractionTrapAlternativeCost extends AlternativeCostImpl { - - public RefractionTrapAlternativeCost() { - super("You may pay {W} rather than pay Refraction Trap's mana cost"); - this.add(new ManaCostsImpl("{W}")); - } - - public RefractionTrapAlternativeCost(final RefractionTrapAlternativeCost cost) { - super(cost); - } - - @Override - public RefractionTrapAlternativeCost copy() { - return new RefractionTrapAlternativeCost(this); - } - - @Override - public boolean isAvailable(Game game, Ability source) { - RefractionTrapWatcher watcher = (RefractionTrapWatcher) game.getState().getWatchers().get("RefractionTrapWatcher"); - return watcher != null && watcher.conditionMetForAnOpponent(source.getControllerId(), game); - } - - @Override - public String getText() { - return "If an opponent cast a red instant or sorcery spell this turn, you may pay {W} rather than pay {this} mana cost"; + public String toString() { + return "If an opponent cast a red instant or sorcery spell this turn"; } } class RefractionTrapPreventDamageEffect extends PreventionEffectImpl { private final TargetSource target; - private int amount; + private final int amount; public RefractionTrapPreventDamageEffect(Duration duration, int amount) { super(duration, amount, false, false); diff --git a/Mage.Sets/src/mage/sets/worldwake/RicochetTrap.java b/Mage.Sets/src/mage/sets/worldwake/RicochetTrap.java index f831a87dc8..850fe754c1 100644 --- a/Mage.Sets/src/mage/sets/worldwake/RicochetTrap.java +++ b/Mage.Sets/src/mage/sets/worldwake/RicochetTrap.java @@ -27,26 +27,22 @@ */ package mage.sets.worldwake; +import java.util.List; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; -import mage.constants.WatcherScope; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; -import mage.abilities.costs.Cost; -import mage.abilities.costs.mana.ColoredManaCost; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; +import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.ChooseNewTargetsTargetEffect; import mage.cards.CardImpl; -import mage.constants.ColoredManaSymbol; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.filter.FilterSpell; import mage.filter.predicate.mageobject.NumberOfTargetsPredicate; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.stack.Spell; import mage.target.TargetSpell; -import mage.watchers.Watcher; +import mage.watchers.common.SpellsCastWatcher; /** * @@ -65,15 +61,14 @@ public class RicochetTrap extends CardImpl { this.expansionSetCode = "WWK"; this.subtype.add("Trap"); - // If an opponent cast a blue spell this turn, you may pay {R} rather than pay Ricochet Trap's mana cost. - this.getSpellAbility().addAlternativeCost(new RicochetTrapAlternativeCost()); + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{R}"), RicochetTrapCondition.getInstance())); // Change the target of target spell with a single target. this.getSpellAbility().addEffect(new ChooseNewTargetsTargetEffect(true, true)); this.getSpellAbility().addTarget(new TargetSpell(filter)); - this.getSpellAbility().addWatcher(new RicochetTrapWatcher()); + this.getSpellAbility().addWatcher(new SpellsCastWatcher()); } public RicochetTrap(final RicochetTrap card) { @@ -86,70 +81,34 @@ public class RicochetTrap extends CardImpl { } } -class RicochetTrapWatcher extends Watcher { +class RicochetTrapCondition implements Condition { - public RicochetTrapWatcher() { - super("RicochetTrapWatcher", WatcherScope.GAME); - } + private static final RicochetTrapCondition fInstance = new RicochetTrapCondition(); - public RicochetTrapWatcher(final RicochetTrapWatcher watcher) { - super(watcher); + public static Condition getInstance() { + return fInstance; } @Override - public RicochetTrapWatcher copy() { - return new RicochetTrapWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - if (condition == true) //no need to check - condition has already occured - { - return; - } - if (event.getType() == EventType.SPELL_CAST - && game.getOpponents(controllerId).contains(event.getPlayerId())) { - Spell spell = game.getStack().getSpell(event.getTargetId()); - if (spell.getColor(game).isBlue()) { - condition = true; + public boolean apply(Game game, Ability source) { + SpellsCastWatcher watcher = (SpellsCastWatcher) game.getState().getWatchers().get(SpellsCastWatcher.class.getName()); + if (watcher != null) { + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + List spells = watcher.getSpellsCastThisTurn(opponentId); + if (spells != null) { + for (Spell spell : spells) { + if (spell.getColor(game).isBlue()) { + return true; + } + } + } } } - } - - @Override - public void reset() { - super.reset(); - condition = false; - } -} - -class RicochetTrapAlternativeCost extends AlternativeCostImpl { - - public RicochetTrapAlternativeCost() { - super("You may pay {R} rather than pay Ricochet Trap's mana cost"); - this.add(new ColoredManaCost(ColoredManaSymbol.R)); - } - - public RicochetTrapAlternativeCost(final RicochetTrapAlternativeCost cost) { - super(cost); - } - - @Override - public RicochetTrapAlternativeCost copy() { - return new RicochetTrapAlternativeCost(this); - } - - @Override - public boolean isAvailable(Game game, Ability source) { - RicochetTrapWatcher watcher = (RicochetTrapWatcher) game.getState().getWatchers().get("RicochetTrapWatcher"); - if (watcher != null && watcher.conditionMet()) { - return true; - } return false; } @Override - public String getText() { - return "If an opponent cast a blue spell this turn, you may pay {R} rather than pay {this} mana cost"; + public String toString() { + return "If an opponent cast a blue spell this turn"; } } diff --git a/Mage.Sets/src/mage/sets/worldwake/SlingbowTrap.java b/Mage.Sets/src/mage/sets/worldwake/SlingbowTrap.java index 6d3fa2bcee..f129921337 100644 --- a/Mage.Sets/src/mage/sets/worldwake/SlingbowTrap.java +++ b/Mage.Sets/src/mage/sets/worldwake/SlingbowTrap.java @@ -27,17 +27,16 @@ */ package mage.sets.worldwake; -import java.util.List; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Rarity; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; -import mage.abilities.costs.Cost; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.filter.common.FilterAttackingCreature; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.game.Game; @@ -61,9 +60,8 @@ public class SlingbowTrap extends CardImpl { this.expansionSetCode = "WWK"; this.subtype.add("Trap"); - // If a black creature with flying is attacking, you may pay {G} rather than pay Slingbow Trap's mana cost. - this.getSpellAbility().addAlternativeCost(new SlingbowTrapAlternativeCost()); + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{G}"), SlingbowTrapCondition.getInstance())); // Destroy target attacking creature with flying. this.getSpellAbility().addEffect(new DestroyTargetEffect()); @@ -80,37 +78,29 @@ public class SlingbowTrap extends CardImpl { } } -class SlingbowTrapAlternativeCost extends AlternativeCostImpl { +class SlingbowTrapCondition implements Condition { - public SlingbowTrapAlternativeCost() { - super("you may pay {G} rather than pay {this}'s mana cost"); - this.add(new ManaCostsImpl("{G}")); - } + private static final SlingbowTrapCondition fInstance = new SlingbowTrapCondition(); - public SlingbowTrapAlternativeCost(final SlingbowTrapAlternativeCost cost) { - super(cost); + public static Condition getInstance() { + return fInstance; } @Override - public SlingbowTrapAlternativeCost copy() { - return new SlingbowTrapAlternativeCost(this); - } - - @Override - public boolean isAvailable(Game game, Ability source) { - List attackers = game.getCombat().getAttackers(); - for (UUID creatureId : attackers) { - Permanent creature = game.getPermanent(creatureId); - if (creature.getColor(game).isBlack() - && creature.getAbilities().contains(FlyingAbility.getInstance())) { - return true; + public boolean apply(Game game, Ability source) { + for (UUID attackingCreatureId : game.getCombat().getAttackers()) { + Permanent attackingCreature = game.getPermanent(attackingCreatureId); + if (attackingCreature != null) { + if (attackingCreature.getColor(game).isBlack() && attackingCreature.hasAbility(FlyingAbility.getInstance().getId(), game)) { + return true; + } } } return false; } @Override - public String getText() { - return "If a black creature with flying is attacking, you may pay {G} rather than pay Slingbow Trap's mana cost"; + public String toString() { + return "If a black creature with flying is attacking"; } } diff --git a/Mage.Sets/src/mage/sets/zendikar/ArrowVolleyTrap.java b/Mage.Sets/src/mage/sets/zendikar/ArrowVolleyTrap.java index a83b34aa21..eafbf913ac 100644 --- a/Mage.Sets/src/mage/sets/zendikar/ArrowVolleyTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/ArrowVolleyTrap.java @@ -29,7 +29,8 @@ package mage.sets.zendikar; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageMultiEffect; import mage.cards.CardImpl; @@ -50,7 +51,7 @@ public class ArrowVolleyTrap extends CardImpl { this.subtype.add("Trap"); // If four or more creatures are attacking, you may pay {1}{W} rather than pay Arrow Volley Trap's mana cost. - this.getSpellAbility().addAlternativeCost(new ArrowVolleyTrapAlternativeCost()); + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{1}{W}"), ArrowVolleyTrapCondition.getInstance())); // Arrow Volley Trap deals 5 damage divided as you choose among any number of target attacking creatures. this.getSpellAbility().addEffect(new DamageMultiEffect(5)); @@ -68,29 +69,21 @@ public class ArrowVolleyTrap extends CardImpl { } } -class ArrowVolleyTrapAlternativeCost extends AlternativeCostImpl { +class ArrowVolleyTrapCondition implements Condition { - public ArrowVolleyTrapAlternativeCost() { - super("you may pay {1}{W} rather than pay Arrow Volley Trap's mana cost"); - this.add(new ManaCostsImpl("{1}{W}")); - } + private static final ArrowVolleyTrapCondition fInstance = new ArrowVolleyTrapCondition(); - public ArrowVolleyTrapAlternativeCost(final ArrowVolleyTrapAlternativeCost cost) { - super(cost); + public static Condition getInstance() { + return fInstance; } @Override - public ArrowVolleyTrapAlternativeCost copy() { - return new ArrowVolleyTrapAlternativeCost(this); - } - - @Override - public boolean isAvailable(Game game, Ability source) { + public boolean apply(Game game, Ability source) { return game.getCombat().getAttackers().size() > 3; } @Override - public String getText() { - return "If four or more creatures are attacking, you may pay {1}{W} rather than pay {this}'s mana cost"; + public String toString() { + return "If four or more creatures are attacking"; } } diff --git a/Mage.Sets/src/mage/sets/zendikar/BalothCageTrap.java b/Mage.Sets/src/mage/sets/zendikar/BalothCageTrap.java index 492af7f4d4..47fff01195 100644 --- a/Mage.Sets/src/mage/sets/zendikar/BalothCageTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/BalothCageTrap.java @@ -27,20 +27,20 @@ */ package mage.sets.zendikar; +import java.util.List; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.constants.WatcherScope; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.game.permanent.token.BeastToken2; -import mage.watchers.Watcher; +import mage.watchers.common.PermanentsEnteredBattlefieldWatcher; /** * @@ -54,8 +54,7 @@ public class BalothCageTrap extends CardImpl { this.subtype.add("Trap"); // If an opponent had an artifact enter the battlefield under his or her control this turn, you may pay {1}{G} rather than pay Baloth Cage Trap's mana cost. - this.getSpellAbility().addAlternativeCost(new BalothCageTrapAlternativeCost()); - this.getSpellAbility().addWatcher(new BalothCageTrapWatcher()); + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{1}{G}"), BalothCageTrapCondition.getInstance()), new PermanentsEnteredBattlefieldWatcher()); // Put a 4/4 green Beast creature token onto the battlefield. this.getSpellAbility().addEffect(new CreateTokenEffect(new BeastToken2())); @@ -71,68 +70,34 @@ public class BalothCageTrap extends CardImpl { } } -class BalothCageTrapWatcher extends Watcher { +class BalothCageTrapCondition implements Condition { - public BalothCageTrapWatcher() { - super("BalothCageTrapWatcher", WatcherScope.GAME); - } + private static final BalothCageTrapCondition fInstance = new BalothCageTrapCondition(); - public BalothCageTrapWatcher(final BalothCageTrapWatcher watcher) { - super(watcher); + public static Condition getInstance() { + return fInstance; } @Override - public BalothCageTrapWatcher copy() { - return new BalothCageTrapWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - if (condition == true) { // no need to check - condition has already occured - return; - } - if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { - Permanent perm = game.getPermanent(event.getTargetId()); - if (perm.getCardType().contains(CardType.ARTIFACT) && !perm.getControllerId().equals(controllerId)) { - condition = true; + public boolean apply(Game game, Ability source) { + PermanentsEnteredBattlefieldWatcher watcher = (PermanentsEnteredBattlefieldWatcher) game.getState().getWatchers().get(PermanentsEnteredBattlefieldWatcher.class.getName()); + if (watcher != null) { + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + List permanents = watcher.getThisTurnEnteringPermanents(opponentId); + if (permanents != null) { + for (Permanent permanent : permanents) { + if (permanent.getCardType().contains(CardType.ARTIFACT)) { + return true; + } + } + } } } - } - - @Override - public void reset() { - super.reset(); - condition = false; - } -} - -class BalothCageTrapAlternativeCost extends AlternativeCostImpl { - - public BalothCageTrapAlternativeCost() { - super("you may pay {1}{G} rather than pay Baloth Cage Trap's mana cost"); - this.add(new ManaCostsImpl("{1}{G}")); - } - - public BalothCageTrapAlternativeCost(final BalothCageTrapAlternativeCost cost) { - super(cost); - } - - @Override - public BalothCageTrapAlternativeCost copy() { - return new BalothCageTrapAlternativeCost(this); - } - - @Override - public boolean isAvailable(Game game, Ability source) { - BalothCageTrapWatcher watcher = (BalothCageTrapWatcher) game.getState().getWatchers().get("BalothCageTrapWatcher"); - if (watcher != null && watcher.conditionMet()) { - return true; - } return false; } @Override - public String getText() { - return "If an opponent had an artifact enter the battlefield under his or her control this turn, you may pay {1}{G} rather than pay Baloth Cage Trap's mana cost"; + public String toString() { + return "If an opponent had an artifact enter the battlefield under his or her control this turn"; } } diff --git a/Mage.Sets/src/mage/sets/zendikar/InfernoTrap.java b/Mage.Sets/src/mage/sets/zendikar/InfernoTrap.java index e9e07a06bb..b38f8e7b0f 100644 --- a/Mage.Sets/src/mage/sets/zendikar/InfernoTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/InfernoTrap.java @@ -28,15 +28,14 @@ package mage.sets.zendikar; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.abilities.Ability; import mage.abilities.costs.AlternativeCostImpl; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageTargetEffect; import mage.cards.Card; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.constants.WatcherScope; import mage.game.Game; import mage.game.events.GameEvent; @@ -54,11 +53,10 @@ public class InfernoTrap extends CardImpl { this.expansionSetCode = "ZEN"; this.subtype.add("Trap"); - // If you've been dealt damage by two or more creatures this turn, you may pay {R} rather than pay Inferno Trap's mana cost. this.getSpellAbility().addAlternativeCost(new InfernoTrapAlternativeCost()); this.getSpellAbility().addWatcher(new ControllerDamagedByCreatureWatcher()); - + // Inferno Trap deals 4 damage to target creature. this.getSpellAbility().addEffect(new DamageTargetEffect(4)); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); diff --git a/Mage.Sets/src/mage/sets/zendikar/LavaballTrap.java b/Mage.Sets/src/mage/sets/zendikar/LavaballTrap.java index 058b40ffe2..a51466ab87 100644 --- a/Mage.Sets/src/mage/sets/zendikar/LavaballTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/LavaballTrap.java @@ -27,26 +27,23 @@ */ package mage.sets.zendikar; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; -import mage.abilities.costs.Cost; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DamageAllEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.constants.WatcherScope; import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterLandPermanent; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.common.TargetLandPermanent; -import mage.watchers.Watcher; +import mage.watchers.common.PermanentsEnteredBattlefieldWatcher; /** * @@ -60,8 +57,7 @@ public class LavaballTrap extends CardImpl { this.subtype.add("Trap"); // If an opponent had two or more lands enter the battlefield under his or her control this turn, you may pay {3}{R}{R} rather than pay Lavaball Trap's mana cost. - this.getSpellAbility().addAlternativeCost(new LavaballTrapAlternativeCost()); - this.getSpellAbility().addWatcher(new LavaballTrapWatcher()); + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{3}{R}{R}"), LavaballTrapCondition.getInstance()), new PermanentsEnteredBattlefieldWatcher()); // Destroy two target lands. Lavaball Trap deals 4 damage to each creature. this.getSpellAbility().addEffect(new DestroyTargetEffect()); @@ -80,87 +76,38 @@ public class LavaballTrap extends CardImpl { } } -class LavaballTrapWatcher extends Watcher { +class LavaballTrapCondition implements Condition { - private Map amountOfLandsPlayedThisTurn = new HashMap<>(); + private static final LavaballTrapCondition fInstance = new LavaballTrapCondition(); - public LavaballTrapWatcher() { - super("LavaballTrapWatcher", WatcherScope.GAME); - } - - public LavaballTrapWatcher(final LavaballTrapWatcher watcher) { - super(watcher); - for (Map.Entry entry : watcher.amountOfLandsPlayedThisTurn.entrySet()) { - amountOfLandsPlayedThisTurn.put(entry.getKey(), entry.getValue()); - } + public static Condition getInstance() { + return fInstance; } @Override - public LavaballTrapWatcher copy() { - return new LavaballTrapWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { - Permanent perm = game.getPermanent(event.getTargetId()); - if (perm.getCardType().contains(CardType.LAND)) { - Integer amount = amountOfLandsPlayedThisTurn.get(perm.getControllerId()); - if (amount == null) { - amount = 1; - } else { - ++amount; + public boolean apply(Game game, Ability source) { + PermanentsEnteredBattlefieldWatcher watcher = (PermanentsEnteredBattlefieldWatcher) game.getState().getWatchers().get(PermanentsEnteredBattlefieldWatcher.class.getName()); + if (watcher != null) { + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + List permanents = watcher.getThisTurnEnteringPermanents(opponentId); + if (permanents != null) { + int count = 0; + for (Permanent permanent : permanents) { + if (permanent.getCardType().contains(CardType.LAND)) { + count++; + if (count == 2) { + return true; + } + } + } } - amountOfLandsPlayedThisTurn.put(perm.getControllerId(), amount); } } - } - - public int maxLandsAnOpponentPlayedThisTurn(UUID playerId, Game game) { - int maxLands = 0; - for (UUID opponentId : game.getOpponents(playerId)) { - Integer amount = amountOfLandsPlayedThisTurn.get(opponentId); - if (amount != null && amount > maxLands) { - maxLands = amount; - } - } - return maxLands; - } - - @Override - public void reset() { - super.reset(); - amountOfLandsPlayedThisTurn.clear(); - } -} - -class LavaballTrapAlternativeCost extends AlternativeCostImpl { - - public LavaballTrapAlternativeCost() { - super("you may pay {3}{R}{R} rather than pay Lavaball Trap's mana cost"); - this.add(new ManaCostsImpl("{3}{R}{R}")); - } - - public LavaballTrapAlternativeCost(final LavaballTrapAlternativeCost cost) { - super(cost); - } - - @Override - public LavaballTrapAlternativeCost copy() { - return new LavaballTrapAlternativeCost(this); - } - - @Override - public boolean isAvailable(Game game, Ability source) { - LavaballTrapWatcher watcher = (LavaballTrapWatcher) game.getState().getWatchers().get("LavaballTrapWatcher"); - if (watcher != null && watcher.maxLandsAnOpponentPlayedThisTurn(source.getControllerId(), game) > 1) { - return true; - } return false; } @Override - public String getText() { - return "If an opponent had two or more lands enter the battlefield under his or her control this turn, you may pay {3}{R}{R} rather than pay Lavaball Trap's mana cost"; + public String toString() { + return "If an opponent had two or more lands enter the battlefield under his or her control this turn"; } } diff --git a/Mage.Sets/src/mage/sets/zendikar/LethargyTrap.java b/Mage.Sets/src/mage/sets/zendikar/LethargyTrap.java index a52dc64ab3..8dce3da894 100644 --- a/Mage.Sets/src/mage/sets/zendikar/LethargyTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/LethargyTrap.java @@ -28,16 +28,15 @@ package mage.sets.zendikar; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; -import mage.abilities.costs.Cost; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.continuous.BoostAllEffect; import mage.cards.CardImpl; +import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.Rarity; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.AttackingPredicate; import mage.game.Game; @@ -47,25 +46,24 @@ import mage.game.Game; * @author jeffwadsworth */ public class LethargyTrap extends CardImpl { - + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("attacking creatures"); static { filter.add(new AttackingPredicate()); } - + public LethargyTrap(UUID ownerId) { super(ownerId, 51, "Lethargy Trap", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{3}{U}"); this.expansionSetCode = "ZEN"; this.subtype.add("Trap"); - // If three or more creatures are attacking, you may pay {U} rather than pay Lethargy Trap's mana cost. - this.getSpellAbility().addAlternativeCost(new LethargyTrapAlternativeCost()); - + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{U}"), LethargyTrapCondition.getInstance())); + // Attacking creatures get -3/-0 until end of turn. this.getSpellAbility().addEffect(new BoostAllEffect(-3, 0, Duration.EndOfTurn, filter, false)); - + } public LethargyTrap(final LethargyTrap card) { @@ -78,32 +76,21 @@ public class LethargyTrap extends CardImpl { } } -class LethargyTrapAlternativeCost extends AlternativeCostImpl { +class LethargyTrapCondition implements Condition { - public LethargyTrapAlternativeCost() { - super("you may pay {U} rather than pay Lethargy Trap's mana cost"); - this.add(new ManaCostsImpl("{U}")); - } + private static final LethargyTrapCondition fInstance = new LethargyTrapCondition(); - public LethargyTrapAlternativeCost(final LethargyTrapAlternativeCost cost) { - super(cost); + public static Condition getInstance() { + return fInstance; } @Override - public LethargyTrapAlternativeCost copy() { - return new LethargyTrapAlternativeCost(this); + public boolean apply(Game game, Ability source) { + return game.getCombat().getAttackers().size() > 2; } @Override - public boolean isAvailable(Game game, Ability source) { - if (game.getCombat().getAttackers().size() >= 3) { - return true; - } - return false; + public String toString() { + return "If three or more creatures are attacking"; } - - @Override - public String getText() { - return "If three or more creatures are attacking, you may pay {U} rather than pay Lethargy Trap's mana cost"; - } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/zendikar/MindbreakTrap.java b/Mage.Sets/src/mage/sets/zendikar/MindbreakTrap.java index 2378fd3698..258a92dbb8 100644 --- a/Mage.Sets/src/mage/sets/zendikar/MindbreakTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/MindbreakTrap.java @@ -27,28 +27,19 @@ */ package mage.sets.zendikar; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; import mage.cards.CardImpl; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.Rarity; -import mage.constants.WatcherScope; -import mage.constants.Zone; import mage.filter.FilterSpell; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; -import mage.game.stack.Spell; -import mage.players.Player; import mage.target.TargetSpell; -import mage.watchers.Watcher; +import mage.watchers.common.CastSpellLastTurnWatcher; /** * @@ -63,14 +54,12 @@ public class MindbreakTrap extends CardImpl { this.expansionSetCode = "ZEN"; this.subtype.add("Trap"); - // If an opponent cast three or more spells this turn, you may pay {0} rather than pay Mindbreak Trap's mana cost. - this.getSpellAbility().addAlternativeCost( - new MindbreakTrapAlternativeCost()); - this.getSpellAbility().addWatcher(new MindbreakTrapWatcher()); + this.addAbility(new AlternativeCostSourceAbility(new GenericManaCost(0), MindbreakTrapCondition.getInstance())); + // Exile any number of target spells. this.getSpellAbility().addTarget(new TargetSpell(0, Integer.MAX_VALUE, filter)); - this.getSpellAbility().addEffect(new MindbreakEffect()); + this.getSpellAbility().addEffect(new ExileTargetEffect("Exile any number of target spells")); } public MindbreakTrap(final MindbreakTrap card) { @@ -83,116 +72,30 @@ public class MindbreakTrap extends CardImpl { } } -class MindbreakTrapWatcher extends Watcher { +class MindbreakTrapCondition implements Condition { - private Map counts = new HashMap<>(); + private static final MindbreakTrapCondition fInstance = new MindbreakTrapCondition(); - public MindbreakTrapWatcher() { - super("opponent cast three or more spells", WatcherScope.PLAYER); - } - - public MindbreakTrapWatcher(final MindbreakTrapWatcher watcher) { - super(watcher); - for (Entry entry: watcher.counts.entrySet()) { - counts.put(entry.getKey(), entry.getValue()); - } - } - - @Override - public MindbreakTrapWatcher copy() { - return new MindbreakTrapWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - if (condition == true) { // no need to check - condition has already occured - return; - } - if (event.getType() == EventType.SPELL_CAST - && game.getOpponents(controllerId).contains(event.getPlayerId())) { - int count = 1; - if (counts.containsKey(event.getPlayerId())) { - count += counts.get(event.getPlayerId()); - if (count >= 3) { - condition = true; - } - } - counts.put(event.getPlayerId(), count); - } - } - - @Override - public void reset() { - super.reset(); - counts.clear(); - } - -} - -class MindbreakTrapAlternativeCost extends AlternativeCostImpl { - - public MindbreakTrapAlternativeCost() { - super("you may pay {0} rather than pay {this}'s mana cost"); - this.add(new GenericManaCost(0)); - } - - public MindbreakTrapAlternativeCost(final MindbreakTrapAlternativeCost cost) { - super(cost); - } - - @Override - public MindbreakTrapAlternativeCost copy() { - return new MindbreakTrapAlternativeCost(this); - } - - @Override - public boolean isAvailable(Game game, Ability source) { - Watcher watcher = game.getState().getWatchers().get("opponent cast three or more spells", source.getControllerId()); - if (watcher != null && watcher.conditionMet()) { - return true; - } - return false; - } - - @Override - public String getText() { - return "If an opponent cast three or more spells this turn, you may pay {0} rather than pay {this}'s mana cost"; - } -} - -class MindbreakEffect extends OneShotEffect{ - - MindbreakEffect(MindbreakEffect effect) { - super(effect); - } - - MindbreakEffect() { - super(Outcome.Exile); - staticText = "Exile any number of target spells"; + public static Condition getInstance() { + return fInstance; } @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - int affectedTargets = 0; - if (targetPointer.getTargets(game, source).size() > 0) { - for (UUID spellId : targetPointer.getTargets(game, source)) { - Spell spell = game.getStack().getSpell(spellId); - if (spell != null) { - controller.moveCardToExileWithInfo(spell, null, "", source.getSourceId(), game, Zone.STACK, true); - affectedTargets++; - } + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); + if (watcher != null) { + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + if (watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(opponentId) > 2) { + return true; } } - return affectedTargets > 0; } return false; } @Override - public MindbreakEffect copy() { - return new MindbreakEffect(this); + public String toString() { + return "If an opponent cast three or more spells this turn"; } } diff --git a/Mage.Sets/src/mage/sets/zendikar/PitfallTrap.java b/Mage.Sets/src/mage/sets/zendikar/PitfallTrap.java index 7ba0ab5b4d..7e446a6796 100644 --- a/Mage.Sets/src/mage/sets/zendikar/PitfallTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/PitfallTrap.java @@ -29,7 +29,8 @@ package mage.sets.zendikar; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.keyword.FlyingAbility; @@ -60,7 +61,7 @@ public class PitfallTrap extends CardImpl { this.subtype.add("Trap"); // If exactly one creature is attacking, you may pay {W} rather than pay Pitfall Trap's mana cost. - this.getSpellAbility().addAlternativeCost(new PitfallTrapAlternativeCost()); + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{W}"), PitfallTrapCondition.getInstance())); // Destroy target attacking creature without flying. this.getSpellAbility().addEffect(new DestroyTargetEffect()); @@ -77,32 +78,21 @@ public class PitfallTrap extends CardImpl { } } -class PitfallTrapAlternativeCost extends AlternativeCostImpl { +class PitfallTrapCondition implements Condition { - public PitfallTrapAlternativeCost() { - super("you may pay {W} rather than pay Pitfall Trap's mana cost"); - this.add(new ManaCostsImpl("{W}")); - } + private static final PitfallTrapCondition fInstance = new PitfallTrapCondition(); - public PitfallTrapAlternativeCost(final PitfallTrapAlternativeCost cost) { - super(cost); + public static Condition getInstance() { + return fInstance; } @Override - public PitfallTrapAlternativeCost copy() { - return new PitfallTrapAlternativeCost(this); + public boolean apply(Game game, Ability source) { + return game.getCombat().getAttackers().size() == 1; } @Override - public boolean isAvailable(Game game, Ability source) { - if (game.getCombat().getAttackers().size() == 1) { - return true; - } - return false; - } - - @Override - public String getText() { - return "If exactly one creature is attacking, you may pay {W} rather than pay Pitfall Trap's mana cost"; + public String toString() { + return "If exactly one creature is attacking"; } } diff --git a/Mage.Sets/src/mage/sets/zendikar/SummoningTrap.java b/Mage.Sets/src/mage/sets/zendikar/SummoningTrap.java index a9fcc4e195..57c869c4ba 100644 --- a/Mage.Sets/src/mage/sets/zendikar/SummoningTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/SummoningTrap.java @@ -63,15 +63,10 @@ public class SummoningTrap extends CardImpl { this.expansionSetCode = "ZEN"; this.subtype.add("Trap"); - // If a creature spell you cast this turn was countered by a spell or - // ability an opponent controlled, you may pay {0} rather than pay - // Summoning Trap's mana cost. - this.getSpellAbility().addAlternativeCost( - new SummoningTrapAlternativeCost()); + // If a creature spell you cast this turn was countered by a spell or ability an opponent controlled, you may pay {0} rather than pay Summoning Trap's mana cost. + this.getSpellAbility().addAlternativeCost(new SummoningTrapAlternativeCost()); this.getSpellAbility().addWatcher(new SummoningTrapWatcher()); - // Look at the top seven cards of your library. You may put a creature - // card from among them onto the battlefield. Put the rest on the bottom - // of your library in any order. + // Look at the top seven cards of your library. You may put a creature card from among them onto the battlefield. Put the rest on the bottom of your library in any order. this.getSpellAbility().addEffect(new SummoningTrapEffect()); } diff --git a/Mage.Sets/src/mage/sets/zendikar/WhiplashTrap.java b/Mage.Sets/src/mage/sets/zendikar/WhiplashTrap.java index fc84669fdf..8778efd3f3 100644 --- a/Mage.Sets/src/mage/sets/zendikar/WhiplashTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/WhiplashTrap.java @@ -27,23 +27,20 @@ */ package mage.sets.zendikar; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.ReturnToHandTargetEffect; import mage.cards.CardImpl; -import mage.constants.WatcherScope; +import mage.constants.CardType; +import mage.constants.Rarity; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; -import mage.watchers.Watcher; +import mage.watchers.common.PermanentsEnteredBattlefieldWatcher; /** * @@ -56,15 +53,13 @@ public class WhiplashTrap extends CardImpl { this.expansionSetCode = "ZEN"; this.subtype.add("Trap"); - // If an opponent had two or more creatures enter the battlefield under his or her control this turn, you may pay {U} rather than pay Whiplash Trap's mana cost. - this.getSpellAbility().addAlternativeCost(new WhiplashAlternativeCost()); - this.getSpellAbility().addWatcher(new WhiplashTrapWatcher()); + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{U}"), WhiplashTrapCondition.getInstance()), new PermanentsEnteredBattlefieldWatcher()); // Return two target creatures to their owners' hands. this.getSpellAbility().addEffect(new ReturnToHandTargetEffect()); this.getSpellAbility().addTarget(new TargetCreaturePermanent(2)); - + } public WhiplashTrap(final WhiplashTrap card) { @@ -77,87 +72,38 @@ public class WhiplashTrap extends CardImpl { } } -class WhiplashTrapWatcher extends Watcher { +class WhiplashTrapCondition implements Condition { - private Map amountOfCreaturesPlayedThisTurn = new HashMap<>(); + private static final WhiplashTrapCondition fInstance = new WhiplashTrapCondition(); - public WhiplashTrapWatcher() { - super("WhiplashTrapWatcher", WatcherScope.GAME); - } - - public WhiplashTrapWatcher(final WhiplashTrapWatcher watcher) { - super(watcher); - for (Map.Entry entry : watcher.amountOfCreaturesPlayedThisTurn.entrySet()) { - amountOfCreaturesPlayedThisTurn.put(entry.getKey(), entry.getValue()); - } + public static Condition getInstance() { + return fInstance; } @Override - public WhiplashTrapWatcher copy() { - return new WhiplashTrapWatcher(this); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { - Permanent perm = game.getPermanent(event.getTargetId()); - if (perm.getCardType().contains(CardType.CREATURE)) { - Integer amount = amountOfCreaturesPlayedThisTurn.get(perm.getControllerId()); - if (amount == null) { - amount = 1; - } else { - ++amount; + public boolean apply(Game game, Ability source) { + PermanentsEnteredBattlefieldWatcher watcher = (PermanentsEnteredBattlefieldWatcher) game.getState().getWatchers().get(PermanentsEnteredBattlefieldWatcher.BASIC_KEY); + if (watcher != null) { + for (UUID opponentId : game.getOpponents(source.getControllerId())) { + List permanents = watcher.getThisTurnEnteringPermanents(opponentId); + if (permanents != null) { + int count = 0; + for (Permanent permanent : permanents) { + if (permanent.getCardType().contains(CardType.CREATURE)) { + count++; + if (count == 2) { + return true; + } + } + } } - amountOfCreaturesPlayedThisTurn.put(perm.getControllerId(), amount); } } - } - - public int maxCreaturesAnOpponentPlayedThisTurn(UUID playerId, Game game) { - int maxCreatures = 0; - for (UUID opponentId : game.getOpponents(playerId)) { - Integer amount = amountOfCreaturesPlayedThisTurn.get(opponentId); - if (amount != null && amount > maxCreatures) { - maxCreatures = amount; - } - } - return maxCreatures; - } - - @Override - public void reset() { - super.reset(); - amountOfCreaturesPlayedThisTurn.clear(); - } -} - -class WhiplashAlternativeCost extends AlternativeCostImpl { - - public WhiplashAlternativeCost() { - super("you may pay {U} rather than pay Whiplash Trap's mana cost"); - this.add(new ManaCostsImpl("{U}")); - } - - public WhiplashAlternativeCost(final WhiplashAlternativeCost cost) { - super(cost); - } - - @Override - public WhiplashAlternativeCost copy() { - return new WhiplashAlternativeCost(this); - } - - @Override - public boolean isAvailable(Game game, Ability source) { - WhiplashTrapWatcher watcher = (WhiplashTrapWatcher) game.getState().getWatchers().get("WhiplashTrapWatcher"); - if (watcher != null && watcher.maxCreaturesAnOpponentPlayedThisTurn(source.getControllerId(), game) >= 2) { - return true; - } return false; } @Override - public String getText() { - return "If an opponent had two or more creatures enter the battlefield under his or her control this turn, you may pay {U} rather than pay {this}'s mana cost"; + public String toString() { + return "If an opponent had two or more creatures enter the battlefield under his or her control this turn"; } -} \ No newline at end of file +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/DefenseGridTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/DefenseGridTest.java new file mode 100644 index 0000000000..493aba19e6 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/modification/DefenseGridTest.java @@ -0,0 +1,78 @@ +/* + * 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.cost.modification; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class DefenseGridTest extends CardTestPlayerBase { + + /** + * Defense Grid vs Mindbreak Trap Not sure how this is coded, but Mindbreak + * Trap should still cost 3 more (0+3=3). + * + */ + @Test + public void testCostIncrease() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + addCard(Zone.HAND, playerA, "Lightning Bolt", 3); + + // Each spell costs {3} more to cast except during its controller's turn. + addCard(Zone.BATTLEFIELD, playerA, "Defense Grid"); + + addCard(Zone.BATTLEFIELD, playerB, "Island", 3); + // If an opponent cast three or more spells this turn, you may pay {0} rather than pay Mindbreak Trap's mana cost. + // Exile any number of target spells. + addCard(Zone.HAND, playerB, "Mindbreak Trap"); // {2}{U}{U} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Mindbreak Trap", "Lightning Bolt^Lightning Bolt^Lightning Bolt"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertExileCount("Lightning Bolt", 3); + assertGraveyardCount(playerB, "Mindbreak Trap", 1); + + assertTappedCount("Island", true, 3); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 588dd63444..6ab3d11fbc 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -300,7 +300,7 @@ public class TestPlayer implements Player { if (selectedMode == null) { throw new UnsupportedOperationException("Mode not available for " + ability.toString()); } - if (selectedMode.getTargets().size() == 0) { + if (selectedMode.getTargets().isEmpty()) { throw new AssertionError("Ability has no targets. " + ability.toString()); } if (index >= selectedMode.getTargets().size()) { @@ -319,27 +319,29 @@ public class TestPlayer implements Player { } } else { for (UUID id : currentTarget.possibleTargets(ability.getSourceId(), ability.getControllerId(), game)) { - MageObject object = game.getObject(id); - if (object != null - && ((!targetName.isEmpty() && object.getName().startsWith(targetName)) || (targetName.isEmpty() && object.getName().isEmpty()))) { - if (currentTarget.getNumberOfTargets() == 1) { - currentTarget.clearChosen(); + if (!currentTarget.getTargets().contains(id)) { + MageObject object = game.getObject(id); + if (object != null + && ((!targetName.isEmpty() && object.getName().startsWith(targetName)) || (targetName.isEmpty() && object.getName().isEmpty()))) { + if (currentTarget.getNumberOfTargets() == 1) { + currentTarget.clearChosen(); + } + if (currentTarget instanceof TargetCreaturePermanentAmount) { + // supports only to set the complete amount to one target + TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget; + targetAmount.setAmount(ability, game); + int amount = targetAmount.getAmountRemaining(); + targetAmount.addTarget(id, amount, ability, game); + targetsSet++; + } else { + currentTarget.addTarget(id, ability, game); + targetsSet++; + } + if (currentTarget.getTargets().size() == currentTarget.getMaxNumberOfTargets()) { + index++; + } + break; } - if (currentTarget instanceof TargetCreaturePermanentAmount) { - // supports only to set the complete amount to one target - TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget; - targetAmount.setAmount(ability, game); - int amount = targetAmount.getAmountRemaining(); - targetAmount.addTarget(id, amount, ability, game); - targetsSet++; - } else { - currentTarget.addTarget(id, ability, game); - targetsSet++; - } - if (currentTarget.getTargets().size() == currentTarget.getMaxNumberOfTargets()) { - index++; - } - break; } } } diff --git a/Mage/src/main/java/mage/abilities/condition/common/NoSpellsWereCastLastTurnCondition.java b/Mage/src/main/java/mage/abilities/condition/common/NoSpellsWereCastLastTurnCondition.java index 61d78f90ed..254921948a 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/NoSpellsWereCastLastTurnCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/NoSpellsWereCastLastTurnCondition.java @@ -45,7 +45,7 @@ public class NoSpellsWereCastLastTurnCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); // if any player cast spell, return false for (Integer count : watcher.getAmountOfSpellsCastOnPrevTurn().values()) { if (count > 0) { diff --git a/Mage/src/main/java/mage/abilities/condition/common/TwoOrMoreSpellsWereCastLastTurnCondition.java b/Mage/src/main/java/mage/abilities/condition/common/TwoOrMoreSpellsWereCastLastTurnCondition.java index c716ebf8a3..7d617df0c7 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/TwoOrMoreSpellsWereCastLastTurnCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/TwoOrMoreSpellsWereCastLastTurnCondition.java @@ -45,7 +45,7 @@ public class TwoOrMoreSpellsWereCastLastTurnCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); // if any player cast more than two spells, return true for (Integer count : watcher.getAmountOfSpellsCastOnPrevTurn().values()) { if (count >= 2) { diff --git a/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java b/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java index be4893b1b4..0b622c477b 100644 --- a/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java +++ b/Mage/src/main/java/mage/abilities/costs/AlternativeCostSourceAbility.java @@ -120,7 +120,8 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter } private AlternativeCost2 convertToAlternativeCost(Cost cost) { - return cost != null ? new AlternativeCost2Impl(null, cost.getText(), cost) : null; + //return cost != null ? new AlternativeCost2Impl(null, cost.getText(), cost) : null; + return cost != null ? new AlternativeCost2Impl(null, "", cost) : null; } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java index c08f9af669..cc0eb23cf5 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ExileTargetEffect.java @@ -38,6 +38,8 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.game.stack.StackObject; import mage.players.Player; import mage.target.Target; import mage.target.targetpointer.FirstTargetPointer; @@ -133,6 +135,11 @@ public class ExileTargetEffect extends OneShotEffect { if (!currentZone.equals(Zone.EXILED) && (onlyFromZone == null || onlyFromZone.equals(currentZone))) { toExile.add(card); } + } else { + StackObject stackObject = game.getStack().getStackObject(targetId); + if (stackObject instanceof Spell && ((Spell) stackObject).getCard() != null) { + toExile.add(((Spell) stackObject).getCard()); + } } } } diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/CantCastMoreThanOneSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/CantCastMoreThanOneSpellEffect.java index 6f13383f28..ee6ca009fb 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/CantCastMoreThanOneSpellEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/CantCastMoreThanOneSpellEffect.java @@ -25,7 +25,6 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.abilities.effects.common.continuous; import mage.abilities.Ability; @@ -43,14 +42,13 @@ import mage.watchers.common.CastSpellLastTurnWatcher; * * @author LevelX2 */ - public class CantCastMoreThanOneSpellEffect extends ContinuousRuleModifyingEffectImpl { private final TargetController targetController; - + public CantCastMoreThanOneSpellEffect(TargetController targetController) { super(Duration.WhileOnBattlefield, Outcome.Detriment); - this.targetController = targetController; + this.targetController = targetController; } public CantCastMoreThanOneSpellEffect(final CantCastMoreThanOneSpellEffect effect) { @@ -95,22 +93,22 @@ public class CantCastMoreThanOneSpellEffect extends ContinuousRuleModifyingEffec Permanent attachment = game.getPermanent(source.getSourceId()); if (attachment == null || !attachment.getAttachedTo().equals(event.getPlayerId())) { return false; - } + } } - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); - if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(event.getPlayerId())> 0) { + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); + if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(event.getPlayerId()) > 0) { return true; } return false; } - + @Override public String getText(Mode mode) { if (staticText != null && !staticText.isEmpty()) { return staticText; } StringBuilder sb = new StringBuilder(); - switch(targetController) { + switch (targetController) { case YOU: sb.append("You"); break; diff --git a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CastOnlyIfYouHaveCastAnotherSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CastOnlyIfYouHaveCastAnotherSpellEffect.java index 072e2360ef..e0fc0786f4 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CastOnlyIfYouHaveCastAnotherSpellEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/ruleModifying/CastOnlyIfYouHaveCastAnotherSpellEffect.java @@ -25,7 +25,6 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.abilities.effects.common.ruleModifying; import mage.abilities.Ability; @@ -40,15 +39,15 @@ import mage.watchers.common.CastSpellLastTurnWatcher; * * @author LoneFox */ - public class CastOnlyIfYouHaveCastAnotherSpellEffect extends ContinuousRuleModifyingEffectImpl { + public CastOnlyIfYouHaveCastAnotherSpellEffect() { - super(Duration.EndOfGame, Outcome.Detriment); - staticText = "Cast {this} only if you've cast another spell this turn"; + super(Duration.EndOfGame, Outcome.Detriment); + staticText = "Cast {this} only if you've cast another spell this turn"; } public CastOnlyIfYouHaveCastAnotherSpellEffect(final CastOnlyIfYouHaveCastAnotherSpellEffect effect) { - super(effect); + super(effect); } @Override @@ -58,22 +57,22 @@ public class CastOnlyIfYouHaveCastAnotherSpellEffect extends ContinuousRuleModif @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getSourceId().equals(source.getSourceId())) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); - if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(source.getControllerId()) == 0) { - return true; - } - } - return false; + if (event.getSourceId().equals(source.getSourceId())) { + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); + if (watcher != null && watcher.getAmountOfSpellsPlayerCastOnCurrentTurn(source.getControllerId()) == 0) { + return true; + } + } + return false; } @Override public boolean apply(Game game, Ability source) { - return true; + return true; } @Override public CastOnlyIfYouHaveCastAnotherSpellEffect copy() { - return new CastOnlyIfYouHaveCastAnotherSpellEffect(this); + return new CastOnlyIfYouHaveCastAnotherSpellEffect(this); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/StormAbility.java b/Mage/src/main/java/mage/abilities/keyword/StormAbility.java index 5c54c47d40..27ff6fba57 100644 --- a/Mage/src/main/java/mage/abilities/keyword/StormAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/StormAbility.java @@ -100,7 +100,7 @@ class StormEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { MageObjectReference spellRef = (MageObjectReference) this.getValue("StormSpellRef"); if (spellRef != null) { - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); int stormCount = watcher.getSpellOrder(spellRef, game) - 1; if (stormCount > 0) { Spell spell = (Spell) this.getValue("StormSpell"); diff --git a/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java b/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java index 6b82e3087a..e63a2f44fb 100644 --- a/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/SurgeAbility.java @@ -69,7 +69,7 @@ public class SurgeAbility extends SpellAbility { @Override public boolean canActivate(UUID playerId, Game game) { // check if controller or teammate has already cast a spell this turn - CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get("CastSpellLastTurnWatcher"); + CastSpellLastTurnWatcher watcher = (CastSpellLastTurnWatcher) game.getState().getWatchers().get(CastSpellLastTurnWatcher.class.getName()); if (watcher != null) { Player player = game.getPlayer(playerId); if (player != null) { diff --git a/Mage/src/main/java/mage/watchers/Watcher.java b/Mage/src/main/java/mage/watchers/Watcher.java index e9d8520293..f00e3354a0 100644 --- a/Mage/src/main/java/mage/watchers/Watcher.java +++ b/Mage/src/main/java/mage/watchers/Watcher.java @@ -1,16 +1,16 @@ /* * 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 @@ -20,12 +20,11 @@ * 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.watchers; import java.io.Serializable; @@ -42,20 +41,19 @@ import mage.game.events.GameEvent; */ public abstract class Watcher implements Serializable { + protected String basicKey; protected UUID controllerId; protected UUID sourceId; - protected String key; protected boolean condition; protected WatcherScope scope; - public Watcher(String key, WatcherScope scope) { - this.key = key; + public Watcher(String basicKey, WatcherScope scope) { + this.basicKey = basicKey; this.scope = scope; } public Watcher(final Watcher watcher) { this.condition = watcher.condition; - this.key = watcher.key; this.controllerId = watcher.controllerId; this.sourceId = watcher.sourceId; this.scope = watcher.scope; @@ -80,19 +78,19 @@ public abstract class Watcher implements Serializable { public String getKey() { switch (scope) { case GAME: - return key; + return basicKey; case PLAYER: - return controllerId + key; + return controllerId + basicKey; case CARD: - return sourceId + key; + return sourceId + basicKey; } - return key; + return basicKey; } public boolean conditionMet() { return condition; } - + public void reset() { condition = false; } @@ -100,4 +98,5 @@ public abstract class Watcher implements Serializable { public abstract void watch(GameEvent event, Game game); public abstract Watcher copy(); + } diff --git a/Mage/src/main/java/mage/watchers/common/CastSpellLastTurnWatcher.java b/Mage/src/main/java/mage/watchers/common/CastSpellLastTurnWatcher.java index d78f382b52..7e96ee21ea 100644 --- a/Mage/src/main/java/mage/watchers/common/CastSpellLastTurnWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/CastSpellLastTurnWatcher.java @@ -50,7 +50,7 @@ public class CastSpellLastTurnWatcher extends Watcher { private final List spellsCastThisTurnInOrder = new ArrayList<>(); public CastSpellLastTurnWatcher() { - super("CastSpellLastTurnWatcher", WatcherScope.GAME); + super(CastSpellLastTurnWatcher.class.getName(), WatcherScope.GAME); } public CastSpellLastTurnWatcher(final CastSpellLastTurnWatcher watcher) { diff --git a/Mage/src/main/java/mage/watchers/common/PermanentsEnteredBattlefieldWatcher.java b/Mage/src/main/java/mage/watchers/common/PermanentsEnteredBattlefieldWatcher.java new file mode 100644 index 0000000000..f1a1e49dd2 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/PermanentsEnteredBattlefieldWatcher.java @@ -0,0 +1,65 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.watchers.common; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.watchers.Watcher; + +/** + * + * @author LevelX2 + */ +public class PermanentsEnteredBattlefieldWatcher extends Watcher { + + private final HashMap> enteringBattlefield = new HashMap<>(); + + public PermanentsEnteredBattlefieldWatcher() { + super(PermanentsEnteredBattlefieldWatcher.class.getName(), WatcherScope.GAME); + } + + public PermanentsEnteredBattlefieldWatcher(final PermanentsEnteredBattlefieldWatcher watcher) { + super(watcher); + } + + @Override + public PermanentsEnteredBattlefieldWatcher copy() { + return new PermanentsEnteredBattlefieldWatcher(this); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) { + Permanent perm = game.getPermanentEntering(event.getTargetId()); + if (perm != null) { + List permanents; + if (!enteringBattlefield.containsKey(perm.getControllerId())) { + permanents = new ArrayList<>(); + enteringBattlefield.put(perm.getControllerId(), permanents); + } else { + permanents = enteringBattlefield.get(perm.getControllerId()); + } + permanents.add(perm.copy()); // copy needed because attributes like color could be changed later + } + } + } + + @Override + public void reset() { + super.reset(); + enteringBattlefield.clear(); + } + + public List getThisTurnEnteringPermanents(UUID playerId) { + return enteringBattlefield.get(playerId); + } +} diff --git a/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java b/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java new file mode 100644 index 0000000000..c23e3747f0 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/SpellsCastWatcher.java @@ -0,0 +1,74 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.watchers.common; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import mage.MageObject; +import mage.constants.WatcherScope; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.stack.Spell; +import mage.watchers.Watcher; + +/** + * + * @author LevelX2 + */ +public class SpellsCastWatcher extends Watcher { + + private final HashMap> spellsCast = new HashMap<>(); + + public SpellsCastWatcher() { + super(SpellsCastWatcher.class.getName(), WatcherScope.GAME); + } + + public SpellsCastWatcher(final SpellsCastWatcher watcher) { + super(watcher); + } + + @Override + public SpellsCastWatcher copy() { + return new SpellsCastWatcher(this); + } + + @Override + public void watch(GameEvent event, Game game) { + if (EventType.SPELL_CAST.equals(event.getType())) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell == null) { + MageObject mageObject = game.getLastKnownInformation(event.getTargetId(), Zone.STACK); + if (mageObject instanceof Spell) { + spell = (Spell) mageObject; + } + } + if (spell != null) { + List spells; + if (!spellsCast.containsKey(spell.getControllerId())) { + spells = new ArrayList<>(); + spellsCast.put(spell.getControllerId(), spells); + } else { + spells = spellsCast.get(spell.getControllerId()); + } + spells.add(spell.copy()); // copy needed because attributes like color could be changed later + } + } + } + + @Override + public void reset() { + super.reset(); + spellsCast.clear(); + } + + public List getSpellsCastThisTurn(UUID playerId) { + return spellsCast.get(playerId); + } +} From 83ce3558043f4d43aaa20e7127684698d5d2495b Mon Sep 17 00:00:00 2001 From: DjB Date: Sat, 27 Feb 2016 22:45:37 -0600 Subject: [PATCH 18/67] Create MetalFatigue.java --- .../src/mage/sets/darksteel/MetalFatigue.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/darksteel/MetalFatigue.java diff --git a/Mage.Sets/src/mage/sets/darksteel/MetalFatigue.java b/Mage.Sets/src/mage/sets/darksteel/MetalFatigue.java new file mode 100644 index 0000000000..5883952397 --- /dev/null +++ b/Mage.Sets/src/mage/sets/darksteel/MetalFatigue.java @@ -0,0 +1,94 @@ +/* + * 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.darksteel; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.filter.common.FilterArtifactPermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author djbrez + */ +public class MetalFatigue extends CardImpl { + + public MetalFatigue(UUID ownerId) { + super(ownerId, 8, "Metal Fatigue", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{2}{W}"); + this.expansionSetCode = "DST"; + + // Tap all artifacts. + this.getSpellAbility().addEffect(new TapAllArtifactsEffect()); + } + + public MetalFatigue(final MetalFatigue card) { + super(card); + } + + @Override + public MetalFatigue copy() { + return new MetalFatigue(this); + } +} + +class TapAllArtifactsEffect extends OneShotEffect { + + public TapAllArtifactsEffect() { + super(Outcome.Tap); + staticText = "Tap all artifacts"; + } + + public TapAllArtifactsEffect(final TapAllArtifactsEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + for (Permanent artifact: game.getBattlefield().getAllActivePermanents(new FilterArtifactPermanent(), game)) { + artifact.tap(game); + } + return true; + } + return false; + } + + @Override + public TapAllArtifactsEffect copy() { + return new TapAllArtifactsEffect(this); + } + +} From 996f07a4ff79285352471de2b9805894435459a5 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 28 Feb 2016 08:44:14 +0100 Subject: [PATCH 19/67] Reworked some trap cards using old AlternateCosts class. --- .../src/mage/sets/zendikar/CobraTrap.java | 91 +++++++++---------- .../src/mage/sets/zendikar/WhiplashTrap.java | 2 +- 2 files changed, 44 insertions(+), 49 deletions(-) diff --git a/Mage.Sets/src/mage/sets/zendikar/CobraTrap.java b/Mage.Sets/src/mage/sets/zendikar/CobraTrap.java index 628bf52267..089cd39315 100644 --- a/Mage.Sets/src/mage/sets/zendikar/CobraTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/CobraTrap.java @@ -27,25 +27,24 @@ */ package mage.sets.zendikar; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.ColoredManaSymbol; -import mage.constants.Rarity; -import mage.constants.WatcherScope; -import mage.constants.Zone; import mage.abilities.Ability; -import mage.abilities.costs.AlternativeCostImpl; -import mage.abilities.costs.Cost; -import mage.abilities.costs.mana.ColoredManaCost; +import mage.abilities.condition.Condition; +import mage.abilities.costs.AlternativeCostSourceAbility; +import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.WatcherScope; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.game.permanent.token.SnakeToken; import mage.game.stack.StackObject; -import mage.players.Player; import mage.watchers.Watcher; /** @@ -59,11 +58,9 @@ public class CobraTrap extends CardImpl { this.expansionSetCode = "ZEN"; this.subtype.add("Trap"); - // If a noncreature permanent under your control was destroyed this turn by a spell or ability an opponent controlled, you may pay {G} rather than pay Cobra Trap's mana cost. - this.getSpellAbility().addAlternativeCost( - new CobraTrapAlternativeCost()); - this.getSpellAbility().addWatcher(new CobraTrapWatcher()); + this.addAbility(new AlternativeCostSourceAbility(new ManaCostsImpl("{G}"), CobraTrapCondition.getInstance()), new CobraTrapWatcher()); + // Put four 1/1 green Snake creature tokens onto the battlefield. this.getSpellAbility().addEffect(new CreateTokenEffect(new SnakeToken(), 4)); } @@ -78,10 +75,33 @@ public class CobraTrap extends CardImpl { } } +class CobraTrapCondition implements Condition { + + private static final CobraTrapCondition fInstance = new CobraTrapCondition(); + + public static Condition getInstance() { + return fInstance; + } + + @Override + public boolean apply(Game game, Ability source) { + CobraTrapWatcher watcher = (CobraTrapWatcher) game.getState().getWatchers().get(CobraTrapWatcher.class.getName()); + return watcher != null && watcher.conditionMet(source.getControllerId()); + } + + @Override + public String toString() { + return "If a noncreature permanent under your control was destroyed this turn by a spell or ability an opponent controlled"; + } + +} + class CobraTrapWatcher extends Watcher { + Set players = new HashSet<>(); + public CobraTrapWatcher() { - super("noncreature permanent destroyed", WatcherScope.PLAYER); + super(CobraTrapWatcher.class.getName(), WatcherScope.GAME); } public CobraTrapWatcher(final CobraTrapWatcher watcher) { @@ -95,51 +115,26 @@ class CobraTrapWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (condition == true) { // no need to check - condition has already occured - return; - } - Player player = game.getPlayer(controllerId); - if (player != null && event.getType() == EventType.DESTROYED_PERMANENT) { - Permanent perm = (Permanent) game.getLastKnownInformation(event.getTargetId(), Zone.BATTLEFIELD); + if (event.getType() == EventType.DESTROYED_PERMANENT) { + Permanent perm = (Permanent) game.getPermanentOrLKIBattlefield(event.getTargetId()); // can regenerate or be indestructible if (perm != null && !perm.getCardType().contains(CardType.CREATURE)) { if (game.getStack().size() > 0) { StackObject spell = game.getStack().getStackObject(event.getSourceId()); - if (spell != null && game.getOpponents(controllerId).contains(spell.getControllerId())) { - condition = true; + if (spell != null && game.getOpponents(perm.getControllerId()).contains(spell.getControllerId())) { + players.add(perm.getControllerId()); } } } } } -} - -class CobraTrapAlternativeCost extends AlternativeCostImpl { - - public CobraTrapAlternativeCost() { - super("you may pay {G} rather than pay Cobra Trap's mana cost"); - this.add(new ColoredManaCost(ColoredManaSymbol.G)); - } - - public CobraTrapAlternativeCost(final CobraTrapAlternativeCost cost) { - super(cost); - } @Override - public CobraTrapAlternativeCost copy() { - return new CobraTrapAlternativeCost(this); + public void reset() { + super.reset(); + players.clear(); } - @Override - public boolean isAvailable(Game game, Ability source) { - Watcher watcher = game.getState().getWatchers().get("noncreature permanent destroyed", source.getControllerId()); - if (watcher != null && watcher.conditionMet()) { - return true; - } - return false; - } - - @Override - public String getText() { - return "If a noncreature permanent under your control was destroyed this turn by a spell or ability an opponent controlled, you may pay {G} rather than pay Cobra Trap's mana cost"; + public boolean conditionMet(UUID playerId) { + return players.contains(playerId); } } diff --git a/Mage.Sets/src/mage/sets/zendikar/WhiplashTrap.java b/Mage.Sets/src/mage/sets/zendikar/WhiplashTrap.java index 8778efd3f3..1e932d059a 100644 --- a/Mage.Sets/src/mage/sets/zendikar/WhiplashTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/WhiplashTrap.java @@ -82,7 +82,7 @@ class WhiplashTrapCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - PermanentsEnteredBattlefieldWatcher watcher = (PermanentsEnteredBattlefieldWatcher) game.getState().getWatchers().get(PermanentsEnteredBattlefieldWatcher.BASIC_KEY); + PermanentsEnteredBattlefieldWatcher watcher = (PermanentsEnteredBattlefieldWatcher) game.getState().getWatchers().get(PermanentsEnteredBattlefieldWatcher.class.getName()); if (watcher != null) { for (UUID opponentId : game.getOpponents(source.getControllerId())) { List permanents = watcher.getThisTurnEnteringPermanents(opponentId); From 6d4a3bac288e9f3aff84d878c9816f89500a67af Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 28 Feb 2016 10:52:55 +0100 Subject: [PATCH 20/67] Fixed that wathcing games did not work correctly. --- .../main/java/mage/client/game/BattlefieldPanel.java | 2 +- .../src/main/java/mage/client/game/GamePanel.java | 10 +++++----- .../src/main/java/mage/client/util/ButtonColumn.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/game/BattlefieldPanel.java b/Mage.Client/src/main/java/mage/client/game/BattlefieldPanel.java index dda1e6a5be..14659169f1 100644 --- a/Mage.Client/src/main/java/mage/client/game/BattlefieldPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/BattlefieldPanel.java @@ -226,7 +226,7 @@ public class BattlefieldPanel extends javax.swing.JLayeredPane { } public void sortLayout() { - if (battlefield == null) { + if (battlefield == null || this.getWidth() < 1) { // Can't do layout when panel is not sized yet return; } diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 38d6a5ea8c..ed5c9b7ba2 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -608,9 +608,9 @@ public final class GamePanel extends javax.swing.JPanel { } } PlayerView player = game.getPlayers().get(playerSeat); - PlayAreaPanel sessionPlayer = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this, - new PlayAreaPanelOptions(game.isPlayer(), true, game.isRollbackTurnsAllowed())); - players.put(player.getPlayerId(), sessionPlayer); + PlayAreaPanel playAreaPanel = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this, + new PlayAreaPanelOptions(game.isPlayer(), game.isPlayer(), game.isRollbackTurnsAllowed())); + players.put(player.getPlayerId(), playAreaPanel); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.BOTH; c.weightx = 0.5; @@ -620,8 +620,8 @@ public final class GamePanel extends javax.swing.JPanel { } c.gridx = col; c.gridy = row; - this.pnlBattlefield.add(sessionPlayer, c); - sessionPlayer.setVisible(true); + this.pnlBattlefield.add(playAreaPanel, c); + playAreaPanel.setVisible(true); if (oddNumber) { col++; } diff --git a/Mage.Client/src/main/java/mage/client/util/ButtonColumn.java b/Mage.Client/src/main/java/mage/client/util/ButtonColumn.java index 7815d8bfdc..20a4d76c45 100644 --- a/Mage.Client/src/main/java/mage/client/util/ButtonColumn.java +++ b/Mage.Client/src/main/java/mage/client/util/ButtonColumn.java @@ -111,7 +111,7 @@ public class ButtonColumn extends AbstractCellEditor implements TableCellRendere @Override public void actionPerformed(ActionEvent e) { - if (table.getRowCount() >= table.getEditingRow()) { + if (table.getRowCount() > 0 && table.getRowCount() >= table.getEditingRow()) { int row = table.convertRowIndexToModel(table.getEditingRow()); fireEditingStopped(); ActionEvent event = new ActionEvent(table, ActionEvent.ACTION_PERFORMED, "" + row); From 245cd0521c05fb719dfdb7546e877b80da863d0d Mon Sep 17 00:00:00 2001 From: DjB Date: Sun, 28 Feb 2016 10:08:16 -0600 Subject: [PATCH 21/67] Update MetalFatigue.java changed to use the existing TapAllEffect --- .../src/mage/sets/darksteel/MetalFatigue.java | 40 ++----------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/Mage.Sets/src/mage/sets/darksteel/MetalFatigue.java b/Mage.Sets/src/mage/sets/darksteel/MetalFatigue.java index 5883952397..f6219a1fc3 100644 --- a/Mage.Sets/src/mage/sets/darksteel/MetalFatigue.java +++ b/Mage.Sets/src/mage/sets/darksteel/MetalFatigue.java @@ -28,29 +28,25 @@ package mage.sets.darksteel; import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.TapAllEffect; import mage.cards.CardImpl; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.Rarity; import mage.filter.common.FilterArtifactPermanent; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; /** * * @author djbrez */ public class MetalFatigue extends CardImpl { + public MetalFatigue(UUID ownerId) { super(ownerId, 8, "Metal Fatigue", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{2}{W}"); this.expansionSetCode = "DST"; // Tap all artifacts. - this.getSpellAbility().addEffect(new TapAllArtifactsEffect()); + this.getSpellAbility().addEffect(new TapAllEffect(new FilterArtifactPermanent("artifacts"))); } public MetalFatigue(final MetalFatigue card) { @@ -62,33 +58,3 @@ public class MetalFatigue extends CardImpl { return new MetalFatigue(this); } } - -class TapAllArtifactsEffect extends OneShotEffect { - - public TapAllArtifactsEffect() { - super(Outcome.Tap); - staticText = "Tap all artifacts"; - } - - public TapAllArtifactsEffect(final TapAllArtifactsEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player != null) { - for (Permanent artifact: game.getBattlefield().getAllActivePermanents(new FilterArtifactPermanent(), game)) { - artifact.tap(game); - } - return true; - } - return false; - } - - @Override - public TapAllArtifactsEffect copy() { - return new TapAllArtifactsEffect(this); - } - -} From f53daaaf940ab90d597c4d08ad786bc11fc17b51 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 28 Feb 2016 18:39:21 +0100 Subject: [PATCH 22/67] Xmage 1.4.9v1 --- Mage.Common/src/mage/utils/MageVersion.java | 2 +- Mage/src/main/java/mage/cards/repository/CardRepository.java | 2 +- Utils/release/getting_implemented_cards.txt | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Mage.Common/src/mage/utils/MageVersion.java b/Mage.Common/src/mage/utils/MageVersion.java index 4c2781ce49..41fed76607 100644 --- a/Mage.Common/src/mage/utils/MageVersion.java +++ b/Mage.Common/src/mage/utils/MageVersion.java @@ -41,7 +41,7 @@ public class MageVersion implements Serializable, Comparable { public final static int MAGE_VERSION_MAJOR = 1; public final static int MAGE_VERSION_MINOR = 4; public final static int MAGE_VERSION_PATCH = 9; - public final static String MAGE_VERSION_MINOR_PATCH = "v0"; + public final static String MAGE_VERSION_MINOR_PATCH = "v1"; public final static String MAGE_VERSION_INFO = ""; private final int major; diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 3c5c4bdb33..527ff0faaf 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -63,7 +63,7 @@ public enum CardRepository { // raise this if db structure was changed private static final long CARD_DB_VERSION = 43; // raise this if new cards were added to the server - private static final long CARD_CONTENT_VERSION = 46; + private static final long CARD_CONTENT_VERSION = 47; private final Random random = new Random(); private Dao cardDao; diff --git a/Utils/release/getting_implemented_cards.txt b/Utils/release/getting_implemented_cards.txt index 12ffa04fa4..4e0e5e41b7 100644 --- a/Utils/release/getting_implemented_cards.txt +++ b/Utils/release/getting_implemented_cards.txt @@ -63,7 +63,8 @@ git log 78ac6688ea02f4950cf35836f0f4a1b0160278d4..head --diff-filter=A --name-st since 1.4.9v0 git log 51a0d8a4b2f53ea67d21f771acc533b610a02e0c..head --diff-filter=A --name-status | sed -ne "s/^A[^u]Mage.Sets\/src\/mage\/sets\///p" | sort > added_cards.txt - +since 1.4.9v1 +git log 6d4a3bac288e9f3aff84d878c9816f89500a67af..head --diff-filter=A --name-status | sed -ne "s/^A[^u]Mage.Sets\/src\/mage\/sets\///p" | sort > added_cards.txt 3. Copy added_cards.txt to trunk\Utils folder 4. Run script: From 739b7d6535f7df81145846046666676c6749f4b1 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sun, 28 Feb 2016 19:00:41 +0100 Subject: [PATCH 23/67] Added a test for Serra Ascendant and Commander FFA. --- .../commander/FFA3/SerraAscendantTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Mage.Tests/src/test/java/org/mage/test/commander/FFA3/SerraAscendantTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/FFA3/SerraAscendantTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/FFA3/SerraAscendantTest.java new file mode 100644 index 0000000000..2245367576 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/commander/FFA3/SerraAscendantTest.java @@ -0,0 +1,84 @@ +/* + * 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.commander.FFA3; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestCommander3PlayersFFA; + +/** + * + * @author LevelX2 + */ +public class SerraAscendantTest extends CardTestCommander3PlayersFFA { + + /** + * Serra Ascendant is not working properly. Playing commander free for all, + * and when life total was less than 30 Serra remained a 6/6. + */ + @Test + public void TestChangePTTo11() { + + // Lifelink (Damage dealt by this creature also causes you to gain that much life.) + // As long as you have 30 or more life, Serra Ascendant gets +5/+5 and has flying. + addCard(Zone.HAND, playerA, "Serra Ascendant"); // {W} + addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); + // Draw cards equal to the power of target creature you control. + addCard(Zone.HAND, playerA, "Soul's Majesty"); // Sorcery - {4}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + + addCard(Zone.HAND, playerB, "Lightning Bolt", 2); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2); + + addCard(Zone.HAND, playerC, "Lightning Bolt", 2); + addCard(Zone.BATTLEFIELD, playerC, "Mountain", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Serra Ascendant"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul's Majesty", "Serra Ascendant"); + + castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerB, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerC, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.BEGIN_COMBAT, playerC, "Lightning Bolt", playerA); + + setStopAt(1, PhaseStep.DECLARE_ATTACKERS); + execute(); + + assertGraveyardCount(playerA, "Soul's Majesty", 1); + assertHandCount(playerA, 7); // 6 from Soul's Majesty + 1 from draw phase + assertGraveyardCount(playerB, "Lightning Bolt", 2); + assertGraveyardCount(playerC, "Lightning Bolt", 2); + assertPermanentCount(playerA, "Serra Ascendant", 1); + assertPowerToughness(playerA, "Serra Ascendant", 1, 1); + assertLife(playerA, 28); + assertLife(playerB, 40); + assertLife(playerC, 40); + } + +} From 182a38b658bfe094533510ea32811fb9bd4e9aef Mon Sep 17 00:00:00 2001 From: DjB Date: Sun, 28 Feb 2016 17:23:45 -0600 Subject: [PATCH 24/67] Create LiegeOfThePit.java --- .../mage/sets/timespiral/LiegeOfThePit.java | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/timespiral/LiegeOfThePit.java diff --git a/Mage.Sets/src/mage/sets/timespiral/LiegeOfThePit.java b/Mage.Sets/src/mage/sets/timespiral/LiegeOfThePit.java new file mode 100644 index 0000000000..36d31695c4 --- /dev/null +++ b/Mage.Sets/src/mage/sets/timespiral/LiegeOfThePit.java @@ -0,0 +1,131 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.timespiral; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.MorphAbility; +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.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetControlledCreaturePermanent; + +/** + * + * @author djbrez + */ +public class LiegeOfThePit extends CardImpl { + + public LiegeOfThePit(UUID ownerId) { + super(ownerId, 113, "Liege of the Pit", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{5}{B}{B}{B}"); + this.expansionSetCode = "TSP"; + this.subtype.add("Demon"); + this.power = new MageInt(7); + this.toughness = new MageInt(7); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + // Trample + this.addAbility(TrampleAbility.getInstance()); + // At the beginning of your upkeep, sacrifice a creature other than Liege of the Pit. If you can't, Liege of the Pit deals 7 damage to you. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new LiegeOfThePitEffect(), TargetController.YOU, false)); + // Morph {B}{B}{B}{B} + this.addAbility(new MorphAbility(this, new ManaCostsImpl("{B}{B}{B}{B}"))); + } + + public LiegeOfThePit(final LiegeOfThePit card) { + super(card); + } + + @Override + public LiegeOfThePit copy() { + return new LiegeOfThePit(this); + } +} + + +class LiegeOfThePitEffect extends OneShotEffect { + + public LiegeOfThePitEffect() { + super(Outcome.Damage); + this.staticText = "Sacrifice a creature other than {this}. If you can't {this} deals 7 damage to you."; + } + + public LiegeOfThePitEffect(final LiegeOfThePitEffect effect) { + super(effect); + } + + @Override + public LiegeOfThePitEffect copy() { + return new LiegeOfThePitEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + if (sourcePermanent == null) { + sourcePermanent = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD); + } + if (player == null || sourcePermanent == null) { + return false; + } + + FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("creature other than " + sourcePermanent.getName()); + filter.add(new AnotherPredicate()); + + Target target = new TargetControlledCreaturePermanent(1, 1, filter, true); + if (target.canChoose(source.getSourceId(), player.getId(), game)) { + player.choose(Outcome.Sacrifice, target, source.getSourceId(), game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent != null) { + permanent.sacrifice(source.getSourceId(), game); + return true; + } + } else { + player.damage(7, source.getSourceId(), game, false, true); + return true; + } + return false; + } +} From 075932e1b058c3994766dc751c9e0e0e9420eb5b Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Mon, 29 Feb 2016 01:19:13 +0100 Subject: [PATCH 25/67] [SOI] Added Invasive Surgery. --- .../shadowsoverinnistrad/InvasiveSurgery.java | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/InvasiveSurgery.java diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InvasiveSurgery.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InvasiveSurgery.java new file mode 100644 index 0000000000..7fdd02f273 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/InvasiveSurgery.java @@ -0,0 +1,130 @@ +/* + * 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.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.effects.common.search.SearchTargetGraveyardHandLibraryForCardNameAndExileEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.game.Game; +import mage.game.stack.StackObject; +import mage.players.Player; +import mage.target.TargetSpell; + +/** + * + * @author LevelX2 + */ +public class InvasiveSurgery extends CardImpl { + + private final static FilterSpell filter = new FilterSpell("sorcery spell"); + + static { + filter.add(new CardTypePredicate(CardType.SORCERY)); + } + + public InvasiveSurgery(UUID ownerId) { + super(ownerId, 68, "Invasive Surgery", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{U}"); + this.expansionSetCode = "SOI"; + + // Counter target sorcery spell. + // Delirium — If there are four or more card types among cards in your graveyard, search the graveyard, hand, and library of that spell's controller for any number of cards with the same name as that spell, exile those cards, then that player shuffles his or her library. + this.getSpellAbility().addEffect(new InvasiveSurgeryEffect()); + this.getSpellAbility().addTarget(new TargetSpell(filter)); + + } + + public InvasiveSurgery(final InvasiveSurgery card) { + super(card); + } + + @Override + public InvasiveSurgery copy() { + return new InvasiveSurgery(this); + } +} + +class InvasiveSurgeryEffect extends SearchTargetGraveyardHandLibraryForCardNameAndExileEffect { + + public InvasiveSurgeryEffect() { + super(true, "that spell's controller", "all cards with the same name as that spell"); + } + + public InvasiveSurgeryEffect(final InvasiveSurgeryEffect effect) { + super(effect); + } + + @Override + public InvasiveSurgeryEffect copy() { + return new InvasiveSurgeryEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + String cardName = ""; + UUID spellController = null; + if (source.getTargets().get(0) instanceof TargetSpell) { + UUID objectId = source.getFirstTarget(); + StackObject stackObject = game.getStack().getStackObject(objectId); + if (stackObject != null) { + MageObject targetObject = game.getObject(stackObject.getSourceId()); + if (targetObject instanceof Card) { + cardName = targetObject.getName(); + } + spellController = stackObject.getControllerId(); + game.getStack().counter(objectId, source.getSourceId(), game); + } + } + + // Check the Delirium condition + if (!DeliriumCondition.getInstance().apply(game, source)) { + return true; + } + return this.applySearchAndExile(game, source, cardName, spellController); + } + + @Override + public String getText(Mode mode) { + return "Counter target sorcery spell.

" + + "Delirium — If there are four or more card types among cards in your graveyard, " + + "search the graveyard, hand, and library of that spell's controller for any number of cards " + + "with the same name as that spell, exile those cards, then that player shuffles his or her library"; + } +} From 87e2cd68c1945cf247495760dfa66dbbfb773c13 Mon Sep 17 00:00:00 2001 From: DjB Date: Sun, 28 Feb 2016 22:48:57 -0600 Subject: [PATCH 26/67] Create PitSpawn.java --- Mage.Sets/src/mage/sets/exodus/PitSpawn.java | 74 ++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/exodus/PitSpawn.java diff --git a/Mage.Sets/src/mage/sets/exodus/PitSpawn.java b/Mage.Sets/src/mage/sets/exodus/PitSpawn.java new file mode 100644 index 0000000000..6347ce9753 --- /dev/null +++ b/Mage.Sets/src/mage/sets/exodus/PitSpawn.java @@ -0,0 +1,74 @@ +/* + * 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.exodus; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.DealsDamageToACreatureTriggeredAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.SacrificeSourceUnlessPaysEffect; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.TargetController; + +/** + * + * @author djbrez + */ +public class PitSpawn extends CardImpl { + + public PitSpawn(UUID ownerId) { + super(ownerId, 70, "Pit Spawn", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{4}{B}{B}{B}"); + this.expansionSetCode = "EXO"; + this.subtype.add("Demon"); + this.power = new MageInt(6); + this.toughness = new MageInt(4); + + // First strike + this.addAbility(FirstStrikeAbility.getInstance()); + + // At the beginning of your upkeep, sacrifice Pit Spawn unless you pay {B}{B}. + this.addAbility(new BeginningOfUpkeepTriggeredAbility(new SacrificeSourceUnlessPaysEffect(new ManaCostsImpl("{B}{B}")), TargetController.YOU, false)); + + // Whenever Pit Spawn deals damage to a creature, exile that creature. + this.addAbility(new DealsDamageToACreatureTriggeredAbility(new ExileTargetEffect("exile that creature"), false, false, true)); + } + + public PitSpawn(final PitSpawn card) { + super(card); + } + + @Override + public PitSpawn copy() { + return new PitSpawn(this); + } +} From 3658a8c7bc8bb470d689b22331ba0c69a123389b Mon Sep 17 00:00:00 2001 From: rkfg Date: Mon, 29 Feb 2016 19:29:10 +0300 Subject: [PATCH 27/67] Prevent NPE if client never connected to any server and doesn't have sets downloaded. --- .../client/deck/generator/DeckGenerator.java | 11 +++++++++++ .../java/mage/client/dialog/ConnectDialog.java | 2 ++ .../client/util/sets/ConstructedFormats.java | 16 ++++++++++++---- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java index 67429dd5e8..cb63a4d543 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java @@ -54,6 +54,14 @@ import mage.util.TournamentUtil; */ public class DeckGenerator { + public static class DeckGeneratorException extends RuntimeException { + + public DeckGeneratorException(String message) { + super(message); + } + + } + private static final int MAX_TRIES = 8196; private static DeckGeneratorDialog genDialog; private static DeckGeneratorPool genPool; @@ -82,6 +90,9 @@ public class DeckGenerator { String format = genDialog.getSelectedFormat(); List setsToUse = ConstructedFormats.getSetsByFormat(format); + if (setsToUse == null) { + throw new DeckGeneratorException("Deck sets aren't initialized; please connect to a server to update the database."); + } if (setsToUse.isEmpty()) { // Default to using all sets setsToUse = ExpansionRepository.instance.getSetCodes(); diff --git a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java index 9a59abd173..6436e0172b 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/ConnectDialog.java @@ -66,6 +66,7 @@ import static mage.client.dialog.PreferencesDialog.KEY_CONNECT_FLAG; import mage.client.preference.MagePreferences; import mage.client.util.Config; import mage.client.util.gui.countryBox.CountryItemEditor; +import mage.client.util.sets.ConstructedFormats; import mage.remote.Connection; import org.apache.log4j.Logger; @@ -442,6 +443,7 @@ public class ConnectDialog extends MageDialog { private void connected() { this.saveSettings(); this.hideDialog(); + ConstructedFormats.ensureLists(); } private void keyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_keyTyped diff --git a/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java b/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java index 81bc28210b..f76efc4af0 100644 --- a/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java +++ b/Mage.Client/src/main/java/mage/client/util/sets/ConstructedFormats.java @@ -50,6 +50,12 @@ public class ConstructedFormats { } + public static void ensureLists() { + if (getSetsByFormat(ConstructedFormats.STANDARD) == null) { + buildLists(); + } + } + private static void buildLists() { GregorianCalendar cutoff; // month is zero based so January = 0 @@ -60,6 +66,7 @@ public class ConstructedFormats { cutoff = new GregorianCalendar(calendar.get(Calendar.YEAR) - 2, Calendar.SEPTEMBER, 1); } final Map expansionInfo = new HashMap<>(); + formats.clear(); // prevent NPE on sorting if this is not the first try for (ExpansionInfo set : ExpansionRepository.instance.getAll()) { expansionInfo.put(set.getName(), set); formats.add(set.getName()); @@ -207,10 +214,11 @@ public class ConstructedFormats { } }); - - formats.add(0, MODERN); - formats.add(0, EXTENDED); - formats.add(0, STANDARD); + if (!formats.isEmpty()) { + formats.add(0, MODERN); + formats.add(0, EXTENDED); + formats.add(0, STANDARD); + } formats.add(0, ALL); } From f00a588e24ba55f065d5df69b7f26940c51ad5e7 Mon Sep 17 00:00:00 2001 From: rkfg Date: Mon, 29 Feb 2016 19:30:50 +0300 Subject: [PATCH 28/67] Add Generate button to Deck Editor. --- .../client/deckeditor/DeckEditorPanel.form | 3 ++ .../client/deckeditor/DeckEditorPanel.java | 37 +++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form index 30eac766e4..f0db80d705 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.form @@ -83,6 +83,8 @@ + + @@ -110,6 +112,7 @@ + diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java index 377b69adda..d9be9c4e20 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/DeckEditorPanel.java @@ -60,9 +60,9 @@ import mage.client.MageFrame; import mage.client.cards.BigCard; import mage.client.cards.ICardGrid; import mage.client.constants.Constants.DeckEditorMode; -import static mage.client.constants.Constants.DeckEditorMode.FREE_BUILDING; -import static mage.client.constants.Constants.DeckEditorMode.LIMITED_BUILDING; -import static mage.client.constants.Constants.DeckEditorMode.SIDEBOARDING; +import mage.client.deck.generator.DeckGenerator; +import mage.client.deck.generator.DeckGenerator.DeckGeneratorException; + import mage.client.dialog.AddLandDialog; import mage.client.plugins.impl.Plugins; import mage.client.util.Event; @@ -173,6 +173,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { this.cardSelector.switchToGrid(); this.btnExit.setVisible(false); this.btnImport.setVisible(false); + this.btnGenDeck.setVisible(false); if (!MageFrame.getSession().isTestMode()) { this.btnLoad.setVisible(false); } @@ -195,6 +196,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { //this.cardTableSelector.loadCards(this.bigCard); this.btnExit.setVisible(true); this.btnImport.setVisible(true); + this.btnGenDeck.setVisible(true); if (!MageFrame.getSession().isTestMode()) { this.btnLoad.setVisible(true); } @@ -504,6 +506,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { btnImport = new javax.swing.JButton(); btnSubmit = new javax.swing.JButton(); btnAddLand = new javax.swing.JButton(); + btnGenDeck = new javax.swing.JButton(); txtTimeRemaining = new javax.swing.JTextField(); jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); @@ -591,6 +594,15 @@ public class DeckEditorPanel extends javax.swing.JPanel { btnAddLandActionPerformed(evt); } }); + + btnGenDeck.setText("Generate"); + btnGenDeck.setName("btnGenDeck"); + btnGenDeck.addActionListener(new java.awt.event.ActionListener() { + @Override + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnGenDeckActionPerformed(evt); + } + }); txtTimeRemaining.setEditable(false); txtTimeRemaining.setForeground(java.awt.Color.red); @@ -626,6 +638,8 @@ public class DeckEditorPanel extends javax.swing.JPanel { .addContainerGap() .addComponent(btnImport) .addContainerGap() + .addComponent(btnGenDeck) + .addContainerGap() .addComponent(btnAddLand) .addContainerGap() .addComponent(btnSubmit)) @@ -650,6 +664,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(btnImport) + .addComponent(btnGenDeck) .addComponent(btnAddLand) .addComponent(btnSubmit)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -801,6 +816,21 @@ public class DeckEditorPanel extends javax.swing.JPanel { refreshDeck(); }//GEN-LAST:event_btnAddLandActionPerformed + private void btnGenDeckActionPerformed(ActionEvent evt) { + try { + setCursor(new Cursor(Cursor.WAIT_CURSOR)); + String path = DeckGenerator.generateDeck(); + deck = Deck.load(DeckImporterUtil.importDeck(path), true, true); + } catch (GameException ex) { + JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage(), "Error loading generated deck", JOptionPane.ERROR_MESSAGE); + }catch (DeckGeneratorException ex) { + JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage(), "Generator error", JOptionPane.ERROR_MESSAGE); + } finally { + setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + refreshDeck(); + } + // Variables declaration - do not modify//GEN-BEGIN:variables private mage.client.cards.BigCard bigCard; private javax.swing.JButton btnExit; @@ -816,6 +846,7 @@ public class DeckEditorPanel extends javax.swing.JPanel { private javax.swing.JTextField txtDeckName; private javax.swing.JButton btnSubmit; private javax.swing.JButton btnAddLand; + private javax.swing.JButton btnGenDeck; private JComponent cardInfoPane; private javax.swing.JTextField txtTimeRemaining; // End of variables declaration//GEN-END:variables From f7314307a6db9360f69f110172ce2eb36f5d653a Mon Sep 17 00:00:00 2001 From: rkfg Date: Mon, 29 Feb 2016 20:08:52 +0300 Subject: [PATCH 29/67] Reload symbols after closing the symbols download window (better UX). --- .../src/main/java/org/mage/plugins/card/CardPluginImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java b/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java index 7e76349d6a..0df1894a87 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java @@ -31,6 +31,7 @@ import net.xeoh.plugins.base.annotations.meta.Author; import org.apache.log4j.Logger; import org.mage.card.arcane.Animation; import org.mage.card.arcane.CardPanel; +import org.mage.card.arcane.ManaSymbols; import org.mage.plugins.card.dl.DownloadGui; import org.mage.plugins.card.dl.DownloadJob; import org.mage.plugins.card.dl.Downloader; @@ -540,6 +541,7 @@ public class CardPluginImpl implements CardPlugin { @Override public void windowClosing(WindowEvent e) { g.getDownloader().dispose(); + ManaSymbols.loadImages(); } }); d.setLayout(new BorderLayout()); From abff5c1958443f64690230d11dc7ef7dea2498a1 Mon Sep 17 00:00:00 2001 From: rkfg Date: Mon, 29 Feb 2016 20:53:03 +0300 Subject: [PATCH 30/67] Filter HTML in ability picker. --- .../main/java/mage/client/components/ability/AbilityPicker.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java b/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java index efed293521..5d46d46db9 100644 --- a/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java +++ b/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java @@ -14,6 +14,7 @@ import org.apache.log4j.Logger; import org.jdesktop.layout.GroupLayout; import org.jdesktop.layout.LayoutStyle; import org.jdesktop.swingx.JXPanel; +import org.jsoup.Jsoup; import org.mage.card.arcane.ManaSymbols; import org.mage.card.arcane.UI; @@ -434,6 +435,7 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { if (choice == null || choice.isEmpty()) { return choice; } + choice = Jsoup.parse(choice).text(); // decode HTML entities and strip tags return choice.substring(0, 1).toUpperCase() + choice.substring(1); } From ba0cd24b552d2a76a56196a2af2765e76f2fe243 Mon Sep 17 00:00:00 2001 From: rkfg Date: Tue, 1 Mar 2016 01:12:57 +0300 Subject: [PATCH 31/67] Show card tooltips in system logs. --- .../mage/client/components/ColorPane.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/Mage.Client/src/main/java/mage/client/components/ColorPane.java b/Mage.Client/src/main/java/mage/client/components/ColorPane.java index 370459a266..aa6d5be1aa 100644 --- a/Mage.Client/src/main/java/mage/client/components/ColorPane.java +++ b/Mage.Client/src/main/java/mage/client/components/ColorPane.java @@ -2,13 +2,28 @@ package mage.client.components; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Component; import java.awt.Graphics; +import java.awt.Point; + import javax.swing.JEditorPane; import javax.swing.JPanel; import javax.swing.JTextPane; +import javax.swing.SwingUtilities; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkEvent.EventType; +import javax.swing.event.HyperlinkListener; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; +import mage.cards.repository.CardInfo; +import mage.cards.repository.CardRepository; +import mage.client.MageFrame; +import mage.client.dialog.PreferencesDialog; +import mage.client.util.gui.GuiDisplayUtil; +import mage.components.CardInfoPane; +import mage.view.CardView; + /** * Enhanced {@link JTextPane} with text highlighting support. * @@ -18,10 +33,52 @@ public class ColorPane extends JEditorPane { HTMLEditorKit kit = new HTMLEditorKit(); HTMLDocument doc = new HTMLDocument(); + private int tooltipDelay; public ColorPane() { this.setEditorKit(kit); this.setDocument(doc); + addHyperlinkListener(new HyperlinkListener() { + + @Override + public void hyperlinkUpdate(final HyperlinkEvent e) { + tooltipDelay = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_TOOLTIPS_DELAY, 300); + if (tooltipDelay == 0) { + return; + } + String name = e.getDescription().substring(1); + CardInfo card = CardRepository.instance.findCard(name); + try { + final Component container = MageFrame.getUI().getComponent(MageComponents.POPUP_CONTAINER); + if (e.getEventType() == EventType.EXITED) { + setPopupVisibility(container, false); + } else { + CardInfoPane cardInfoPane = (CardInfoPane) MageFrame.getUI().getComponent(MageComponents.CARD_INFO_PANE); + cardInfoPane.setCard(new CardView(card.getMockCard()), container); + Point location = new Point(getLocationOnScreen().x - container.getWidth(), (int) MageFrame.getDesktop() + .getMousePosition().getY()); + Component parentComponent = MageFrame.getInstance(); + location = GuiDisplayUtil.keepComponentInsideParent(location, parentComponent.getLocationOnScreen(), container, + parentComponent); + container.setLocation(location); + setPopupVisibility(container, true); + } + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + + private void setPopupVisibility(final Component container, final boolean show) throws InterruptedException { + final Component c = MageFrame.getUI().getComponent(MageComponents.DESKTOP_PANE); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + container.setVisible(show); + c.repaint(); + } + }); + } + }); } /** @@ -45,6 +102,7 @@ public class ColorPane extends JEditorPane { public void append(String text) { try { + text = text.replaceAll("(]*>([^<]*)) (\\[[0-9a-fA-F]*\\])", "$1 $3"); setEditable(true); kit.insertHTML(doc, doc.getLength(), text, 0, 0, null); setEditable(false); From b737f3bf8add64ff7125672cc67103d72ff5c7ec Mon Sep 17 00:00:00 2001 From: spjspj Date: Tue, 1 Mar 2016 12:36:22 +1100 Subject: [PATCH 32/67] spjspj - Implement Vesuvan ShapeShifter (TimeSpiral) --- .../sets/timespiral/VesuvanShapeshifter.java | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/timespiral/VesuvanShapeshifter.java diff --git a/Mage.Sets/src/mage/sets/timespiral/VesuvanShapeshifter.java b/Mage.Sets/src/mage/sets/timespiral/VesuvanShapeshifter.java new file mode 100644 index 0000000000..013b3a58f1 --- /dev/null +++ b/Mage.Sets/src/mage/sets/timespiral/VesuvanShapeshifter.java @@ -0,0 +1,207 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.timespiral; + +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.AsTurnedFaceUpEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CopyEffect; +import mage.abilities.effects.common.CopyPermanentEffect; +import mage.abilities.keyword.MorphAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; +import mage.util.functions.ApplyToPermanent; + +/** + * + * @author spjspj + */ +public class VesuvanShapeshifter extends CardImpl { + + protected Ability turnFaceUpAbility = null; + private static final String effectText = "as a copy of any creature on the battlefield until {this} is turned faced down"; + + public VesuvanShapeshifter(UUID ownerId) { + super(ownerId, 90, "Vesuvan Shapeshifter", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); + this.expansionSetCode = "TSP"; + this.subtype.add("Shapeshifter"); + this.power = new MageInt(0); + this.toughness = new MageInt(0); + + // Morph {1}{U} + this.addAbility(new MorphAbility(this, new ManaCostsImpl("{1}{U}"))); + + // As Vesuvan Shapeshifter turned face up, may choose another creature. If you do, until Vesuvan Shapeshifter is turned face down, it becomes a copy of that creature + Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new AsTurnedFaceUpEffect(new VesuvanShapeshifterEffect(), false)); + ability.setWorksFaceDown(true); + this.addAbility(ability); + + // As Vesuvan Shapeshifter etbs, may choose another creature. If you do, until Vesuvan Shapeshifter is turned face down, it becomes a copy of that creature + Effect effect = new CopyPermanentEffect(new FilterCreaturePermanent()); + effect.setText(effectText); + ability = new EntersBattlefieldAbility(effect, true); + ability.setWorksFaceDown(false); + this.addAbility(ability); + + // At the beginning of your upkeep, you may turn this creature face down + effect = new VesuvanShapeshifterFaceDownEffect(); + ability = new BeginningOfUpkeepTriggeredAbility(effect, TargetController.YOU, true); + this.addAbility(ability); + } + + public VesuvanShapeshifter(final VesuvanShapeshifter card) { + super(card); + } + + @Override + public VesuvanShapeshifter copy() { + return new VesuvanShapeshifter(this); + } +} + +class VesuvanShapeshifterEffect extends OneShotEffect { + + public VesuvanShapeshifterEffect() { + super(Outcome.Copy); + staticText = "have {this} become a copy of a creature and gain this ability"; + } + + public VesuvanShapeshifterEffect(final VesuvanShapeshifterEffect effect) { + super(effect); + } + + @Override + public VesuvanShapeshifterEffect copy() { + return new VesuvanShapeshifterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + + ApplyToPermanent vesuvanShapeShifterFaceUpApplier = new ApplyToPermanent() { + @Override + public Boolean apply(Game game, Permanent permanent) { + Effect effect = new VesuvanShapeshifterFaceDownEffect(); + Ability ability = new BeginningOfUpkeepTriggeredAbility(effect, TargetController.YOU, true); + permanent.getAbilities().add(ability); + permanent.addAbility(new MorphAbility(permanent, new ManaCostsImpl("{1}{U}")), permanent.getId(), game); + return true; + } + + @Override + public Boolean apply(Game game, MageObject mageObject) { + Effect effect = new VesuvanShapeshifterFaceDownEffect(); + Ability ability = new BeginningOfUpkeepTriggeredAbility(effect, TargetController.YOU, true); + mageObject.getAbilities().add(ability); + return true; + } + }; + + Permanent copyToCreature = game.getPermanent(source.getSourceId()); + if (copyToCreature != null) { + FilterCreaturePermanent filter = new FilterCreaturePermanent("another creature"); + filter.add(new AnotherPredicate()); + + TargetCreaturePermanent target = new TargetCreaturePermanent(0, 1, filter, false); + + if (controller.chooseTarget(Outcome.BecomeCreature, target, source, game) && !target.getTargets().isEmpty()) { + Permanent copyFromCreature = game.getPermanentOrLKIBattlefield(target.getFirstTarget()); + if (copyFromCreature != null) { + game.copyPermanent(Duration.Custom, copyFromCreature, copyToCreature.getId(), source, vesuvanShapeShifterFaceUpApplier); + source.getTargets().clear(); + return true; + } + } + } + return false; + } +} + +class VesuvanShapeshifterFaceDownEffect extends OneShotEffect { + + public VesuvanShapeshifterFaceDownEffect() { + super(Outcome.Copy); + staticText = "have {this} become a morphed, faced down creature"; + } + + public VesuvanShapeshifterFaceDownEffect(final VesuvanShapeshifterFaceDownEffect effect) { + super(effect); + } + + @Override + public VesuvanShapeshifterFaceDownEffect copy() { + return new VesuvanShapeshifterFaceDownEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + + Permanent permanent = game.getPermanent(source.getSourceId()); + if (controller != null && permanent != null) { + permanent.removeAllAbilities(source.getSourceId(), game); + + // Set any previous copy effects to 'discarded' + for (Effect effect : game.getState().getContinuousEffects().getLayeredEffects(game)) { + if (effect instanceof CopyEffect) { + CopyEffect copyEffect = (CopyEffect) effect; + if (copyEffect.getSourceId().equals(permanent.getId())) { + copyEffect.discard(); + } + } + } + + permanent.turnFaceDown(game, source.getControllerId()); + permanent.setManifested(false); + permanent.setMorphed(true); + return permanent.isFaceDown(game); + } + + return false; + } +} From 0e2e4ccedd6e4ee47091f1e117c0448f79d9cc9b Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 1 Mar 2016 15:36:23 +0100 Subject: [PATCH 33/67] * Crib Swap - Fixed that the created token was ehite instead of colorless. --- Mage.Sets/src/mage/sets/lorwyn/CribSwap.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/Mage.Sets/src/mage/sets/lorwyn/CribSwap.java b/Mage.Sets/src/mage/sets/lorwyn/CribSwap.java index be6f4c2804..fb62020e18 100644 --- a/Mage.Sets/src/mage/sets/lorwyn/CribSwap.java +++ b/Mage.Sets/src/mage/sets/lorwyn/CribSwap.java @@ -39,7 +39,6 @@ import mage.constants.Outcome; import mage.constants.Rarity; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.token.SpiritWhiteToken; import mage.game.permanent.token.Token; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; @@ -55,7 +54,6 @@ public class CribSwap extends CardImpl { this.expansionSetCode = "LRW"; this.subtype.add("Shapeshifter"); - // Changeling this.addAbility(ChangelingAbility.getInstance()); // Exile target creature. Its controller puts a 1/1 colorless Shapeshifter creature token with changeling onto the battlefield. @@ -112,7 +110,6 @@ class CribSwapShapeshifterWhiteToken extends Token { this.setOriginalExpansionSetCode("LRW"); cardType.add(CardType.CREATURE); subtype.add("Shapeshifter"); - color.setWhite(true); power = new MageInt(1); toughness = new MageInt(1); addAbility(ChangelingAbility.getInstance()); From 5b15f96342c8aac61622e922319eec70c5ce1734 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 1 Mar 2016 15:38:50 +0100 Subject: [PATCH 34/67] Fixed some problems of CastFromHandCondition. --- .../condition/common/CastFromHandCondition.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java index 1abc7306f5..069f736716 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java @@ -2,8 +2,10 @@ package mage.abilities.condition.common; import mage.abilities.Ability; import mage.abilities.condition.Condition; +import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; import mage.watchers.Watcher; /** @@ -15,11 +17,21 @@ public class CastFromHandCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); + Permanent permanent = game.getPermanentEntering(source.getSourceId()); + int zccDiff = 0; if (permanent == null) { - permanent = game.getPermanentEntering(source.getSourceId()); + permanent = game.getPermanentOrLKIBattlefield(source.getSourceId()); // can be alredy again removed from battlefield so also check LKI + zccDiff = -1; } if (permanent != null) { + // check that the spell is still in the LKI + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (spell == null || spell.getZoneChangeCounter(game) != permanent.getZoneChangeCounter(game) + zccDiff) { + if (game.getLastKnownInformation(source.getSourceId(), Zone.STACK, permanent.getZoneChangeCounter(game) + zccDiff) == null) { + return false; + } + } + // Probably watcher is no longer needed Watcher watcher = game.getState().getWatchers().get("CastFromHand", source.getSourceId()); if (watcher != null && watcher.conditionMet()) { return true; From b19b43c4c89a0ff884edf6458c151b55ac2e3216 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 1 Mar 2016 15:39:37 +0100 Subject: [PATCH 35/67] * Scion of Vitu-Ghazi - Fixed that conditional trigger was not handled correctly. --- .../sets/dragonsmaze/ScionOfVituGhazi.java | 19 ++++------ .../EntersTheBattlefieldTriggerTest.java | 36 +++++++++++++++++++ .../effects/common/PopulateEffect.java | 34 ++++++++---------- 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java b/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java index 23e1bcc6bd..6453ca233b 100644 --- a/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java +++ b/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java @@ -25,15 +25,14 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.sets.dragonsmaze; import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; +import mage.abilities.TriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.CastFromHandCondition; -import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.PopulateEffect; import mage.cards.CardImpl; @@ -46,11 +45,9 @@ import mage.watchers.common.CastFromHandWatcher; * * @author LevelX2 */ - - public class ScionOfVituGhazi extends CardImpl { - public ScionOfVituGhazi (UUID ownerId) { + public ScionOfVituGhazi(UUID ownerId) { super(ownerId, 7, "Scion of Vitu-Ghazi", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{3}{W}{W}"); this.expansionSetCode = "DGM"; this.subtype.add("Elemental"); @@ -58,15 +55,13 @@ public class ScionOfVituGhazi extends CardImpl { this.power = new MageInt(4); this.toughness = new MageInt(4); - // When Scion of Vitu-Ghazi enters the battlefield, if you cast it from your hand, put a 1/1 white Bird creature token with flying onto the battlefield, then populate. - Ability ability = new EntersBattlefieldTriggeredAbility( - new ConditionalOneShotEffect(new CreateTokenEffect(new BirdToken()), new CastFromHandCondition(), - "if you cast it from your hand, put a 1/1 white Bird creature token with flying onto the battlefield,")); + TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new BirdToken()), false); ability.addEffect(new PopulateEffect("then")); - this.addAbility(ability, new CastFromHandWatcher()); + this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandCondition(), + "When {this} enters the battlefield, if you cast it from your hand, put a 1/1 white Bird creature token with flying onto the battlefield, then populate."), new CastFromHandWatcher()); } - public ScionOfVituGhazi (final ScionOfVituGhazi card) { + public ScionOfVituGhazi(final ScionOfVituGhazi card) { super(card); } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java index d6950d92b9..4f4fc2a62d 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java @@ -95,4 +95,40 @@ public class EntersTheBattlefieldTriggerTest extends CardTestPlayerBase { assertLife(playerB, 17); } + /** + * Scion of Vitu-Ghazi if it is NOT cast from the hand, it will still allow + * the Populate effect. It should only allow these when it is cast from + * hand. + * + */ + @Test + public void testScionOfVituGhaziConditionNotTrue() { + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + // When Scion of Vitu-Ghazi enters the battlefield, if you cast it from your hand, put a 1/1 white Bird creature token with flying onto the battlefield, then populate. + addCard(Zone.HAND, playerA, "Scion of Vitu-Ghazi", 1); // 4/4 - {3}{W}{W} + // Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost. + addCard(Zone.HAND, playerA, "Reanimate", 1); // {B} + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + // Destroy target nonartifact, nonblack creature. It can't be regenerated. + addCard(Zone.HAND, playerB, "Terror", 1); // {1}{B} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scion of Vitu-Ghazi"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Terror", "Scion of Vitu-Ghazi"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Reanimate", "Scion of Vitu-Ghazi"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, "Terror", 1); + + assertGraveyardCount(playerA, "Reanimate", 1); + assertPermanentCount(playerA, "Scion of Vitu-Ghazi", 1); + assertPermanentCount(playerA, "Bird", 2); // only 2 from cast from hand creation and populate. Populate may not trigger from reanimate + + assertLife(playerA, 15); + assertLife(playerB, 20); + } + } diff --git a/Mage/src/main/java/mage/abilities/effects/common/PopulateEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PopulateEffect.java index c2b7d005da..1eb5026f2b 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PopulateEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PopulateEffect.java @@ -1,16 +1,16 @@ /* * Copyright 2011 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 @@ -20,7 +20,7 @@ * 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. @@ -37,36 +37,28 @@ import mage.filter.predicate.permanent.ControllerPredicate; import mage.filter.predicate.permanent.TokenPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentToken; -import mage.game.permanent.token.Token; import mage.players.Player; import mage.target.Target; import mage.target.TargetPermanent; import mage.target.targetpointer.FixedTarget; -import mage.util.CardUtil; - - /** * * @author LevelX2 */ - // // 701.27. Populate // -// 701.27a To populate means to choose a creature token you control and put a +// 701.27a To populate means to choose a creature token you control and put a // token onto the battlefield that’s a copy of that creature token. // -// 701.27b If you control no creature tokens when instructed to populate, you +// 701.27b If you control no creature tokens when instructed to populate, you // won’t put a token onto the battlefield. // - - public class PopulateEffect extends OneShotEffect { private static final FilterPermanent filter = new FilterPermanent("token for populate"); - + static { filter.add(new TokenPredicate()); filter.add(new ControllerPredicate(TargetController.YOU)); @@ -75,10 +67,10 @@ public class PopulateEffect extends OneShotEffect { public PopulateEffect() { this(""); } - + public PopulateEffect(String prefixText) { super(Outcome.Copy); - this.staticText = (prefixText.length()>0?prefixText+" p":"P")+"opulate (Put a token onto the battlefield that's a copy of a creature token you control.)"; + this.staticText = (prefixText.length() > 0 ? prefixText + " p" : "P") + "opulate (Put a token onto the battlefield that's a copy of a creature token you control.)"; } public PopulateEffect(final PopulateEffect effect) { @@ -94,13 +86,15 @@ public class PopulateEffect extends OneShotEffect { player.choose(Outcome.Copy, target, source.getSourceId(), game); Permanent tokenToCopy = game.getPermanent(target.getFirstTarget()); if (tokenToCopy != null) { - if (!game.isSimulation()) + if (!game.isSimulation()) { game.informPlayers("Token selected for populate: " + tokenToCopy.getLogName()); + } Effect effect = new PutTokenOntoBattlefieldCopyTargetEffect(); effect.setTargetPointer(new FixedTarget(target.getFirstTarget())); return effect.apply(game, source); } } + return true; } return false; } @@ -109,4 +103,4 @@ public class PopulateEffect extends OneShotEffect { public PopulateEffect copy() { return new PopulateEffect(this); } -} \ No newline at end of file +} From 570a6d92dcaef087b8f51f81cb8823916e693947 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 1 Mar 2016 17:00:02 +0100 Subject: [PATCH 36/67] Foxed some EntersBattlefieldTriggeredAbilities with Intervening-If-Clause not handled correctly. --- .../commander2014/AngelOfTheDireHour.java | 12 +++--- .../commander2014/BreachingLeviathan.java | 13 +++--- .../mage/sets/darksteel/FurnaceDragon.java | 41 +++++-------------- .../sets/divinevsdemonic/ReiverDemon.java | 18 ++++---- .../dragonsoftarkir/DeathbringerRegent.java | 39 ++++++------------ .../sets/saviorsofkamigawa/InameAsOne.java | 13 +++--- .../mage/sets/sorinvstibalt/CoalStoker.java | 14 +++---- .../common/CastFromHandCondition.java | 7 ++-- .../watchers/common/CastFromHandWatcher.java | 35 ++++++++++++++-- 9 files changed, 93 insertions(+), 99 deletions(-) diff --git a/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java b/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java index 40d5609c62..04336e553d 100644 --- a/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java +++ b/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java @@ -29,10 +29,9 @@ package mage.sets.commander2014; import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.CastFromHandCondition; -import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ExileAllEffect; import mage.abilities.keyword.FlashAbility; import mage.abilities.keyword.FlyingAbility; @@ -61,10 +60,11 @@ public class AngelOfTheDireHour extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); // When Angel of the Dire Hour enters the battlefield, if you cast it from your hand, exile all attacking creatures. - Ability ability = new EntersBattlefieldTriggeredAbility( - new ConditionalOneShotEffect(new ExileAllEffect(new FilterAttackingCreature("attacking creatures")), new CastFromHandCondition(), - " if you cast it from your hand, exile all attacking creatures")); - this.addAbility(ability, new CastFromHandWatcher()); + this.addAbility(new ConditionalTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new ExileAllEffect(new FilterAttackingCreature("attacking creatures")), false), + new CastFromHandCondition(), + "When {this} enters the battlefield, if you cast it from your hand, exile all attacking creatures."), + new CastFromHandWatcher()); } public AngelOfTheDireHour(final AngelOfTheDireHour card) { diff --git a/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java b/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java index 9e81434115..e8e24c4d57 100644 --- a/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java +++ b/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java @@ -33,7 +33,7 @@ import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.CastFromHandCondition; -import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; @@ -64,10 +64,11 @@ public class BreachingLeviathan extends CardImpl { this.toughness = new MageInt(9); // When Breaching Leviathan enters the battlefield, if you cast it from your hand, tap all nonblue creatures. Those creatures don't untap during their controllers' next untap steps. - Ability ability = new EntersBattlefieldTriggeredAbility( - new ConditionalOneShotEffect(new BreachingLeviathanEffect(), new CastFromHandCondition(), - "if you cast it from your hand, tap all nonblue creatures. Those creatures don't untap during their controllers' next untap steps")); - this.addAbility(ability, new CastFromHandWatcher()); + this.addAbility(new ConditionalTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new BreachingLeviathanEffect(), false), + new CastFromHandCondition(), + "When {this} enters the battlefield, if you cast it from your hand, tap all nonblue creatures. Those creatures don't untap during their controllers' next untap steps."), + new CastFromHandWatcher()); } public BreachingLeviathan(final BreachingLeviathan card) { @@ -104,7 +105,7 @@ class BreachingLeviathanEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - for (Permanent creature: game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { + for (Permanent creature : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source.getSourceId(), game)) { creature.tap(game); ContinuousEffect effect = new DontUntapInControllersNextUntapStepTargetEffect(); effect.setTargetPointer(new FixedTarget(creature.getId())); diff --git a/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java b/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java index 8c7f749d57..0e9d02637b 100644 --- a/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java +++ b/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java @@ -29,10 +29,9 @@ package mage.sets.darksteel; import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.Condition; -import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ExileAllEffect; import mage.abilities.keyword.AffinityForArtifactsAbility; import mage.abilities.keyword.FlyingAbility; @@ -41,9 +40,6 @@ import mage.constants.CardType; import mage.constants.Rarity; import mage.filter.FilterPermanent; import mage.filter.predicate.mageobject.CardTypePredicate; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.watchers.Watcher; import mage.watchers.common.CastFromHandWatcher; /** @@ -51,7 +47,7 @@ import mage.watchers.common.CastFromHandWatcher; * @author fireshoes */ public class FurnaceDragon extends CardImpl { - + private static final FilterPermanent filter = new FilterPermanent("artifacts"); static { @@ -67,12 +63,16 @@ public class FurnaceDragon extends CardImpl { // Affinity for artifacts this.addAbility(new AffinityForArtifactsAbility()); - + // Flying this.addAbility(FlyingAbility.getInstance()); - + // When Furnace Dragon enters the battlefield, if you cast it from your hand, exile all artifacts. - this.addAbility(new EntersBattlefieldTriggeredAbility(new ConditionalOneShotEffect(new ExileAllEffect(filter), new FurnaceDragonCondition()), false), new CastFromHandWatcher()); + this.addAbility(new ConditionalTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new ExileAllEffect(filter), false), + new CastFromHandCondition(), + "When {this} enters the battlefield, if you cast it from your hand, exile all artifacts."), + new CastFromHandWatcher()); } public FurnaceDragon(final FurnaceDragon card) { @@ -84,24 +84,3 @@ public class FurnaceDragon extends CardImpl { return new FurnaceDragon(this); } } - -class FurnaceDragonCondition implements Condition { - - @Override - public boolean apply(Game game, Ability source) { - boolean applies = false; - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { - Watcher watcher = game.getState().getWatchers().get("CastFromHand", source.getSourceId()); - if (watcher != null && watcher.conditionMet()) { - applies = true; - } - } - return applies; - } - - @Override - public String toString() { - return "you cast it from your hand"; - } -} diff --git a/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java b/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java index 92b37c9990..1278473e69 100644 --- a/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java +++ b/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java @@ -30,10 +30,9 @@ package mage.sets.divinevsdemonic; import java.util.UUID; import mage.MageInt; import mage.ObjectColor; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.CastFromHandCondition; -import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; @@ -50,9 +49,9 @@ import mage.watchers.common.CastFromHandWatcher; * @author daagar */ public class ReiverDemon extends CardImpl { - + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("nonartifact, nonblack creatures"); - + static { filter.add(Predicates.not(new CardTypePredicate(CardType.ARTIFACT))); filter.add(Predicates.not(new ColorPredicate(ObjectColor.BLACK))); @@ -67,12 +66,13 @@ public class ReiverDemon extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - + // When Reiver Demon enters the battlefield, if you cast it from your hand, destroy all nonartifact, nonblack creatures. They can't be regenerated. - Ability ability = new EntersBattlefieldTriggeredAbility( - new ConditionalOneShotEffect(new DestroyAllEffect(filter), new CastFromHandCondition(), - "if you cast it from your hand, destroy all nonartifact, nonblack creatures. They can't be regenerated")); - this.addAbility(ability, new CastFromHandWatcher()); + this.addAbility(new ConditionalTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new DestroyAllEffect(filter, true), false), + new CastFromHandCondition(), + "When {this} enters the battlefield, if you cast it from your hand, destroy all nonartifact, nonblack creatures. They can't be regenerated."), + new CastFromHandWatcher()); } public ReiverDemon(final ReiverDemon card) { diff --git a/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java b/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java index e7da53ee3e..b4b5e194f9 100644 --- a/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java +++ b/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java @@ -32,7 +32,8 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.Condition; -import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; @@ -41,8 +42,6 @@ import mage.constants.Rarity; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.permanent.AnotherPredicate; import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.watchers.Watcher; import mage.watchers.common.CastFromHandWatcher; /** @@ -50,9 +49,9 @@ import mage.watchers.common.CastFromHandWatcher; * @author jeffwadsworth */ public class DeathbringerRegent extends CardImpl { - + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("other creatures"); - + static { filter.add(new AnotherPredicate()); } @@ -66,10 +65,13 @@ public class DeathbringerRegent extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - + // When Deathbringer Regent enters the battlefield, if you cast it from your hand and there are five or more other creatures on the battlefield, destroy all other creatures. - this.addAbility(new EntersBattlefieldTriggeredAbility(new ConditionalOneShotEffect(new DestroyAllEffect(filter), new DeathbringerRegentCondition()), false), new CastFromHandWatcher()); - + this.addAbility(new ConditionalTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new DestroyAllEffect(filter), false), + new DeathbringerRegentCondition(), + "When {this} enters the battlefield, if you cast it from your hand and there are five or more other creatures on the battlefield, destroy all other creatures."), + new CastFromHandWatcher()); } public DeathbringerRegent(final DeathbringerRegent card) { @@ -86,22 +88,7 @@ class DeathbringerRegentCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - boolean applies = false; - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { - Watcher watcher = game.getState().getWatchers().get("CastFromHand", source.getSourceId()); - if (watcher != null && watcher.conditionMet()) { - applies = true; - } - } - if (applies) { - applies = game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), game).size() >= 6; - } - return applies; + return new CastFromHandCondition().apply(game, source) + && game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), game).size() >= 6; } - - @Override - public String toString() { - return "you cast it from your hand and there are five or more other creatures on the battlefield"; - } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java b/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java index ddb5f7a790..769130d982 100644 --- a/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java +++ b/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java @@ -34,7 +34,7 @@ import mage.abilities.Ability; import mage.abilities.common.DiesTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.CastFromHandCondition; -import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ExileSourceEffect; @@ -75,13 +75,14 @@ public class InameAsOne extends CardImpl { this.toughness = new MageInt(8); // When Iname as One enters the battlefield, if you cast it from your hand, you may search your library for a Spirit permanent card, put it onto the battlefield, then shuffle your library. - Ability ability = new EntersBattlefieldTriggeredAbility( - new ConditionalOneShotEffect(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 1, filter), false), - new CastFromHandCondition())); - this.addAbility(ability, new CastFromHandWatcher()); + this.addAbility(new ConditionalTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 1, filter)), true), + new CastFromHandCondition(), + "When {this} enters the battlefield, if you cast it from your hand, you may search your library for a Spirit permanent card, put it onto the battlefield, then shuffle your library."), + new CastFromHandWatcher()); // When Iname as One dies, you may exile it. If you do, return target Spirit permanent card from your graveyard to the battlefield. - ability = new DiesTriggeredAbility(new InameAsOneEffect(), false); + Ability ability = new DiesTriggeredAbility(new InameAsOneEffect(), false); ability.addTarget(new TargetCardInYourGraveyard(filter)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java b/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java index 85b2aa3217..403d9c7a95 100644 --- a/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java +++ b/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java @@ -30,16 +30,13 @@ package mage.sets.sorinvstibalt; import java.util.UUID; import mage.MageInt; import mage.Mana; -import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.common.CastFromHandCondition; -import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.BasicManaEffect; -import mage.abilities.effects.common.ExileAllEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; -import mage.filter.common.FilterAttackingCreature; import mage.watchers.common.CastFromHandWatcher; /** @@ -56,10 +53,11 @@ public class CoalStoker extends CardImpl { this.toughness = new MageInt(3); // When Coal Stoker enters the battlefield, if you cast it from your hand, add {R}{R}{R} to your mana pool. - Ability ability = new EntersBattlefieldTriggeredAbility( - new ConditionalOneShotEffect(new BasicManaEffect(new Mana(3, 0, 0, 0, 0, 0, 0, 0)), new CastFromHandCondition(), - " if you cast it from your hand, add {R}{R}{R} to your mana pool.")); - this.addAbility(ability, new CastFromHandWatcher()); + this.addAbility(new ConditionalTriggeredAbility( + new EntersBattlefieldTriggeredAbility(new BasicManaEffect(new Mana(3, 0, 0, 0, 0, 0, 0, 0)), false), + new CastFromHandCondition(), + "When {this} enters the battlefield, if you cast it from your hand, add {R}{R}{R} to your mana pool."), + new CastFromHandWatcher()); } public CoalStoker(final CoalStoker card) { diff --git a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java index 069f736716..8c36bb8bb6 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java @@ -6,7 +6,7 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; import mage.game.stack.Spell; -import mage.watchers.Watcher; +import mage.watchers.common.CastFromHandWatcher; /** * Warning: CastFromHandWatcher must be installed to card for proper working. @@ -31,9 +31,8 @@ public class CastFromHandCondition implements Condition { return false; } } - // Probably watcher is no longer needed - Watcher watcher = game.getState().getWatchers().get("CastFromHand", source.getSourceId()); - if (watcher != null && watcher.conditionMet()) { + CastFromHandWatcher watcher = (CastFromHandWatcher) game.getState().getWatchers().get(CastFromHandWatcher.class.getName(), source.getSourceId()); + if (watcher != null && watcher.spellWasCastFromHand(source.getSourceId()) { return true; } } diff --git a/Mage/src/main/java/mage/watchers/common/CastFromHandWatcher.java b/Mage/src/main/java/mage/watchers/common/CastFromHandWatcher.java index 3aee2d79fd..805423c19f 100644 --- a/Mage/src/main/java/mage/watchers/common/CastFromHandWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/CastFromHandWatcher.java @@ -1,15 +1,23 @@ package mage.watchers.common; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.stack.Spell; +import mage.game.turn.Step; import mage.watchers.Watcher; public class CastFromHandWatcher extends Watcher { + + private final Set spellsCastFromHand = new HashSet<>(); + private Step step; + public CastFromHandWatcher() { - super("CastFromHand", WatcherScope.CARD); + super(CastFromHandWatcher.class.getName(), WatcherScope.GAME); } public CastFromHandWatcher(final CastFromHandWatcher watcher) { @@ -18,14 +26,35 @@ public class CastFromHandWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.SPELL_CAST && event.getZone() == Zone.HAND) { + /** + * This does still not handle if a spell is cast from hand and comes to + * play from other zones during the same step. But at least the state is + * reset if the game comes to a new step + */ + + if (step != null && game.getTurn().getStep() != step) { + spellsCastFromHand.clear(); + step = null; + } + if (event.getType() == GameEvent.EventType.SPELL_CAST && event.getZone().equals(Zone.HAND)) { + step = game.getTurn().getStep(); Spell spell = (Spell) game.getObject(event.getTargetId()); if (this.getSourceId().equals(spell.getSourceId())) { - condition = true; + condition = true; } } } + public boolean spellWasCastFromHand(UUID sourceId) { + return spellsCastFromHand.contains(sourceId); + } + + @Override + public void reset() { + super.reset(); + spellsCastFromHand.clear(); + } + @Override public CastFromHandWatcher copy() { return new CastFromHandWatcher(this); From ad49eeb4ea8b7af616f3802a3e7578aec7f798c0 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 1 Mar 2016 21:18:53 +0100 Subject: [PATCH 37/67] Fixed error of CastFromHandCondition. --- .../mage/abilities/condition/common/CastFromHandCondition.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java index 8c36bb8bb6..cdfa477253 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java @@ -32,7 +32,7 @@ public class CastFromHandCondition implements Condition { } } CastFromHandWatcher watcher = (CastFromHandWatcher) game.getState().getWatchers().get(CastFromHandWatcher.class.getName(), source.getSourceId()); - if (watcher != null && watcher.spellWasCastFromHand(source.getSourceId()) { + if (watcher != null && watcher.spellWasCastFromHand(source.getSourceId())) { return true; } } From 6299425c1d846631bc8d0a535509c8f541cb9984 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 1 Mar 2016 21:38:52 +0100 Subject: [PATCH 38/67] Fixed error of CastFromHandCondition. --- Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java | 3 ++- .../mage/abilities/condition/common/CastFromHandCondition.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java b/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java index 6453ca233b..4c5ea06c01 100644 --- a/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java +++ b/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java @@ -58,7 +58,8 @@ public class ScionOfVituGhazi extends CardImpl { TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new BirdToken()), false); ability.addEffect(new PopulateEffect("then")); this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandCondition(), - "When {this} enters the battlefield, if you cast it from your hand, put a 1/1 white Bird creature token with flying onto the battlefield, then populate."), new CastFromHandWatcher()); + "When {this} enters the battlefield, if you cast it from your hand, put a 1/1 white Bird creature token with flying onto the battlefield, then populate."), + new CastFromHandWatcher()); } public ScionOfVituGhazi(final ScionOfVituGhazi card) { diff --git a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java index cdfa477253..d14fc200ac 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java @@ -31,7 +31,7 @@ public class CastFromHandCondition implements Condition { return false; } } - CastFromHandWatcher watcher = (CastFromHandWatcher) game.getState().getWatchers().get(CastFromHandWatcher.class.getName(), source.getSourceId()); + CastFromHandWatcher watcher = (CastFromHandWatcher) game.getState().getWatchers().get(CastFromHandWatcher.class.getName()); if (watcher != null && watcher.spellWasCastFromHand(source.getSourceId())) { return true; } From 385313bd64bf9472b6ab746006bedf5ceafe9fcb Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 1 Mar 2016 21:41:05 +0100 Subject: [PATCH 39/67] Fixed error of CastFromHandCondition. --- .../java/mage/watchers/common/CastFromHandWatcher.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mage/src/main/java/mage/watchers/common/CastFromHandWatcher.java b/Mage/src/main/java/mage/watchers/common/CastFromHandWatcher.java index 805423c19f..da34b23909 100644 --- a/Mage/src/main/java/mage/watchers/common/CastFromHandWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/CastFromHandWatcher.java @@ -37,11 +37,11 @@ public class CastFromHandWatcher extends Watcher { step = null; } if (event.getType() == GameEvent.EventType.SPELL_CAST && event.getZone().equals(Zone.HAND)) { - step = game.getTurn().getStep(); - Spell spell = (Spell) game.getObject(event.getTargetId()); - if (this.getSourceId().equals(spell.getSourceId())) { - condition = true; + if (step == null) { + step = game.getTurn().getStep(); } + Spell spell = (Spell) game.getObject(event.getTargetId()); + spellsCastFromHand.add(spell.getSourceId()); } } From dcb9a7122bd8009d2a821188df832fd49f7b0c2d Mon Sep 17 00:00:00 2001 From: drmDev Date: Tue, 1 Mar 2016 17:26:46 -0500 Subject: [PATCH 40/67] DreadCacodemon card with ConditionalTriggeredAbility --- .../mage/sets/commander/DreadCacodemon.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/commander/DreadCacodemon.java diff --git a/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java b/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java new file mode 100644 index 0000000000..3be7b9acf2 --- /dev/null +++ b/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java @@ -0,0 +1,86 @@ +/* + * 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.commander; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.TapAllEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.watchers.common.CastFromHandWatcher; + +/** + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class DreadCacodemon extends CardImpl { + + private static final FilterCreaturePermanent opponentsCreatures = new FilterCreaturePermanent("creatures your opponents control"); + static { + opponentsCreatures.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + private static final FilterCreaturePermanent otherCreaturesYouControl = new FilterCreaturePermanent("other creatures you control"); + static { + otherCreaturesYouControl.add(new ControllerPredicate(TargetController.YOU)); + otherCreaturesYouControl.add(new AnotherPredicate()); + } + + public DreadCacodemon(UUID ownerId) { + super(ownerId, 79, "Dread Cacodemon", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{7}{B}{B}{B}"); + this.expansionSetCode = "CMD"; + this.subtype.add("Demon"); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + + // When Dread Cacodemon enters the battlefield, + // if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. + TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new DestroyAllEffect(opponentsCreatures, false)); + ability.addEffect(new TapAllEffect(otherCreaturesYouControl)); + this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandCondition(), + " if you cast it from your hand, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control."), new CastFromHandWatcher()); + } + + public DreadCacodemon(final DreadCacodemon card) { + super(card); + } + + @Override + public DreadCacodemon copy() { + return new DreadCacodemon(this); + } +} \ No newline at end of file From a9214e6d351fc22b02870bf557b15d309d3edfc9 Mon Sep 17 00:00:00 2001 From: drmDev Date: Tue, 1 Mar 2016 18:45:21 -0500 Subject: [PATCH 41/67] unit tests for Dread Cacodemon Cast from hand and Not Cast from hand --- .../EntersTheBattlefieldTriggerTest.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java index 4f4fc2a62d..1fe8be8bb1 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java @@ -130,5 +130,77 @@ public class EntersTheBattlefieldTriggerTest extends CardTestPlayerBase { assertLife(playerA, 15); assertLife(playerB, 20); } + + /** + * Dread Cacodemon's abilities should only trigger when cast from hand. + * + * Testing when cast from hand abilities take effect. + * Cast from hand destroys opponents creatures and taps all other creatures owner controls. + */ + @Test + public void testDreadCacodemonConditionTrue() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); + + // When Dread Cacodemon enters the battlefield, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. + addCard(Zone.HAND, playerA, "Dread Cacodemon", 1); // 8/8 - {7}{B}{B}{B} + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + + // Protection from white, first strike + addCard(Zone.BATTLEFIELD, playerA, "Black Knight", 2); // {B}{B} + // Deathtouch + addCard(Zone.BATTLEFIELD, playerB, "Typhoid Rats", 2); // {B} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dread Cacodemon"); + setStopAt(1, PhaseStep.END_TURN); + + execute(); + + assertPermanentCount(playerB, "Typhoid Rats", 0); + + assertPermanentCount(playerA, "Dread Cacodemon", 1); + assertPermanentCount(playerA, "Black Knight", 2); + assertTappedCount("Black Knight", true, 2); + assertTapped("Dread Cacodemon", false); + } + + /** + * Dread Cacodemon's abilities should only trigger when cast from hand. + * + * Testing when card is not cast from hand, abilities do not take effect. + * All opponents creatures remain alive and owner's creatures are not tapped. + */ + @Test + public void testDreadCacodemonConditionFalse() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); + + // When Dread Cacodemon enters the battlefield, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. + addCard(Zone.GRAVEYARD, playerA, "Dread Cacodemon", 1); // 8/8 - {7}{B}{B}{B} + // Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost. + addCard(Zone.HAND, playerA, "Reanimate", 1); // {B} + + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); + + // Protection from white, first strike + addCard(Zone.BATTLEFIELD, playerA, "Black Knight", 2); // {B}{B} + // Deathtouch + addCard(Zone.BATTLEFIELD, playerB, "Typhoid Rats", 2); // {B} + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate", "Dread Cacodemon"); + setStopAt(1, PhaseStep.END_TURN); + + execute(); + + assertPermanentCount(playerB, "Typhoid Rats", 2); + + assertGraveyardCount(playerA, "Reanimate", 1); + assertPermanentCount(playerA, "Dread Cacodemon", 1); + assertPermanentCount(playerA, "Black Knight", 2); + assertTappedCount("Black Knight", false, 2); + assertTapped("Dread Cacodemon", false); + + assertLife(playerA, 10); // loses 10 life from reanimating Dread Cacodemon at 10 CMC + assertLife(playerB, 20); + } } From bfb97672bbf12a917e41b4701006f404ea2884b4 Mon Sep 17 00:00:00 2001 From: Fenhl Date: Wed, 2 Mar 2016 06:29:03 +0000 Subject: [PATCH 42/67] Implement Wild Pair --- .../src/mage/sets/pdsslivers/WildPair.java | 52 +++++++ .../src/mage/sets/planarchaos/WildPair.java | 139 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/pdsslivers/WildPair.java create mode 100644 Mage.Sets/src/mage/sets/planarchaos/WildPair.java diff --git a/Mage.Sets/src/mage/sets/pdsslivers/WildPair.java b/Mage.Sets/src/mage/sets/pdsslivers/WildPair.java new file mode 100644 index 0000000000..700d6dbf9f --- /dev/null +++ b/Mage.Sets/src/mage/sets/pdsslivers/WildPair.java @@ -0,0 +1,52 @@ +/* + * 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.pdsslivers; + +import java.util.UUID; + +/** + * + * @author fenhl + */ +public class WildPair extends mage.sets.planarchaos.WildPair { + + public WildPair(UUID ownerId) { + super(ownerId); + this.cardNumber = 30; + this.expansionSetCode = "H09"; + } + + public WildPair(final WildPair card) { + super(card); + } + + @Override + public WildPair copy() { + return new WildPair(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/planarchaos/WildPair.java b/Mage.Sets/src/mage/sets/planarchaos/WildPair.java new file mode 100644 index 0000000000..9f47914f80 --- /dev/null +++ b/Mage.Sets/src/mage/sets/planarchaos/WildPair.java @@ -0,0 +1,139 @@ +/* + * 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.planarchaos; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.cards.CardImpl; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.Filter; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.IntComparePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author fenhl + */ +public class WildPair extends CardImpl { + + public WildPair(UUID ownerID) { + super(ownerID, 30, "Wild Pair", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}{G}"); + this.expansionSetCode = "PLC"; + + // Whenever a creature enters the battlefield, if you cast it from your hand, you may search your library for a creature card with the same total power and toughness and put it onto the battlefield. If you do, shuffle your library. + this.addAbility(new ConditionalTriggeredAbility( + new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new WildPairEffect(), new FilterCreaturePermanent("a creature"), true), + new CastFromHandCondition(), + "Whenever a creature enters the battlefield, if you cast it from your hand, you may search your library for a creature card with the same total power and toughness and put it onto the battlefield. If you do, shuffle your library." + )); + } + + public WildPair(final WildPair card) { + super(card); + } + + @Override + public WildPair copy() { + return new WildPair(this); + } +} + +class WildPairEffect extends OneShotEffect { + + public WildPairEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "search your library for a creature card with the same total power and toughness and put it onto the battlefield"; + } + + public WildPairEffect(final WildPairEffect effect) { + super(effect); + } + + @Override + public WildPairEffect copy() { + return new WildPairEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent permanent = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); + if (permanent != null) { + int totalPT = permanent.getPower().getValue() + permanent.getToughness().getValue(); + FilterCreatureCard filter = new FilterCreatureCard("creature card with total power and toughness " + totalPT); + filter.add(new TotalPowerAndToughnessPredicate(Filter.ComparisonType.Equal, totalPT)); + TargetCardInLibrary target = new TargetCardInLibrary(1, filter); + if (controller.searchLibrary(target, game)) { + if (target.getTargets().size() > 0) { + controller.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); + } + } + controller.shuffleLibrary(game); + return true; + } + } + return false; + } +} + +/** + * + * @author fenhl + */ +class TotalPowerAndToughnessPredicate extends IntComparePredicate { + + public TotalPowerAndToughnessPredicate(Filter.ComparisonType type, int value) { + super(type, value); + } + + @Override + protected int getInputValue(MageObject input) { + return input.getPower().getValue() + input.getToughness().getValue(); + } + + @Override + public String toString() { + return "TotalPowerAndToughness" + super.toString(); + } +} From 5b9a38bf228537f56bc2d9104c101a1a7dd6b5d0 Mon Sep 17 00:00:00 2001 From: rkfg Date: Wed, 2 Mar 2016 14:48:44 +0300 Subject: [PATCH 43/67] Fix first card tooltip show. --- .../mage/client/components/ColorPane.java | 65 ++++++++++++------- .../plugins/adapters/MageActionCallback.java | 30 +++++---- .../plugins/card/info/CardInfoPaneImpl.java | 41 +++++------- 3 files changed, 76 insertions(+), 60 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/components/ColorPane.java b/Mage.Client/src/main/java/mage/client/components/ColorPane.java index aa6d5be1aa..43a402ae0c 100644 --- a/Mage.Client/src/main/java/mage/client/components/ColorPane.java +++ b/Mage.Client/src/main/java/mage/client/components/ColorPane.java @@ -22,6 +22,7 @@ import mage.client.MageFrame; import mage.client.dialog.PreferencesDialog; import mage.client.util.gui.GuiDisplayUtil; import mage.components.CardInfoPane; +import mage.utils.ThreadUtils; import mage.view.CardView; /** @@ -42,37 +43,53 @@ public class ColorPane extends JEditorPane { @Override public void hyperlinkUpdate(final HyperlinkEvent e) { - tooltipDelay = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_TOOLTIPS_DELAY, 300); - if (tooltipDelay == 0) { - return; - } - String name = e.getDescription().substring(1); - CardInfo card = CardRepository.instance.findCard(name); - try { - final Component container = MageFrame.getUI().getComponent(MageComponents.POPUP_CONTAINER); - if (e.getEventType() == EventType.EXITED) { - setPopupVisibility(container, false); - } else { - CardInfoPane cardInfoPane = (CardInfoPane) MageFrame.getUI().getComponent(MageComponents.CARD_INFO_PANE); - cardInfoPane.setCard(new CardView(card.getMockCard()), container); - Point location = new Point(getLocationOnScreen().x - container.getWidth(), (int) MageFrame.getDesktop() - .getMousePosition().getY()); - Component parentComponent = MageFrame.getInstance(); - location = GuiDisplayUtil.keepComponentInsideParent(location, parentComponent.getLocationOnScreen(), container, - parentComponent); - container.setLocation(location); - setPopupVisibility(container, true); + ThreadUtils.threadPool2.submit(new Runnable() { + + @Override + public void run() { + tooltipDelay = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_TOOLTIPS_DELAY, 300); + if (tooltipDelay == 0) { + return; + } + String name = e.getDescription().substring(1); + CardInfo card = CardRepository.instance.findCard(name); + try { + final Component container = MageFrame.getUI().getComponent(MageComponents.POPUP_CONTAINER); + if (e.getEventType() == EventType.EXITED) { + setPopupVisibility(null, container, false); + } else { + CardInfoPane cardInfoPane = (CardInfoPane) MageFrame.getUI().getComponent(MageComponents.CARD_INFO_PANE); + cardInfoPane.setCard(new CardView(card.getMockCard()), container); + Point mousePosition = MageFrame.getDesktop().getMousePosition(); + int popupY = 0; + if (mousePosition == null) { // switched to another window + popupY = getLocationOnScreen().y; + } else { + popupY = mousePosition.y; + } + Point location = new Point(getLocationOnScreen().x - container.getWidth(), popupY); + Component parentComponent = MageFrame.getInstance(); + location = GuiDisplayUtil.keepComponentInsideParent(location, parentComponent.getLocationOnScreen(), + container, parentComponent); + setPopupVisibility(location, container, true); + } + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } - } catch (InterruptedException e1) { - e1.printStackTrace(); - } + }); } - private void setPopupVisibility(final Component container, final boolean show) throws InterruptedException { + private void setPopupVisibility(final Point location, final Component container, final boolean show) + throws InterruptedException { final Component c = MageFrame.getUI().getComponent(MageComponents.DESKTOP_PANE); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { + if (location != null) { + container.setLocation(location); + } container.setVisible(show); c.repaint(); } diff --git a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java index 069d3a8a8e..015280b3d3 100644 --- a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java +++ b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java @@ -189,27 +189,31 @@ public class MageActionCallback implements ActionCallback { Point location = new Point((int) data.locationOnScreen.getX() + data.popupOffsetX - 40, (int) data.locationOnScreen.getY() + data.popupOffsetY - 40); location = GuiDisplayUtil.keepComponentInsideParent(location, parentPoint, popup2, parentComponent); location.translate(-parentPoint.x, -parentPoint.y); - popupContainer.setLocation(location); ThreadUtils.sleep(200); - final Component c = MageFrame.getUI().getComponent(MageComponents.DESKTOP_PANE); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if (!popupTextWindowOpen || !enlargedWindowState.equals(EnlargedWindowState.CLOSED)) { - return; - } - popupContainer.setVisible(true); - c.repaint(); - } - } - ); + showPopup(popupContainer, location); } catch (InterruptedException e) { LOGGER.warn(e.getMessage()); } } + + public void showPopup(final Component popupContainer, final Point location) throws InterruptedException { + final Component c = MageFrame.getUI().getComponent(MageComponents.DESKTOP_PANE); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (!popupTextWindowOpen || !enlargedWindowState.equals(EnlargedWindowState.CLOSED)) { + return; + } + popupContainer.setLocation(location); + popupContainer.setVisible(true); + c.repaint(); + } + } + ); + } }); } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java b/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java index 19a33bed51..1d2e242aa2 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java @@ -59,33 +59,28 @@ public class CardInfoPaneImpl extends JEditorPane implements CardInfoPane { } currentCard = card; - ThreadUtils.threadPool.submit(new Runnable() { - @Override - public void run() { - try { + try { + if (!card.equals(currentCard)) { + return; + } + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { if (!card.equals(currentCard)) { return; } - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if (!card.equals(currentCard)) { - return; - } - TextLines textLines = GuiDisplayUtil.getTextLinesfromCardView(card); - StringBuilder buffer = GuiDisplayUtil.getRulefromCardView(card, textLines); - resizeTooltipIfNeeded(container, textLines.basicTextLength, textLines.lines.size()); - setText(buffer.toString()); - setCaretPosition(0); - } - }); - - } catch (Exception e) { - e.printStackTrace(); + TextLines textLines = GuiDisplayUtil.getTextLinesfromCardView(card); + StringBuilder buffer = GuiDisplayUtil.getRulefromCardView(card, textLines); + resizeTooltipIfNeeded(container, textLines.basicTextLength, textLines.lines.size()); + setText(buffer.toString()); + setCaretPosition(0); } - } - }); + }); + + } catch (Exception e) { + e.printStackTrace(); + } } private void resizeTooltipIfNeeded(Component container, int ruleLength, int rules) { From ff0dbba539aeaa290d2b4072bb834170f2b8311d Mon Sep 17 00:00:00 2001 From: drmDev Date: Wed, 2 Mar 2016 07:46:02 -0500 Subject: [PATCH 44/67] tooltip change to include when {this} enters the battlefield --- .../mage/sets/commander/DreadCacodemon.java | 170 +++++++++--------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java b/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java index 3be7b9acf2..0af6bfc333 100644 --- a/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java +++ b/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java @@ -1,86 +1,86 @@ -/* - * 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.commander; - -import java.util.UUID; -import mage.MageInt; -import mage.abilities.TriggeredAbility; -import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; -import mage.abilities.decorator.ConditionalTriggeredAbility; -import mage.abilities.effects.common.DestroyAllEffect; -import mage.abilities.effects.common.TapAllEffect; -import mage.cards.CardImpl; -import mage.constants.CardType; -import mage.constants.Rarity; -import mage.constants.TargetController; -import mage.filter.common.FilterCreaturePermanent; -import mage.filter.predicate.permanent.AnotherPredicate; -import mage.filter.predicate.permanent.ControllerPredicate; -import mage.watchers.common.CastFromHandWatcher; - -/** - * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) - */ -public class DreadCacodemon extends CardImpl { - - private static final FilterCreaturePermanent opponentsCreatures = new FilterCreaturePermanent("creatures your opponents control"); - static { - opponentsCreatures.add(new ControllerPredicate(TargetController.OPPONENT)); - } - - private static final FilterCreaturePermanent otherCreaturesYouControl = new FilterCreaturePermanent("other creatures you control"); - static { - otherCreaturesYouControl.add(new ControllerPredicate(TargetController.YOU)); - otherCreaturesYouControl.add(new AnotherPredicate()); - } - - public DreadCacodemon(UUID ownerId) { - super(ownerId, 79, "Dread Cacodemon", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{7}{B}{B}{B}"); - this.expansionSetCode = "CMD"; - this.subtype.add("Demon"); - this.power = new MageInt(8); - this.toughness = new MageInt(8); - - // When Dread Cacodemon enters the battlefield, - // if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. - TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new DestroyAllEffect(opponentsCreatures, false)); - ability.addEffect(new TapAllEffect(otherCreaturesYouControl)); - this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandCondition(), - " if you cast it from your hand, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control."), new CastFromHandWatcher()); - } - - public DreadCacodemon(final DreadCacodemon card) { - super(card); - } - - @Override - public DreadCacodemon copy() { - return new DreadCacodemon(this); - } +/* + * 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.commander; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.common.DestroyAllEffect; +import mage.abilities.effects.common.TapAllEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.AnotherPredicate; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.watchers.common.CastFromHandWatcher; + +/** + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class DreadCacodemon extends CardImpl { + + private static final FilterCreaturePermanent opponentsCreatures = new FilterCreaturePermanent("creatures your opponents control"); + static { + opponentsCreatures.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + private static final FilterCreaturePermanent otherCreaturesYouControl = new FilterCreaturePermanent("other creatures you control"); + static { + otherCreaturesYouControl.add(new ControllerPredicate(TargetController.YOU)); + otherCreaturesYouControl.add(new AnotherPredicate()); + } + + public DreadCacodemon(UUID ownerId) { + super(ownerId, 79, "Dread Cacodemon", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{7}{B}{B}{B}"); + this.expansionSetCode = "CMD"; + this.subtype.add("Demon"); + this.power = new MageInt(8); + this.toughness = new MageInt(8); + + // When Dread Cacodemon enters the battlefield, + // if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. + TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new DestroyAllEffect(opponentsCreatures, false)); + ability.addEffect(new TapAllEffect(otherCreaturesYouControl)); + this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandCondition(), + "When {this} enters the battlefield, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control."), new CastFromHandWatcher()); + } + + public DreadCacodemon(final DreadCacodemon card) { + super(card); + } + + @Override + public DreadCacodemon copy() { + return new DreadCacodemon(this); + } } \ No newline at end of file From d481c124eae62b73554b9b3d99e6bdad41457da9 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 2 Mar 2016 19:46:10 +0100 Subject: [PATCH 45/67] [SOI] Added Eerie Interlude and Stiched Mangler. --- .../sets/blessedvscursed/EerieInterlude.java | 125 ++++++++++++++++++ .../shadowsoverinnistrad/EerieInterlude.java | 52 ++++++++ .../shadowsoverinnistrad/StichedMangler.java | 82 ++++++++++++ 3 files changed, 259 insertions(+) create mode 100644 Mage.Sets/src/mage/sets/blessedvscursed/EerieInterlude.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/EerieInterlude.java create mode 100644 Mage.Sets/src/mage/sets/shadowsoverinnistrad/StichedMangler.java diff --git a/Mage.Sets/src/mage/sets/blessedvscursed/EerieInterlude.java b/Mage.Sets/src/mage/sets/blessedvscursed/EerieInterlude.java new file mode 100644 index 0000000000..d903464bbc --- /dev/null +++ b/Mage.Sets/src/mage/sets/blessedvscursed/EerieInterlude.java @@ -0,0 +1,125 @@ +/* + * 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.blessedvscursed; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlTargetEffect; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.targetpointer.FixedTargets; +import mage.util.CardUtil; + +/** + * + * @author LevelX2 + */ +public class EerieInterlude extends CardImpl { + + public EerieInterlude(UUID ownerId) { + super(ownerId, 8, "Eerie Interlude", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{2}{W}"); + this.expansionSetCode = "DDQ"; + + // Exile any number of target creatures you control. Return those cards to the battlefield under their owner's control at the beginning of the next end step. + this.getSpellAbility().addEffect(new EerieInterludeEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent(0, Integer.MAX_VALUE, new FilterControlledCreaturePermanent(), false)); + + } + + public EerieInterlude(final EerieInterlude card) { + super(card); + } + + @Override + public EerieInterlude copy() { + return new EerieInterlude(this); + } +} + +class EerieInterludeEffect extends OneShotEffect { + + public EerieInterludeEffect() { + super(Outcome.Neutral); + staticText = "Exile any number of target creatures you control. Return those cards to the battlefield under their owner's control at the beginning of the next end step"; + } + + public EerieInterludeEffect(final EerieInterludeEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (sourceObject != null && controller != null) { + Set toExile = new HashSet<>(); + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + Permanent targetCreature = game.getPermanent(targetId); + if (targetCreature != null) { + toExile.add(targetCreature); + } + } + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + controller.moveCardsToExile(toExile, source, game, true, exileId, sourceObject.getIdName()); + + Cards cardsToReturn = new CardsImpl(); + for (Card exiled : toExile) { + if (((Permanent) exiled).getZoneChangeCounter(game) == game.getState().getZoneChangeCounter(exiled.getId()) - 1) { + cardsToReturn.add(exiled); + } + } + Effect effect = new ReturnToBattlefieldUnderOwnerControlTargetEffect(); + effect.setTargetPointer(new FixedTargets(cardsToReturn, game)); + AtTheBeginOfNextEndStepDelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect); + game.addDelayedTriggeredAbility(delayedAbility, source); + return true; + } + return false; + } + + @Override + public EerieInterludeEffect copy() { + return new EerieInterludeEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EerieInterlude.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EerieInterlude.java new file mode 100644 index 0000000000..04d7a17e12 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/EerieInterlude.java @@ -0,0 +1,52 @@ +/* + * 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.UUID; + +/** + * + * @author LevelX2 + */ +public class EerieInterlude extends mage.sets.blessedvscursed.EerieInterlude { + + public EerieInterlude(UUID ownerId) { + super(ownerId); + this.cardNumber = 994; //TODO: Fix card number + this.expansionSetCode = "SOI"; + } + + public EerieInterlude(final EerieInterlude card) { + super(card); + } + + @Override + public EerieInterlude copy() { + return new EerieInterlude(this); + } +} diff --git a/Mage.Sets/src/mage/sets/shadowsoverinnistrad/StichedMangler.java b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/StichedMangler.java new file mode 100644 index 0000000000..a9e8098561 --- /dev/null +++ b/Mage.Sets/src/mage/sets/shadowsoverinnistrad/StichedMangler.java @@ -0,0 +1,82 @@ +/* + * 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.UUID; +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; +import mage.abilities.effects.common.TapTargetEffect; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Rarity; +import mage.constants.TargetController; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerPredicate; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author LevelX2 + */ +public class StichedMangler extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creature an opponent controls"); + + static { + filter.add(new ControllerPredicate(TargetController.OPPONENT)); + } + + public StichedMangler(UUID ownerId) { + super(ownerId, 89, "Stiched Mangler", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{2}{U}"); + this.expansionSetCode = "SOI"; + this.subtype.add("Zombie"); + this.subtype.add("Horror"); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Stitched Mangler enters the battlefield tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // When Stitched Mangler enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step. + EntersBattlefieldTriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect()); + ability.addEffect(new DontUntapInControllersNextUntapStepTargetEffect()); + ability.addTarget(new TargetCreaturePermanent(filter)); + this.addAbility(ability); + } + + public StichedMangler(final StichedMangler card) { + super(card); + } + + @Override + public StichedMangler copy() { + return new StichedMangler(this); + } +} From a5504c1a96d1fff50cc1d80537eb8e970cddccc1 Mon Sep 17 00:00:00 2001 From: GitHubMage <> Date: Wed, 2 Mar 2016 22:29:29 +0100 Subject: [PATCH 46/67] fixed Ali from Cairo the previous implementation did reduce the amount of damage that is being dealt to not reduce life below one. now the damage is untouched but the affected player's life total is set to 1 if the damage would otherwise reduce below 1. --- .../mage/sets/arabiannights/AliFromCairo.java | 17 ++++---- .../test/cards/single/AliFromCairoTest.java | 40 +++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/AliFromCairoTest.java diff --git a/Mage.Sets/src/mage/sets/arabiannights/AliFromCairo.java b/Mage.Sets/src/mage/sets/arabiannights/AliFromCairo.java index 076d03c794..5cc37d20a0 100644 --- a/Mage.Sets/src/mage/sets/arabiannights/AliFromCairo.java +++ b/Mage.Sets/src/mage/sets/arabiannights/AliFromCairo.java @@ -97,11 +97,7 @@ class AliFromCairoReplacementEffect extends ReplacementEffectImpl { && (controller.getLife() > 0) &&(controller.getLife() - event.getAmount()) < 1 && event.getPlayerId().equals(controller.getId()) ) { - return true; - //unsure how to make this comply with - // 10/1/2008: The ability doesn't change how much damage is dealt; - // it just changes how much life that damage makes you lose. - // An effect such as Spirit Link will see the full amount of damage being dealt. + return true; } } return false; @@ -110,10 +106,17 @@ class AliFromCairoReplacementEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { Player controller = game.getPlayer(source.getControllerId()); + + // 10/1/2008: The ability doesn't change how much damage is dealt; + // it just changes how much life that damage makes you lose. + // An effect such as Spirit Link will see the full amount of damage being dealt. + game.fireEvent(event); + if (controller != null) { - event.setAmount(controller.getLife() - 1); + controller.setLife(1, game); } - return false; + + return true; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/AliFromCairoTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/AliFromCairoTest.java new file mode 100644 index 0000000000..fefd0c65d2 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/AliFromCairoTest.java @@ -0,0 +1,40 @@ +package org.mage.test.cards.single; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class AliFromCairoTest extends CardTestPlayerBase { + + @Test + public void testCard() { + addCard(Zone.BATTLEFIELD, playerA, "Ali from Cairo", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 12); + addCard(Zone.BATTLEFIELD, playerB, "Soulfire Grand Master", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 12); + addCard(Zone.HAND, playerA, "Lightning Bolt", 7); + addCard(Zone.HAND, playerB, "Lightning Bolt", 7); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 1); + assertLife(playerB, 23); + } +} \ No newline at end of file From 6cfe43e9b7e769b594b1fd69e46c80277d06f10b Mon Sep 17 00:00:00 2001 From: rkfg Date: Thu, 3 Mar 2016 00:25:27 +0300 Subject: [PATCH 47/67] Make DeckGeneratorDialog fields non-static for more OOP-style. --- .../deck/generator/DeckGeneratorDialog.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java index 8e3770ce58..e6ef49e9ec 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java @@ -48,13 +48,13 @@ import java.util.Date; */ public class DeckGeneratorDialog { - private static JDialog dlg; - private static String selectedColors; - private static JComboBox cbSets; - private static JComboBox cbDeckSize; - private static JButton btnGenerate, btnCancel; - private static JCheckBox cArtifacts, cSingleton, cNonBasicLands; - private static SimpleDateFormat dateFormat; + private JDialog dlg; + private String selectedColors; + private JComboBox cbSets; + private JComboBox cbDeckSize; + private JButton btnGenerate, btnCancel; + private JCheckBox cArtifacts, cSingleton, cNonBasicLands; + private SimpleDateFormat dateFormat; public DeckGeneratorDialog() { @@ -83,7 +83,7 @@ public class DeckGeneratorDialog { p0.add(Box.createVerticalStrut(5)); JPanel jPanel = new JPanel(); JLabel text3 = new JLabel("Choose sets:"); - cbSets = new JComboBox(ConstructedFormats.getTypes()); + cbSets = new JComboBox(ConstructedFormats.getTypes()); cbSets.setSelectedIndex(0); cbSets.setPreferredSize(new Dimension(300, 25)); cbSets.setMaximumSize(new Dimension(300, 25)); @@ -100,7 +100,7 @@ public class DeckGeneratorDialog { p0.add(Box.createVerticalStrut(5)); JPanel jPanel2 = new JPanel(); JLabel textDeckSize = new JLabel("Deck size:"); - cbDeckSize = new JComboBox(new String[]{"40","60"}); + cbDeckSize = new JComboBox(new String[] { "40", "60" }); cbDeckSize.setSelectedIndex(0); cbDeckSize.setPreferredSize(new Dimension(300, 25)); cbDeckSize.setMaximumSize(new Dimension(300, 25)); @@ -168,7 +168,7 @@ public class DeckGeneratorDialog { dlg.dispose(); } - public static void cleanUp() { + public void cleanUp() { for (ActionListener al: btnGenerate.getActionListeners()) { btnGenerate.removeActionListener(al); } @@ -187,7 +187,7 @@ public class DeckGeneratorDialog { tmp.createNewFile(); deck.setName(deckName); Sets.saveDeck(tmp.getAbsolutePath(), deck.getDeckCardLists()); - DeckGeneratorDialog.cleanUp(); + cleanUp(); return tmp.getAbsolutePath(); } catch (Exception e) { JOptionPane.showMessageDialog(null, "Couldn't generate deck. Try again."); From 89bce030d04c7e0c1e550486c484a2dc668642f9 Mon Sep 17 00:00:00 2001 From: rkfg Date: Thu, 3 Mar 2016 00:32:32 +0300 Subject: [PATCH 48/67] Add colorless mana filter to deck generator. --- .../client/deck/generator/DeckGenerator.java | 2 +- .../deck/generator/DeckGeneratorDialog.java | 19 ++++++++++++++++--- .../deck/generator/DeckGeneratorPool.java | 7 ++++++- .../mage/client/dialog/PreferencesDialog.java | 1 + 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java index cb63a4d543..5f654e18b7 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGenerator.java @@ -155,7 +155,7 @@ public class DeckGenerator { * @return the final deck to use. */ private static Deck generateDeck(int deckSize, List allowedColors, List setsToUse) { - genPool = new DeckGeneratorPool(deckSize, allowedColors, genDialog.isSingleton()); + genPool = new DeckGeneratorPool(deckSize, allowedColors, genDialog.isSingleton(), genDialog.isColorless()); final String[] sets = setsToUse.toArray(new String[setsToUse.size()]); diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java index e6ef49e9ec..0c0ca15850 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorDialog.java @@ -53,7 +53,7 @@ public class DeckGeneratorDialog { private JComboBox cbSets; private JComboBox cbDeckSize; private JButton btnGenerate, btnCancel; - private JCheckBox cArtifacts, cSingleton, cNonBasicLands; + private JCheckBox cArtifacts, cSingleton, cNonBasicLands, cColorless; private SimpleDateFormat dateFormat; public DeckGeneratorDialog() @@ -138,8 +138,15 @@ public class DeckGeneratorDialog { cNonBasicLands.setSelected(Boolean.valueOf(nonBasicEnabled)); jCheckBoxes.add(cNonBasicLands); - jCheckBoxes.setPreferredSize(new Dimension(300, 25)); - jCheckBoxes.setMaximumSize(new Dimension(300, 25)); + // Non-basic lands + cColorless = new JCheckBox("Colorless mana", false); + cColorless.setToolTipText("Allow cards with colorless mana cost."); + String colorlessEnabled = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORLESS, "false"); + cColorless.setSelected(Boolean.valueOf(colorlessEnabled)); + jCheckBoxes.add(cColorless); + + jCheckBoxes.setPreferredSize(new Dimension(450, 25)); + jCheckBoxes.setMaximumSize(new Dimension(450, 25)); p0.add(jCheckBoxes); btnGenerate = new JButton("Ok"); @@ -205,6 +212,12 @@ public class DeckGeneratorDialog { return selected; } + public boolean isColorless() { + boolean selected = cColorless.isSelected(); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_COLORLESS, Boolean.toString(selected)); + return selected; + } + public boolean useArtifacts() { boolean selected = cArtifacts.isSelected(); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_ARTIFACTS, Boolean.toString(selected)); diff --git a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java index 3e92fef086..849a31997d 100644 --- a/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java +++ b/Mage.Client/src/main/java/mage/client/deck/generator/DeckGeneratorPool.java @@ -51,6 +51,7 @@ public class DeckGeneratorPool private static final int NONCREATURE_COUNT_60 = 13; private final List allowedColors; + private boolean colorlessAllowed; private final List poolCMCs; private final int creatureCount; private final int nonCreatureCount; @@ -68,11 +69,12 @@ public class DeckGeneratorPool private List reserveSpells = new ArrayList<>(); private Deck deck; - public DeckGeneratorPool(final int deckSize, final List allowedColors, boolean isSingleton) + public DeckGeneratorPool(final int deckSize, final List allowedColors, boolean isSingleton, boolean colorlessAllowed) { this.deckSize = deckSize; this.allowedColors = allowedColors; this.isSingleton = isSingleton; + this.colorlessAllowed = colorlessAllowed; this.deck = new Deck(); @@ -207,6 +209,9 @@ public class DeckGeneratorPool return false; } } + if (symbol.equals("C") && !colorlessAllowed) { + return false; + } } return true; } diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index b6fafef610..14219e5e2e 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -235,6 +235,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_DECK_GENERATOR_SINGLETON = "newDeckGeneratorSingleton"; public static final String KEY_NEW_DECK_GENERATOR_ARTIFACTS = "newDeckGeneratorArtifacts"; public static final String KEY_NEW_DECK_GENERATOR_NON_BASIC_LANDS = "newDeckGeneratorNonBasicLands"; + public static final String KEY_NEW_DECK_GENERATOR_COLORLESS = "newDeckGeneratorColorless"; // used to save and restore the settings for the cardArea (draft, sideboarding, deck builder) public static final String KEY_DRAFT_VIEW = "draftView"; From 50e1828d418565552b638c6388f4cc9df597df40 Mon Sep 17 00:00:00 2001 From: drmDev Date: Wed, 2 Mar 2016 19:33:51 -0500 Subject: [PATCH 49/67] tests for Blood Moon and Pithing Needle interactions --- .../cards/abilities/enters/BloodMoonTest.java | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java index 276f8ee438..e6d17a2b49 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java @@ -159,7 +159,75 @@ public class BloodMoonTest extends CardTestPlayerBase { assertHandCount(playerA, 1); // Check that the Steam Vents produces only {R} Assert.assertTrue("The mana the land can produce should be [{U}] but it's " + playerB.getManaAvailable(currentGame).toString(), playerB.getManaAvailable(currentGame).toString().equals("[{U}]")); - } + + /** + * Possible bug reported: Blood Moon effects no longer appearing with Pithing Needle naming Blood Moon. + * + * Testing Blood Moon on battlefield before Pithing Needle naming it. + * Non-basics should still only produce {R} + */ + @Test + public void testBloodMoonBeforePithingNeedle() { + + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); + // Blood Moon 2R + // Enchantment + // Nonbasic lands are Mountains + addCard(Zone.BATTLEFIELD, playerA, "Blood Moon", 1); + // Artifact (1) + // As Pithing Needle enters the battlefield, name a card. + // Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. + addCard(Zone.HAND, playerB, "Pithing Needle"); // {1} + addCard(Zone.BATTLEFIELD, playerB, "Ghost Quarter", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Pithing Needle"); + setChoice(playerB, "Blood Moon"); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Blood Moon", 1); + assertPermanentCount(playerB, "Pithing Needle", 1); + assertPermanentCount(playerB, "Ghost Quarter", 1); + + Assert.assertTrue("The mana the land can produce should be [{R}] but it's " + playerB.getManaAvailable(currentGame).toString(), playerB.getManaAvailable(currentGame).toString().equals("[{R}]")); + } + + /** + * Possible bug reported: Blood Moon effects no longer appearing with Pithing Needle naming Blood Moon. + * + * Testing Pithing Needle on the battlefield naming Blood Moon, then playing Blood Moon after. + * Non-basics should still only produce {R} + */ + @Test + public void testBloodMoonAfterPithingNeedle() { + + // Artifact (1) + // As Pithing Needle enters the battlefield, name a card. + // Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. + addCard(Zone.HAND, playerA, "Pithing Needle"); // {1} + addCard(Zone.BATTLEFIELD, playerA, "Ghost Quarter", 1); + + // Blood Moon 2R + // Enchantment + // Nonbasic lands are Mountains + addCard(Zone.HAND, playerB, "Blood Moon", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle"); + setChoice(playerA, "Blood Moon"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Blood Moon"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + + execute(); + + assertPermanentCount(playerB, "Blood Moon", 1); + assertPermanentCount(playerA, "Pithing Needle", 1); + assertPermanentCount(playerA, "Ghost Quarter", 1); + + Assert.assertTrue("The mana the land can produce should be [{R}] but it's " + playerA.getManaAvailable(currentGame).toString(), playerA.getManaAvailable(currentGame).toString().equals("[{R}]")); + } } From db2784ff5d4126680a8ba35b6f116a6df588b6f5 Mon Sep 17 00:00:00 2001 From: drmDev Date: Wed, 2 Mar 2016 22:29:42 -0500 Subject: [PATCH 50/67] Tests for reported bug on Skyline Cascade. --- .../abilities/enters/SkylineCascadeTest.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java new file mode 100644 index 0000000000..8703d86878 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java @@ -0,0 +1,81 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mage.test.cards.abilities.enters; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) + */ +public class SkylineCascadeTest extends CardTestPlayerBase { + + /** + * Reported bug on Skyline Cascade not working properly. + * + * Test the typical situation - tapped creature not being able to untap during next untap step. + */ + @Test + public void testPreventsTappedCreatureUntapping() { + + // {W} 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Savannah Lions"); + + /** + * Skyline Cascade enters the battlefield tapped. + * When Skyline Cascade enters the battlefield, target creature an opponent controls doesn't untap during its controller's next untap step. + * Tap: Add {B} to your mana pool. + */ + addCard(Zone.HAND, playerB, "Skyline Cascade"); + + attack(1, playerA, "Savannah Lions"); + + playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Skyline Cascade"); + addTarget(playerA, "Savannah Lions"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + + execute(); + + assertTapped("Savannah Lions", true); + assertTapped("Skyline Cascade", true); + } + + /** + * Reported bug on Skyline Cascade not working properly. + * + * "Skyline Cascade’s triggered ability doesn't tap the creature. It can target any creature, tapped or untapped. + * If that creature is already untapped at the beginning of its controller’s next untap step, the effect won’t do anything." + * http://gatherer.wizards.com/Pages/Card/Details.aspx?multiverseid=402038 + * + * An untapped creature will remain untapped. + */ + @Test + public void testDoesNotStopUntappedCreatureUntapping() { + + // {W} 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Savannah Lions"); + + /** + * Skyline Cascade enters the battlefield tapped. + * When Skyline Cascade enters the battlefield, target creature an opponent controls doesn't untap during its controller's next untap step. + * Tap: Add {B} to your mana pool. + */ + addCard(Zone.HAND, playerB, "Skyline Cascade"); + + playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Skyline Cascade"); + addTarget(playerA, "Savannah Lions"); + + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); + + execute(); + + assertTapped("Savannah Lions", false); + assertTapped("Skyline Cascade", true); + } +} From 527bb2a492ae7ebb2c0a56fe171e92b1d15f5264 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 3 Mar 2016 10:21:11 +0100 Subject: [PATCH 51/67] Modified Blood Moon test. --- .../cards/abilities/enters/BloodMoonTest.java | 72 ++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java index e6d17a2b49..b373a20616 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/BloodMoonTest.java @@ -160,73 +160,81 @@ public class BloodMoonTest extends CardTestPlayerBase { // Check that the Steam Vents produces only {R} Assert.assertTrue("The mana the land can produce should be [{U}] but it's " + playerB.getManaAvailable(currentGame).toString(), playerB.getManaAvailable(currentGame).toString().equals("[{U}]")); } - + /** - * Possible bug reported: Blood Moon effects no longer appearing with Pithing Needle naming Blood Moon. - * + * Possible bug reported: Blood Moon effects no longer appearing with + * Pithing Needle naming Blood Moon. + * * Testing Blood Moon on battlefield before Pithing Needle naming it. * Non-basics should still only produce {R} */ @Test public void testBloodMoonBeforePithingNeedle() { - addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3); // Blood Moon 2R // Enchantment // Nonbasic lands are Mountains - addCard(Zone.BATTLEFIELD, playerA, "Blood Moon", 1); + addCard(Zone.HAND, playerA, "Blood Moon", 1); // Artifact (1) // As Pithing Needle enters the battlefield, name a card. // Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. addCard(Zone.HAND, playerB, "Pithing Needle"); // {1} + addCard(Zone.HAND, playerB, "Ghost Quarter", 1); + // {T}: Add {C} to your mana pool. + // {T}, Sacrifice Ghost Quarter: Destroy target land. Its controller may search his or her library for a basic land card, put it onto the battlefield, then shuffle his or her library. addCard(Zone.BATTLEFIELD, playerB, "Ghost Quarter", 1); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Pithing Needle"); - setChoice(playerB, "Blood Moon"); - setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); - execute(); - - assertPermanentCount(playerA, "Blood Moon", 1); - assertPermanentCount(playerB, "Pithing Needle", 1); - assertPermanentCount(playerB, "Ghost Quarter", 1); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Blood Moon"); - Assert.assertTrue("The mana the land can produce should be [{R}] but it's " + playerB.getManaAvailable(currentGame).toString(), playerB.getManaAvailable(currentGame).toString().equals("[{R}]")); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Pithing Needle"); + setChoice(playerB, "Blood Moon"); + playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Ghost Quarter"); + + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Blood Moon", 1); + assertPermanentCount(playerB, "Pithing Needle", 1); + assertPermanentCount(playerB, "Ghost Quarter", 2); + + Assert.assertTrue("The mana Ghost Quarter can produce should be [{R}] but it's " + playerB.getManaAvailable(currentGame).toString(), playerB.getManaAvailable(currentGame).toString().equals("[{R}]")); } - + /** - * Possible bug reported: Blood Moon effects no longer appearing with Pithing Needle naming Blood Moon. - * - * Testing Pithing Needle on the battlefield naming Blood Moon, then playing Blood Moon after. - * Non-basics should still only produce {R} + * Possible bug reported: Blood Moon effects no longer appearing with + * Pithing Needle naming Blood Moon. + * + * Testing Pithing Needle on the battlefield naming Blood Moon, then playing + * Blood Moon after. Non-basics should still only produce {R} */ @Test public void testBloodMoonAfterPithingNeedle() { - + // Artifact (1) // As Pithing Needle enters the battlefield, name a card. // Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. - addCard(Zone.HAND, playerA, "Pithing Needle"); // {1} + addCard(Zone.HAND, playerA, "Pithing Needle"); // {1} addCard(Zone.BATTLEFIELD, playerA, "Ghost Quarter", 1); - + // Blood Moon 2R // Enchantment // Nonbasic lands are Mountains - addCard(Zone.HAND, playerB, "Blood Moon", 1); + addCard(Zone.HAND, playerB, "Blood Moon", 1); addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle"); - setChoice(playerA, "Blood Moon"); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Blood Moon"); - + setChoice(playerA, "Blood Moon"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Blood Moon"); + setStopAt(3, PhaseStep.PRECOMBAT_MAIN); - - execute(); - + + execute(); + assertPermanentCount(playerB, "Blood Moon", 1); - assertPermanentCount(playerA, "Pithing Needle", 1); - assertPermanentCount(playerA, "Ghost Quarter", 1); + assertPermanentCount(playerA, "Pithing Needle", 1); + assertPermanentCount(playerA, "Ghost Quarter", 1); Assert.assertTrue("The mana the land can produce should be [{R}] but it's " + playerA.getManaAvailable(currentGame).toString(), playerA.getManaAvailable(currentGame).toString().equals("[{R}]")); } From e2b62fecd809b6c3c8bbe217f56df96b2f915d16 Mon Sep 17 00:00:00 2001 From: rkfg Date: Thu, 3 Mar 2016 11:43:50 +0300 Subject: [PATCH 52/67] Prevent repeatable conceding (fixes #1526). --- Mage/src/main/java/mage/game/GameImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 6d35aad001..5b39cac2a6 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1192,7 +1192,7 @@ public abstract class GameImpl implements Game, Serializable { @Override public synchronized void concede(UUID playerId) { Player player = state.getPlayer(playerId); - if (player != null) { + if (player != null && !player.hasLost()) { logger.debug("Player " + player.getName() + " concedes game " + this.getId()); fireInformEvent(player.getLogName() + " has conceded."); player.concede(this); From b46c2d5b4561531b67e103695455169223017240 Mon Sep 17 00:00:00 2001 From: drmDev Date: Thu, 3 Mar 2016 14:50:00 -0500 Subject: [PATCH 53/67] testReflectorMageBouncesFaceupCreatureReplayAsMorph fails due to bug reported --- .../cards/abilities/keywords/MorphTest.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java index 7b35f5ef6a..00f770a05f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java @@ -640,6 +640,91 @@ public class MorphTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Pine Walker", 1); assertPowerToughness(playerA, "Pine Walker", 5, 5); assertTapped("Pine Walker", false); + } + + /** + * Reflector Mage bouncing a creature that can be played as a morph should not prevent the card + * from being replayed as a morph. Morph creatures are nameless. + * + * Reported bug: + * Face-up morph creatures that are bounced by Reflector Mage should be able to be replayed as morphs + * without the "until the next turn" restriction." + */ + @Test + public void testReflectorMageBouncesFaceupCreatureReplayAsMorph() { + + // {1}{W}{U} When Reflector Mage enters the battlefield, return target creature an opponent controls to its owner's hand. + // That creature's owner can't cast spells with the same name as that creature until your next turn. + addCard(Zone.HAND, playerA, "Reflector Mage"); // 2/3 + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + //Tap: Add {G}, {U}, or {R} to your mana pool. + // Morph 2 (You may cast this card face down as a 2/2 creature for 3. Turn it face up any time for its morph cost.) + // When Rattleclaw Mystic is turned face up, add {G}{U}{R} to your mana pool. + addCard(Zone.BATTLEFIELD, playerB, "Rattleclaw Mystic"); // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Forest"); + addCard(Zone.BATTLEFIELD, playerB, "Island"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reflector Mage"); + addTarget(playerA, "Rattleclaw Mystic"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Rattleclaw Mystic"); + setChoice(playerB, "Yes"); // cast it face down as 2/2 creature + setStopAt(2, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertPermanentCount(playerA, "Reflector Mage", 1); + assertPermanentCount(playerB, "Rattleclaw Mystic", 0); + assertHandCount(playerB, "Rattleclaw Mystic", 0); // should have been replayed + assertPermanentCount(playerB, "", 1); // Rattleclaw played as a morph + } + + /** + * Reflector Mage bouncing a creature that can be played as a morph should not prevent the card + * from being replayed as a morph. Morph creatures are nameless. + * + * Reported bug: + * Face-up morph creatures that are bounced by Reflector Mage should be able to be replayed as morphs + * without the "until the next turn" restriction." + * + * Testing bouncing a face-down creature played next turn face-up. + */ + @Test + public void testReflectorMageBouncesMorphCreatureReplayAsFaceup() { + + //Tap: Add {G}, {U}, or {R} to your mana pool. + // Morph 2 (You may cast this card face down as a 2/2 creature for 3. Turn it face up any time for its morph cost.) + // When Rattleclaw Mystic is turned face up, add {G}{U}{R} to your mana pool. + addCard(Zone.HAND, playerA, "Rattleclaw Mystic"); // 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + // {1}{W}{U} When Reflector Mage enters the battlefield, return target creature an opponent controls to its owner's hand. + // That creature's owner can't cast spells with the same name as that creature until your next turn. + addCard(Zone.HAND, playerB, "Reflector Mage"); // 2/3 + addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rattleclaw Mystic"); + setChoice(playerA, "Yes"); // cast it face down as 2/2 creature + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Reflector Mage"); + addTarget(playerB, ""); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Rattleclaw Mystic"); + setChoice(playerA, "No"); // cast it face down as 2/2 creature + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertPermanentCount(playerB, "Reflector Mage", 1); + assertPermanentCount(playerA, "Rattleclaw Mystic", 1); + assertHandCount(playerA, "Rattleclaw Mystic", 0); // should have been replayed faceup } } From 9cf972ced4699399f97e60c84757ed2f36716b4b Mon Sep 17 00:00:00 2001 From: rkfg Date: Thu, 3 Mar 2016 23:04:35 +0300 Subject: [PATCH 54/67] Faster symbols load. --- .../org/mage/card/arcane/ManaSymbols.java | 564 +++++++++--------- 1 file changed, 282 insertions(+), 282 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index cfd3ee6834..7aa3ef492a 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -1,282 +1,282 @@ -package org.mage.card.arcane; - -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.Rectangle; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.regex.Pattern; -import javax.imageio.ImageIO; -import mage.cards.repository.ExpansionRepository; -import mage.client.dialog.PreferencesDialog; -import mage.client.util.GUISizeHelper; -import mage.client.util.ImageHelper; -import mage.client.util.gui.BufferedImageBuilder; -import org.apache.log4j.Logger; -import org.mage.plugins.card.constants.Constants; - -public class ManaSymbols { - - private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); - private static final Map> manaImages = new HashMap<>(); - private static boolean smallSymbolsFound = false; - private static boolean mediumSymbolsFound = false; - - private static final Map setImages = new HashMap<>(); - private static final Map setImagesExist = new HashMap<>(); - private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}"); - private static String cachedPath; - private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG", - "BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU", - "WP", "UP", "BP", "RP", "GP", "X", "C"}; - - public static void loadImages() { - smallSymbolsFound = loadSymbolsImages(15); - mediumSymbolsFound = loadSymbolsImages(25); - - List setCodes = ExpansionRepository.instance.getSetCodes(); - if (setCodes == null) { - // the cards db file is probaly not included in the client. It will be created after the first connect to a server. - LOGGER.warn("No db information for sets found. Connect to a server to create database file on client side. Then try to restart the client."); - return; - } - for (String set : setCodes) { - File file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-C.jpg"); - try { - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - int width = image.getWidth(null); - if (width > 21) { - int h = image.getHeight(null); - if (h > 0) { - Rectangle r = new Rectangle(21, (int) (h * 21.0f / width)); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - setImages.put(set, resized); - } - } else { - setImages.put(set, image); - } - } catch (Exception e) { - } - String[] codes = new String[]{"C", "U", "R", "M"}; - try { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); - if (!file.exists()) { - file.mkdirs(); - } - - for (String code : codes) { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-" + code + ".png"); - if (file.exists()) { - continue; - } - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-" + code + ".jpg"); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - try { - int width = image.getWidth(null); - int height = image.getHeight(null); - if (height > 0) { - int dx = 0; - if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { - dx = 6; - } - Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - File newFile = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + File.separator + set + "-" + code + ".png"); - ImageIO.write(resized, "png", newFile); - } - } catch (Exception e) { - if (file.exists()) { - file.delete(); - } - } - } - - } catch (Exception e) { - } - } - - File file; - for (String set : ExpansionRepository.instance.getSetCodes()) { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); - if (!file.exists()) { - break; - } - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-C.png"); - try { - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - int width = image.getWidth(null); - int height = image.getHeight(null); - setImagesExist.put(set, new Dimension(width, height)); - } catch (Exception e) { - } - } - } - - private static boolean loadSymbolsImages(int size) { - boolean fileErrors = false; - HashMap sizedSymbols = new HashMap<>(); - for (String symbol : symbols) { - String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL; - if (size > 25) { - resourcePath = Constants.RESOURCE_PATH_MANA_LARGE; - } else if (size > 15) { - resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM; - } - File file = new File(getSymbolsPath() + resourcePath + "/" + symbol + ".jpg"); - try { - - if (size == 15 || size == 25) { - BufferedImage notResized = ImageIO.read(file); - sizedSymbols.put(symbol, notResized); - } else { - Rectangle r = new Rectangle(size, size); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - sizedSymbols.put(symbol, resized); - } - } catch (Exception e) { - LOGGER.error("Error for symbol:" + symbol); - fileErrors = true; - } - } - manaImages.put(size, sizedSymbols); - return !fileErrors; - } - - private static String getSymbolsPath() { - return getSymbolsPath(false); - } - - private static String getSymbolsPath(boolean forHtmlCode) { - String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); - String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null); - if (path == null) { - if (forHtmlCode) { - // for html code we need to use double '//' symbols - // and seems it should be hard coded - as it is not the same as using File.separator - return "plugins/images/"; - } else { - return mage.client.constants.Constants.IO.imageBaseDir; - } - } - if (forHtmlCode) { - if (cachedPath != null) { - return cachedPath; - } - if (path.contains("\\")) { - cachedPath = path.replaceAll("[\\\\]", "/"); - return cachedPath; - } - } - return path; - } - - public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth) { - if (!manaImages.containsKey(symbolWidth)) { - loadSymbolsImages(symbolWidth); - } - Map sizedSymbols = manaImages.get(symbolWidth); - if (manaCost.length() == 0) { - return; - } - manaCost = manaCost.replace("\\", ""); - manaCost = UI.getDisplayManaCost(manaCost); - StringTokenizer tok = new StringTokenizer(manaCost, " "); - while (tok.hasMoreTokens()) { - String symbol = tok.nextToken().substring(0); - // Check and load symbol in the width - Image image = sizedSymbols.get(symbol); - if (image == null) { - //log.error("Symbol not recognized \"" + symbol + "\" in mana cost: " + manaCost); - continue; - } - g.drawImage(image, x, y, null); - x += symbolWidth; - } - } - - public static String getStringManaCost(List manaCost) { - StringBuilder sb = new StringBuilder(); - for (String s : manaCost) { - sb.append(s); - } - return sb.toString().replace("{", "").replace("}", " ").trim(); - } - - public enum Type { - TABLE, - CHAT, - DIALOG, - TOOLTIP, - } - - public static synchronized String replaceSymbolsWithHTML(String value, Type type) { - value = value.replace("{source}", "|source|"); - value = value.replace("{this}", "|this|"); - String replaced = value; - boolean symbolFilesFound; - int symbolSize; - switch (type) { - case TABLE: - symbolSize = GUISizeHelper.symbolTableSize; - break; - case CHAT: - symbolSize = GUISizeHelper.symbolChatSize; - break; - case DIALOG: - symbolSize = GUISizeHelper.symbolDialogSize; - break; - case TOOLTIP: - symbolSize = GUISizeHelper.symbolTooltipSize; - break; - default: - symbolSize = 11; - break; - } - String resourcePath = "small"; - symbolFilesFound = smallSymbolsFound; - if (symbolSize > 25) { - resourcePath = "large"; - } else if (symbolSize > 15) { - resourcePath = "medium"; - symbolFilesFound = mediumSymbolsFound; - } - if (symbolFilesFound) { - replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll("$1$2"); - } - replaced = replaced.replace("|source|", "{source}"); - replaced = replaced.replace("|this|", "{this}"); - return replaced; - } - - public static String replaceSetCodeWithHTML(String set, String rarity, int size) { - String _set = set; - if (setImagesExist.containsKey(_set)) { - int factor = size / 15 + 1; - Integer width = setImagesExist.get(_set).width * factor; - Integer height = setImagesExist.get(_set).height * factor; - return "" + rarity + ""; - } else { - return set; - } - } - - public static Image getSetSymbolImage(String set) { - return setImages.get(set); - } - - public static BufferedImage getSizedManaSymbol(String symbol) { - if (!manaImages.containsKey(GUISizeHelper.symbolDialogSize)) { - loadSymbolsImages(GUISizeHelper.symbolDialogSize); - } - Map sizedSymbols = manaImages.get(GUISizeHelper.symbolDialogSize); - return sizedSymbols.get(symbol); - } -} +package org.mage.card.arcane; + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.regex.Pattern; +import javax.imageio.ImageIO; +import mage.cards.repository.ExpansionRepository; +import mage.client.dialog.PreferencesDialog; +import mage.client.util.GUISizeHelper; +import mage.client.util.ImageHelper; +import mage.client.util.gui.BufferedImageBuilder; +import org.apache.log4j.Logger; +import org.mage.plugins.card.constants.Constants; + +public class ManaSymbols { + + private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); + private static final Map> manaImages = new HashMap<>(); + private static boolean smallSymbolsFound = false; + private static boolean mediumSymbolsFound = false; + + private static final Map setImages = new HashMap<>(); + private static final Map setImagesExist = new HashMap<>(); + private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}"); + private static String cachedPath; + private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG", + "BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU", + "WP", "UP", "BP", "RP", "GP", "X", "C"}; + + public static void loadImages() { + smallSymbolsFound = loadSymbolsImages(15); + mediumSymbolsFound = loadSymbolsImages(25); + + List setCodes = ExpansionRepository.instance.getSetCodes(); + if (setCodes == null) { + // the cards db file is probaly not included in the client. It will be created after the first connect to a server. + LOGGER.warn("No db information for sets found. Connect to a server to create database file on client side. Then try to restart the client."); + return; + } + for (String set : setCodes) { + File file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-C.jpg"); + try { + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + int width = image.getWidth(null); + if (width > 21) { + int h = image.getHeight(null); + if (h > 0) { + Rectangle r = new Rectangle(21, (int) (h * 21.0f / width)); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + setImages.put(set, resized); + } + } else { + setImages.put(set, image); + } + } catch (Exception e) { + } + String[] codes = new String[]{"C", "U", "R", "M"}; + try { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); + if (!file.exists()) { + file.mkdirs(); + } + + for (String code : codes) { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-" + code + ".png"); + if (file.exists()) { + continue; + } + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-" + code + ".jpg"); + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + try { + int width = image.getWidth(null); + int height = image.getHeight(null); + if (height > 0) { + int dx = 0; + if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { + dx = 6; + } + Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + File newFile = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + File.separator + set + "-" + code + ".png"); + ImageIO.write(resized, "png", newFile); + } + } catch (Exception e) { + if (file.exists()) { + file.delete(); + } + } + } + + } catch (Exception e) { + } + } + + File file; + for (String set : ExpansionRepository.instance.getSetCodes()) { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); + if (!file.exists()) { + break; + } + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-C.png"); + try { + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + int width = image.getWidth(null); + int height = image.getHeight(null); + setImagesExist.put(set, new Dimension(width, height)); + } catch (Exception e) { + } + } + } + + private static boolean loadSymbolsImages(int size) { + boolean fileErrors = false; + HashMap sizedSymbols = new HashMap<>(); + String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL; + if (size > 25) { + resourcePath = Constants.RESOURCE_PATH_MANA_LARGE; + } else if (size > 15) { + resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM; + } + for (String symbol : symbols) { + File file = new File(getSymbolsPath() + resourcePath + "/" + symbol + ".gif"); + try { + + if (size == 15 || size == 25) { + BufferedImage notResized = ImageIO.read(file); + sizedSymbols.put(symbol, notResized); + } else { + Rectangle r = new Rectangle(size, size); + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + sizedSymbols.put(symbol, resized); + } + } catch (Exception e) { + LOGGER.error("Error for symbol:" + symbol); + fileErrors = true; + } + } + manaImages.put(size, sizedSymbols); + return !fileErrors; + } + + private static String getSymbolsPath() { + return getSymbolsPath(false); + } + + private static String getSymbolsPath(boolean forHtmlCode) { + String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); + String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null); + if (path == null) { + if (forHtmlCode) { + // for html code we need to use double '//' symbols + // and seems it should be hard coded - as it is not the same as using File.separator + return "plugins/images/"; + } else { + return mage.client.constants.Constants.IO.imageBaseDir; + } + } + if (forHtmlCode) { + if (cachedPath != null) { + return cachedPath; + } + if (path.contains("\\")) { + cachedPath = path.replaceAll("[\\\\]", "/"); + return cachedPath; + } + } + return path; + } + + public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth) { + if (!manaImages.containsKey(symbolWidth)) { + loadSymbolsImages(symbolWidth); + } + Map sizedSymbols = manaImages.get(symbolWidth); + if (manaCost.length() == 0) { + return; + } + manaCost = manaCost.replace("\\", ""); + manaCost = UI.getDisplayManaCost(manaCost); + StringTokenizer tok = new StringTokenizer(manaCost, " "); + while (tok.hasMoreTokens()) { + String symbol = tok.nextToken().substring(0); + // Check and load symbol in the width + Image image = sizedSymbols.get(symbol); + if (image == null) { + //log.error("Symbol not recognized \"" + symbol + "\" in mana cost: " + manaCost); + continue; + } + g.drawImage(image, x, y, null); + x += symbolWidth; + } + } + + public static String getStringManaCost(List manaCost) { + StringBuilder sb = new StringBuilder(); + for (String s : manaCost) { + sb.append(s); + } + return sb.toString().replace("{", "").replace("}", " ").trim(); + } + + public enum Type { + TABLE, + CHAT, + DIALOG, + TOOLTIP, + } + + public static synchronized String replaceSymbolsWithHTML(String value, Type type) { + value = value.replace("{source}", "|source|"); + value = value.replace("{this}", "|this|"); + String replaced = value; + boolean symbolFilesFound; + int symbolSize; + switch (type) { + case TABLE: + symbolSize = GUISizeHelper.symbolTableSize; + break; + case CHAT: + symbolSize = GUISizeHelper.symbolChatSize; + break; + case DIALOG: + symbolSize = GUISizeHelper.symbolDialogSize; + break; + case TOOLTIP: + symbolSize = GUISizeHelper.symbolTooltipSize; + break; + default: + symbolSize = 11; + break; + } + String resourcePath = "small"; + symbolFilesFound = smallSymbolsFound; + if (symbolSize > 25) { + resourcePath = "large"; + } else if (symbolSize > 15) { + resourcePath = "medium"; + symbolFilesFound = mediumSymbolsFound; + } + if (symbolFilesFound) { + replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll("$1$2"); + } + replaced = replaced.replace("|source|", "{source}"); + replaced = replaced.replace("|this|", "{this}"); + return replaced; + } + + public static String replaceSetCodeWithHTML(String set, String rarity, int size) { + String _set = set; + if (setImagesExist.containsKey(_set)) { + int factor = size / 15 + 1; + Integer width = setImagesExist.get(_set).width * factor; + Integer height = setImagesExist.get(_set).height * factor; + return "" + rarity + ""; + } else { + return set; + } + } + + public static Image getSetSymbolImage(String set) { + return setImages.get(set); + } + + public static BufferedImage getSizedManaSymbol(String symbol) { + if (!manaImages.containsKey(GUISizeHelper.symbolDialogSize)) { + loadSymbolsImages(GUISizeHelper.symbolDialogSize); + } + Map sizedSymbols = manaImages.get(GUISizeHelper.symbolDialogSize); + return sizedSymbols.get(symbol); + } +} From 95d91912360a9e17424125db1c76a53495c31941 Mon Sep 17 00:00:00 2001 From: drmDev Date: Thu, 3 Mar 2016 14:50:10 -0500 Subject: [PATCH 55/67] Fixed SkylineCascade Test usage of addTarget --- .../cards/abilities/enters/SkylineCascadeTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java index 8703d86878..2dad85bb0a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java @@ -29,14 +29,14 @@ public class SkylineCascadeTest extends CardTestPlayerBase { /** * Skyline Cascade enters the battlefield tapped. * When Skyline Cascade enters the battlefield, target creature an opponent controls doesn't untap during its controller's next untap step. - * Tap: Add {B} to your mana pool. + * Tap: Add {U} to your mana pool. */ addCard(Zone.HAND, playerB, "Skyline Cascade"); attack(1, playerA, "Savannah Lions"); playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Skyline Cascade"); - addTarget(playerA, "Savannah Lions"); + addTarget(playerB, "Savannah Lions"); setStopAt(3, PhaseStep.PRECOMBAT_MAIN); @@ -64,12 +64,12 @@ public class SkylineCascadeTest extends CardTestPlayerBase { /** * Skyline Cascade enters the battlefield tapped. * When Skyline Cascade enters the battlefield, target creature an opponent controls doesn't untap during its controller's next untap step. - * Tap: Add {B} to your mana pool. + * Tap: Add {U} to your mana pool. */ addCard(Zone.HAND, playerB, "Skyline Cascade"); playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Skyline Cascade"); - addTarget(playerA, "Savannah Lions"); + addTarget(playerB, "Savannah Lions"); setStopAt(3, PhaseStep.PRECOMBAT_MAIN); @@ -78,4 +78,4 @@ public class SkylineCascadeTest extends CardTestPlayerBase { assertTapped("Savannah Lions", false); assertTapped("Skyline Cascade", true); } -} +} \ No newline at end of file From 47b2a64cee80682dfdf54a04e31b25defd8b5582 Mon Sep 17 00:00:00 2001 From: rkfg Date: Fri, 4 Mar 2016 00:59:12 +0300 Subject: [PATCH 56/67] Store symbols as .gif instead of .jpg, rename already downloaded ones on start, fixes #98. --- .../org/mage/card/arcane/ManaSymbols.java | 35 +++++++++++++++++-- .../card/dl/sources/GathererSymbols.java | 2 +- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index 7aa3ef492a..7d133699f0 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -6,6 +6,16 @@ import java.awt.Image; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,6 +46,7 @@ public class ManaSymbols { "WP", "UP", "BP", "RP", "GP", "X", "C"}; public static void loadImages() { + renameSymbols(getSymbolsPath() + File.separator + "symbols"); smallSymbolsFound = loadSymbolsImages(15); mediumSymbolsFound = loadSymbolsImages(25); @@ -148,6 +159,24 @@ public class ManaSymbols { return !fileErrors; } + private static void renameSymbols(String path) { + final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg"); + try { + Files.walkFileTree(Paths.get(path), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (matcher.matches(file)) { + Path gifPath = file.resolveSibling(file.getFileName().toString().replaceAll("\\.jpg$", ".gif")); + Files.move(file, gifPath, StandardCopyOption.REPLACE_EXISTING); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + LOGGER.error("Couldn't rename mana symbols!"); + } + } + private static String getSymbolsPath() { return getSymbolsPath(false); } @@ -247,9 +276,9 @@ public class ManaSymbols { symbolFilesFound = mediumSymbolsFound; } if (symbolFilesFound) { - replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll("$1$2"); + replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll( + "$1$2"); } replaced = replaced.replace("|source|", "{source}"); replaced = replaced.replace("|this|", "{this}"); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java index bd25bd0059..10bd03ff7a 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java @@ -71,7 +71,7 @@ public class GathererSymbols implements Iterable { return computeNext(); } String symbol = sym.replaceAll("/", ""); - File dst = new File(dir, symbol + ".jpg"); + File dst = new File(dir, symbol + ".gif"); switch (symbol) { case "T": From b9bbf9ccec5d82d2034e4a376034051e6893e821 Mon Sep 17 00:00:00 2001 From: rkfg Date: Fri, 4 Mar 2016 00:59:12 +0300 Subject: [PATCH 57/67] Store symbols as .gif instead of .jpg, rename already downloaded ones on start, fixes #98. --- .../org/mage/card/arcane/ManaSymbols.java | 593 +++++++++--------- .../card/dl/sources/GathererSymbols.java | 2 +- 2 files changed, 312 insertions(+), 283 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index 7aa3ef492a..2921db77a3 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -1,282 +1,311 @@ -package org.mage.card.arcane; - -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.Rectangle; -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.regex.Pattern; -import javax.imageio.ImageIO; -import mage.cards.repository.ExpansionRepository; -import mage.client.dialog.PreferencesDialog; -import mage.client.util.GUISizeHelper; -import mage.client.util.ImageHelper; -import mage.client.util.gui.BufferedImageBuilder; -import org.apache.log4j.Logger; -import org.mage.plugins.card.constants.Constants; - -public class ManaSymbols { - - private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); - private static final Map> manaImages = new HashMap<>(); - private static boolean smallSymbolsFound = false; - private static boolean mediumSymbolsFound = false; - - private static final Map setImages = new HashMap<>(); - private static final Map setImagesExist = new HashMap<>(); - private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}"); - private static String cachedPath; - private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG", - "BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU", - "WP", "UP", "BP", "RP", "GP", "X", "C"}; - - public static void loadImages() { - smallSymbolsFound = loadSymbolsImages(15); - mediumSymbolsFound = loadSymbolsImages(25); - - List setCodes = ExpansionRepository.instance.getSetCodes(); - if (setCodes == null) { - // the cards db file is probaly not included in the client. It will be created after the first connect to a server. - LOGGER.warn("No db information for sets found. Connect to a server to create database file on client side. Then try to restart the client."); - return; - } - for (String set : setCodes) { - File file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-C.jpg"); - try { - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - int width = image.getWidth(null); - if (width > 21) { - int h = image.getHeight(null); - if (h > 0) { - Rectangle r = new Rectangle(21, (int) (h * 21.0f / width)); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - setImages.put(set, resized); - } - } else { - setImages.put(set, image); - } - } catch (Exception e) { - } - String[] codes = new String[]{"C", "U", "R", "M"}; - try { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); - if (!file.exists()) { - file.mkdirs(); - } - - for (String code : codes) { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-" + code + ".png"); - if (file.exists()) { - continue; - } - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-" + code + ".jpg"); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - try { - int width = image.getWidth(null); - int height = image.getHeight(null); - if (height > 0) { - int dx = 0; - if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { - dx = 6; - } - Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - File newFile = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + File.separator + set + "-" + code + ".png"); - ImageIO.write(resized, "png", newFile); - } - } catch (Exception e) { - if (file.exists()) { - file.delete(); - } - } - } - - } catch (Exception e) { - } - } - - File file; - for (String set : ExpansionRepository.instance.getSetCodes()) { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); - if (!file.exists()) { - break; - } - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-C.png"); - try { - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - int width = image.getWidth(null); - int height = image.getHeight(null); - setImagesExist.put(set, new Dimension(width, height)); - } catch (Exception e) { - } - } - } - - private static boolean loadSymbolsImages(int size) { - boolean fileErrors = false; - HashMap sizedSymbols = new HashMap<>(); - String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL; - if (size > 25) { - resourcePath = Constants.RESOURCE_PATH_MANA_LARGE; - } else if (size > 15) { - resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM; - } - for (String symbol : symbols) { - File file = new File(getSymbolsPath() + resourcePath + "/" + symbol + ".gif"); - try { - - if (size == 15 || size == 25) { - BufferedImage notResized = ImageIO.read(file); - sizedSymbols.put(symbol, notResized); - } else { - Rectangle r = new Rectangle(size, size); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - sizedSymbols.put(symbol, resized); - } - } catch (Exception e) { - LOGGER.error("Error for symbol:" + symbol); - fileErrors = true; - } - } - manaImages.put(size, sizedSymbols); - return !fileErrors; - } - - private static String getSymbolsPath() { - return getSymbolsPath(false); - } - - private static String getSymbolsPath(boolean forHtmlCode) { - String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); - String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null); - if (path == null) { - if (forHtmlCode) { - // for html code we need to use double '//' symbols - // and seems it should be hard coded - as it is not the same as using File.separator - return "plugins/images/"; - } else { - return mage.client.constants.Constants.IO.imageBaseDir; - } - } - if (forHtmlCode) { - if (cachedPath != null) { - return cachedPath; - } - if (path.contains("\\")) { - cachedPath = path.replaceAll("[\\\\]", "/"); - return cachedPath; - } - } - return path; - } - - public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth) { - if (!manaImages.containsKey(symbolWidth)) { - loadSymbolsImages(symbolWidth); - } - Map sizedSymbols = manaImages.get(symbolWidth); - if (manaCost.length() == 0) { - return; - } - manaCost = manaCost.replace("\\", ""); - manaCost = UI.getDisplayManaCost(manaCost); - StringTokenizer tok = new StringTokenizer(manaCost, " "); - while (tok.hasMoreTokens()) { - String symbol = tok.nextToken().substring(0); - // Check and load symbol in the width - Image image = sizedSymbols.get(symbol); - if (image == null) { - //log.error("Symbol not recognized \"" + symbol + "\" in mana cost: " + manaCost); - continue; - } - g.drawImage(image, x, y, null); - x += symbolWidth; - } - } - - public static String getStringManaCost(List manaCost) { - StringBuilder sb = new StringBuilder(); - for (String s : manaCost) { - sb.append(s); - } - return sb.toString().replace("{", "").replace("}", " ").trim(); - } - - public enum Type { - TABLE, - CHAT, - DIALOG, - TOOLTIP, - } - - public static synchronized String replaceSymbolsWithHTML(String value, Type type) { - value = value.replace("{source}", "|source|"); - value = value.replace("{this}", "|this|"); - String replaced = value; - boolean symbolFilesFound; - int symbolSize; - switch (type) { - case TABLE: - symbolSize = GUISizeHelper.symbolTableSize; - break; - case CHAT: - symbolSize = GUISizeHelper.symbolChatSize; - break; - case DIALOG: - symbolSize = GUISizeHelper.symbolDialogSize; - break; - case TOOLTIP: - symbolSize = GUISizeHelper.symbolTooltipSize; - break; - default: - symbolSize = 11; - break; - } - String resourcePath = "small"; - symbolFilesFound = smallSymbolsFound; - if (symbolSize > 25) { - resourcePath = "large"; - } else if (symbolSize > 15) { - resourcePath = "medium"; - symbolFilesFound = mediumSymbolsFound; - } - if (symbolFilesFound) { - replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll("$1$2"); - } - replaced = replaced.replace("|source|", "{source}"); - replaced = replaced.replace("|this|", "{this}"); - return replaced; - } - - public static String replaceSetCodeWithHTML(String set, String rarity, int size) { - String _set = set; - if (setImagesExist.containsKey(_set)) { - int factor = size / 15 + 1; - Integer width = setImagesExist.get(_set).width * factor; - Integer height = setImagesExist.get(_set).height * factor; - return "" + rarity + ""; - } else { - return set; - } - } - - public static Image getSetSymbolImage(String set) { - return setImages.get(set); - } - - public static BufferedImage getSizedManaSymbol(String symbol) { - if (!manaImages.containsKey(GUISizeHelper.symbolDialogSize)) { - loadSymbolsImages(GUISizeHelper.symbolDialogSize); - } - Map sizedSymbols = manaImages.get(GUISizeHelper.symbolDialogSize); - return sizedSymbols.get(symbol); - } -} +package org.mage.card.arcane; + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.regex.Pattern; +import javax.imageio.ImageIO; +import mage.cards.repository.ExpansionRepository; +import mage.client.dialog.PreferencesDialog; +import mage.client.util.GUISizeHelper; +import mage.client.util.ImageHelper; +import mage.client.util.gui.BufferedImageBuilder; +import org.apache.log4j.Logger; +import org.mage.plugins.card.constants.Constants; + +public class ManaSymbols { + + private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); + private static final Map> manaImages = new HashMap<>(); + private static boolean smallSymbolsFound = false; + private static boolean mediumSymbolsFound = false; + + private static final Map setImages = new HashMap<>(); + private static final Map setImagesExist = new HashMap<>(); + private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}"); + private static String cachedPath; + private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG", + "BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU", + "WP", "UP", "BP", "RP", "GP", "X", "C"}; + + public static void loadImages() { + renameSymbols(getSymbolsPath() + File.separator + "symbols"); + smallSymbolsFound = loadSymbolsImages(15); + mediumSymbolsFound = loadSymbolsImages(25); + + List setCodes = ExpansionRepository.instance.getSetCodes(); + if (setCodes == null) { + // the cards db file is probaly not included in the client. It will be created after the first connect to a server. + LOGGER.warn("No db information for sets found. Connect to a server to create database file on client side. Then try to restart the client."); + return; + } + for (String set : setCodes) { + File file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-C.jpg"); + try { + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + int width = image.getWidth(null); + if (width > 21) { + int h = image.getHeight(null); + if (h > 0) { + Rectangle r = new Rectangle(21, (int) (h * 21.0f / width)); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + setImages.put(set, resized); + } + } else { + setImages.put(set, image); + } + } catch (Exception e) { + } + String[] codes = new String[]{"C", "U", "R", "M"}; + try { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); + if (!file.exists()) { + file.mkdirs(); + } + + for (String code : codes) { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-" + code + ".png"); + if (file.exists()) { + continue; + } + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-" + code + ".jpg"); + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + try { + int width = image.getWidth(null); + int height = image.getHeight(null); + if (height > 0) { + int dx = 0; + if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { + dx = 6; + } + Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + File newFile = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + File.separator + set + "-" + code + ".png"); + ImageIO.write(resized, "png", newFile); + } + } catch (Exception e) { + if (file.exists()) { + file.delete(); + } + } + } + + } catch (Exception e) { + } + } + + File file; + for (String set : ExpansionRepository.instance.getSetCodes()) { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); + if (!file.exists()) { + break; + } + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-C.png"); + try { + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + int width = image.getWidth(null); + int height = image.getHeight(null); + setImagesExist.put(set, new Dimension(width, height)); + } catch (Exception e) { + } + } + } + + private static boolean loadSymbolsImages(int size) { + boolean fileErrors = false; + HashMap sizedSymbols = new HashMap<>(); + String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL; + if (size > 25) { + resourcePath = Constants.RESOURCE_PATH_MANA_LARGE; + } else if (size > 15) { + resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM; + } + for (String symbol : symbols) { + File file = new File(getSymbolsPath() + resourcePath + "/" + symbol + ".gif"); + try { + + if (size == 15 || size == 25) { + BufferedImage notResized = ImageIO.read(file); + sizedSymbols.put(symbol, notResized); + } else { + Rectangle r = new Rectangle(size, size); + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + sizedSymbols.put(symbol, resized); + } + } catch (Exception e) { + LOGGER.error("Error for symbol:" + symbol); + fileErrors = true; + } + } + manaImages.put(size, sizedSymbols); + return !fileErrors; + } + + private static void renameSymbols(String path) { + final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg"); + try { + Files.walkFileTree(Paths.get(path), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (matcher.matches(file)) { + Path gifPath = file.resolveSibling(file.getFileName().toString().replaceAll("\\.jpg$", ".gif")); + Files.move(file, gifPath, StandardCopyOption.REPLACE_EXISTING); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + LOGGER.error("Couldn't rename mana symbols!"); + } + } + + private static String getSymbolsPath() { + return getSymbolsPath(false); + } + + private static String getSymbolsPath(boolean forHtmlCode) { + String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); + String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null); + if (path == null) { + if (forHtmlCode) { + // for html code we need to use double '//' symbols + // and seems it should be hard coded - as it is not the same as using File.separator + return "plugins/images/"; + } else { + return mage.client.constants.Constants.IO.imageBaseDir; + } + } + if (forHtmlCode) { + if (cachedPath != null) { + return cachedPath; + } + if (path.contains("\\")) { + cachedPath = path.replaceAll("[\\\\]", "/"); + return cachedPath; + } + } + return path; + } + + public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth) { + if (!manaImages.containsKey(symbolWidth)) { + loadSymbolsImages(symbolWidth); + } + Map sizedSymbols = manaImages.get(symbolWidth); + if (manaCost.length() == 0) { + return; + } + manaCost = manaCost.replace("\\", ""); + manaCost = UI.getDisplayManaCost(manaCost); + StringTokenizer tok = new StringTokenizer(manaCost, " "); + while (tok.hasMoreTokens()) { + String symbol = tok.nextToken().substring(0); + // Check and load symbol in the width + Image image = sizedSymbols.get(symbol); + if (image == null) { + //log.error("Symbol not recognized \"" + symbol + "\" in mana cost: " + manaCost); + continue; + } + g.drawImage(image, x, y, null); + x += symbolWidth; + } + } + + public static String getStringManaCost(List manaCost) { + StringBuilder sb = new StringBuilder(); + for (String s : manaCost) { + sb.append(s); + } + return sb.toString().replace("{", "").replace("}", " ").trim(); + } + + public enum Type { + TABLE, + CHAT, + DIALOG, + TOOLTIP, + } + + public static synchronized String replaceSymbolsWithHTML(String value, Type type) { + value = value.replace("{source}", "|source|"); + value = value.replace("{this}", "|this|"); + String replaced = value; + boolean symbolFilesFound; + int symbolSize; + switch (type) { + case TABLE: + symbolSize = GUISizeHelper.symbolTableSize; + break; + case CHAT: + symbolSize = GUISizeHelper.symbolChatSize; + break; + case DIALOG: + symbolSize = GUISizeHelper.symbolDialogSize; + break; + case TOOLTIP: + symbolSize = GUISizeHelper.symbolTooltipSize; + break; + default: + symbolSize = 11; + break; + } + String resourcePath = "small"; + symbolFilesFound = smallSymbolsFound; + if (symbolSize > 25) { + resourcePath = "large"; + } else if (symbolSize > 15) { + resourcePath = "medium"; + symbolFilesFound = mediumSymbolsFound; + } + if (symbolFilesFound) { + replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll( + "$1$2"); + } + replaced = replaced.replace("|source|", "{source}"); + replaced = replaced.replace("|this|", "{this}"); + return replaced; + } + + public static String replaceSetCodeWithHTML(String set, String rarity, int size) { + String _set = set; + if (setImagesExist.containsKey(_set)) { + int factor = size / 15 + 1; + Integer width = setImagesExist.get(_set).width * factor; + Integer height = setImagesExist.get(_set).height * factor; + return "" + rarity + ""; + } else { + return set; + } + } + + public static Image getSetSymbolImage(String set) { + return setImages.get(set); + } + + public static BufferedImage getSizedManaSymbol(String symbol) { + if (!manaImages.containsKey(GUISizeHelper.symbolDialogSize)) { + loadSymbolsImages(GUISizeHelper.symbolDialogSize); + } + Map sizedSymbols = manaImages.get(GUISizeHelper.symbolDialogSize); + return sizedSymbols.get(symbol); + } +} diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java index bd25bd0059..10bd03ff7a 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/GathererSymbols.java @@ -71,7 +71,7 @@ public class GathererSymbols implements Iterable { return computeNext(); } String symbol = sym.replaceAll("/", ""); - File dst = new File(dir, symbol + ".jpg"); + File dst = new File(dir, symbol + ".gif"); switch (symbol) { case "T": From e8091daea5f452ad7a27e8c4fb356ee104c567e5 Mon Sep 17 00:00:00 2001 From: rkfg Date: Fri, 4 Mar 2016 01:09:45 +0300 Subject: [PATCH 58/67] Reverted crlf's back. --- .../org/mage/card/arcane/ManaSymbols.java | 622 +++++++++--------- 1 file changed, 311 insertions(+), 311 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index 2921db77a3..89843310e5 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -1,311 +1,311 @@ -package org.mage.card.arcane; - -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.Rectangle; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.PathMatcher; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.regex.Pattern; -import javax.imageio.ImageIO; -import mage.cards.repository.ExpansionRepository; -import mage.client.dialog.PreferencesDialog; -import mage.client.util.GUISizeHelper; -import mage.client.util.ImageHelper; -import mage.client.util.gui.BufferedImageBuilder; -import org.apache.log4j.Logger; -import org.mage.plugins.card.constants.Constants; - -public class ManaSymbols { - - private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); - private static final Map> manaImages = new HashMap<>(); - private static boolean smallSymbolsFound = false; - private static boolean mediumSymbolsFound = false; - - private static final Map setImages = new HashMap<>(); - private static final Map setImagesExist = new HashMap<>(); - private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}"); - private static String cachedPath; - private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG", - "BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU", - "WP", "UP", "BP", "RP", "GP", "X", "C"}; - - public static void loadImages() { - renameSymbols(getSymbolsPath() + File.separator + "symbols"); - smallSymbolsFound = loadSymbolsImages(15); - mediumSymbolsFound = loadSymbolsImages(25); - - List setCodes = ExpansionRepository.instance.getSetCodes(); - if (setCodes == null) { - // the cards db file is probaly not included in the client. It will be created after the first connect to a server. - LOGGER.warn("No db information for sets found. Connect to a server to create database file on client side. Then try to restart the client."); - return; - } - for (String set : setCodes) { - File file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-C.jpg"); - try { - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - int width = image.getWidth(null); - if (width > 21) { - int h = image.getHeight(null); - if (h > 0) { - Rectangle r = new Rectangle(21, (int) (h * 21.0f / width)); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - setImages.put(set, resized); - } - } else { - setImages.put(set, image); - } - } catch (Exception e) { - } - String[] codes = new String[]{"C", "U", "R", "M"}; - try { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); - if (!file.exists()) { - file.mkdirs(); - } - - for (String code : codes) { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-" + code + ".png"); - if (file.exists()) { - continue; - } - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-" + code + ".jpg"); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - try { - int width = image.getWidth(null); - int height = image.getHeight(null); - if (height > 0) { - int dx = 0; - if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { - dx = 6; - } - Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - File newFile = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + File.separator + set + "-" + code + ".png"); - ImageIO.write(resized, "png", newFile); - } - } catch (Exception e) { - if (file.exists()) { - file.delete(); - } - } - } - - } catch (Exception e) { - } - } - - File file; - for (String set : ExpansionRepository.instance.getSetCodes()) { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); - if (!file.exists()) { - break; - } - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-C.png"); - try { - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - int width = image.getWidth(null); - int height = image.getHeight(null); - setImagesExist.put(set, new Dimension(width, height)); - } catch (Exception e) { - } - } - } - - private static boolean loadSymbolsImages(int size) { - boolean fileErrors = false; - HashMap sizedSymbols = new HashMap<>(); - String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL; - if (size > 25) { - resourcePath = Constants.RESOURCE_PATH_MANA_LARGE; - } else if (size > 15) { - resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM; - } - for (String symbol : symbols) { - File file = new File(getSymbolsPath() + resourcePath + "/" + symbol + ".gif"); - try { - - if (size == 15 || size == 25) { - BufferedImage notResized = ImageIO.read(file); - sizedSymbols.put(symbol, notResized); - } else { - Rectangle r = new Rectangle(size, size); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - sizedSymbols.put(symbol, resized); - } - } catch (Exception e) { - LOGGER.error("Error for symbol:" + symbol); - fileErrors = true; - } - } - manaImages.put(size, sizedSymbols); - return !fileErrors; - } - - private static void renameSymbols(String path) { - final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg"); - try { - Files.walkFileTree(Paths.get(path), new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (matcher.matches(file)) { - Path gifPath = file.resolveSibling(file.getFileName().toString().replaceAll("\\.jpg$", ".gif")); - Files.move(file, gifPath, StandardCopyOption.REPLACE_EXISTING); - } - return FileVisitResult.CONTINUE; - } - }); - } catch (IOException e) { - LOGGER.error("Couldn't rename mana symbols!"); - } - } - - private static String getSymbolsPath() { - return getSymbolsPath(false); - } - - private static String getSymbolsPath(boolean forHtmlCode) { - String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); - String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null); - if (path == null) { - if (forHtmlCode) { - // for html code we need to use double '//' symbols - // and seems it should be hard coded - as it is not the same as using File.separator - return "plugins/images/"; - } else { - return mage.client.constants.Constants.IO.imageBaseDir; - } - } - if (forHtmlCode) { - if (cachedPath != null) { - return cachedPath; - } - if (path.contains("\\")) { - cachedPath = path.replaceAll("[\\\\]", "/"); - return cachedPath; - } - } - return path; - } - - public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth) { - if (!manaImages.containsKey(symbolWidth)) { - loadSymbolsImages(symbolWidth); - } - Map sizedSymbols = manaImages.get(symbolWidth); - if (manaCost.length() == 0) { - return; - } - manaCost = manaCost.replace("\\", ""); - manaCost = UI.getDisplayManaCost(manaCost); - StringTokenizer tok = new StringTokenizer(manaCost, " "); - while (tok.hasMoreTokens()) { - String symbol = tok.nextToken().substring(0); - // Check and load symbol in the width - Image image = sizedSymbols.get(symbol); - if (image == null) { - //log.error("Symbol not recognized \"" + symbol + "\" in mana cost: " + manaCost); - continue; - } - g.drawImage(image, x, y, null); - x += symbolWidth; - } - } - - public static String getStringManaCost(List manaCost) { - StringBuilder sb = new StringBuilder(); - for (String s : manaCost) { - sb.append(s); - } - return sb.toString().replace("{", "").replace("}", " ").trim(); - } - - public enum Type { - TABLE, - CHAT, - DIALOG, - TOOLTIP, - } - - public static synchronized String replaceSymbolsWithHTML(String value, Type type) { - value = value.replace("{source}", "|source|"); - value = value.replace("{this}", "|this|"); - String replaced = value; - boolean symbolFilesFound; - int symbolSize; - switch (type) { - case TABLE: - symbolSize = GUISizeHelper.symbolTableSize; - break; - case CHAT: - symbolSize = GUISizeHelper.symbolChatSize; - break; - case DIALOG: - symbolSize = GUISizeHelper.symbolDialogSize; - break; - case TOOLTIP: - symbolSize = GUISizeHelper.symbolTooltipSize; - break; - default: - symbolSize = 11; - break; - } - String resourcePath = "small"; - symbolFilesFound = smallSymbolsFound; - if (symbolSize > 25) { - resourcePath = "large"; - } else if (symbolSize > 15) { - resourcePath = "medium"; - symbolFilesFound = mediumSymbolsFound; - } - if (symbolFilesFound) { - replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll( - "$1$2"); - } - replaced = replaced.replace("|source|", "{source}"); - replaced = replaced.replace("|this|", "{this}"); - return replaced; - } - - public static String replaceSetCodeWithHTML(String set, String rarity, int size) { - String _set = set; - if (setImagesExist.containsKey(_set)) { - int factor = size / 15 + 1; - Integer width = setImagesExist.get(_set).width * factor; - Integer height = setImagesExist.get(_set).height * factor; - return "" + rarity + ""; - } else { - return set; - } - } - - public static Image getSetSymbolImage(String set) { - return setImages.get(set); - } - - public static BufferedImage getSizedManaSymbol(String symbol) { - if (!manaImages.containsKey(GUISizeHelper.symbolDialogSize)) { - loadSymbolsImages(GUISizeHelper.symbolDialogSize); - } - Map sizedSymbols = manaImages.get(GUISizeHelper.symbolDialogSize); - return sizedSymbols.get(symbol); - } -} +package org.mage.card.arcane; + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.regex.Pattern; +import javax.imageio.ImageIO; +import mage.cards.repository.ExpansionRepository; +import mage.client.dialog.PreferencesDialog; +import mage.client.util.GUISizeHelper; +import mage.client.util.ImageHelper; +import mage.client.util.gui.BufferedImageBuilder; +import org.apache.log4j.Logger; +import org.mage.plugins.card.constants.Constants; + +public class ManaSymbols { + + private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); + private static final Map> manaImages = new HashMap<>(); + private static boolean smallSymbolsFound = false; + private static boolean mediumSymbolsFound = false; + + private static final Map setImages = new HashMap<>(); + private static final Map setImagesExist = new HashMap<>(); + private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}"); + private static String cachedPath; + private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG", + "BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU", + "WP", "UP", "BP", "RP", "GP", "X", "C"}; + + public static void loadImages() { + renameSymbols(getSymbolsPath() + File.separator + "symbols"); + smallSymbolsFound = loadSymbolsImages(15); + mediumSymbolsFound = loadSymbolsImages(25); + + List setCodes = ExpansionRepository.instance.getSetCodes(); + if (setCodes == null) { + // the cards db file is probaly not included in the client. It will be created after the first connect to a server. + LOGGER.warn("No db information for sets found. Connect to a server to create database file on client side. Then try to restart the client."); + return; + } + for (String set : setCodes) { + File file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-C.jpg"); + try { + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + int width = image.getWidth(null); + if (width > 21) { + int h = image.getHeight(null); + if (h > 0) { + Rectangle r = new Rectangle(21, (int) (h * 21.0f / width)); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + setImages.put(set, resized); + } + } else { + setImages.put(set, image); + } + } catch (Exception e) { + } + String[] codes = new String[]{"C", "U", "R", "M"}; + try { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); + if (!file.exists()) { + file.mkdirs(); + } + + for (String code : codes) { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-" + code + ".png"); + if (file.exists()) { + continue; + } + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-" + code + ".jpg"); + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + try { + int width = image.getWidth(null); + int height = image.getHeight(null); + if (height > 0) { + int dx = 0; + if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { + dx = 6; + } + Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + File newFile = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + File.separator + set + "-" + code + ".png"); + ImageIO.write(resized, "png", newFile); + } + } catch (Exception e) { + if (file.exists()) { + file.delete(); + } + } + } + + } catch (Exception e) { + } + } + + File file; + for (String set : ExpansionRepository.instance.getSetCodes()) { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); + if (!file.exists()) { + break; + } + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-C.png"); + try { + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + int width = image.getWidth(null); + int height = image.getHeight(null); + setImagesExist.put(set, new Dimension(width, height)); + } catch (Exception e) { + } + } + } + + private static boolean loadSymbolsImages(int size) { + boolean fileErrors = false; + HashMap sizedSymbols = new HashMap<>(); + String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL; + if (size > 25) { + resourcePath = Constants.RESOURCE_PATH_MANA_LARGE; + } else if (size > 15) { + resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM; + } + for (String symbol : symbols) { + File file = new File(getSymbolsPath() + resourcePath + "/" + symbol + ".gif"); + try { + + if (size == 15 || size == 25) { + BufferedImage notResized = ImageIO.read(file); + sizedSymbols.put(symbol, notResized); + } else { + Rectangle r = new Rectangle(size, size); + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + sizedSymbols.put(symbol, resized); + } + } catch (Exception e) { + LOGGER.error("Error for symbol:" + symbol); + fileErrors = true; + } + } + manaImages.put(size, sizedSymbols); + return !fileErrors; + } + + private static void renameSymbols(String path) { + final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg"); + try { + Files.walkFileTree(Paths.get(path), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (matcher.matches(file)) { + Path gifPath = file.resolveSibling(file.getFileName().toString().replaceAll("\\.jpg$", ".gif")); + Files.move(file, gifPath, StandardCopyOption.REPLACE_EXISTING); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + LOGGER.error("Couldn't rename mana symbols!"); + } + } + + private static String getSymbolsPath() { + return getSymbolsPath(false); + } + + private static String getSymbolsPath(boolean forHtmlCode) { + String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); + String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null); + if (path == null) { + if (forHtmlCode) { + // for html code we need to use double '//' symbols + // and seems it should be hard coded - as it is not the same as using File.separator + return "plugins/images/"; + } else { + return mage.client.constants.Constants.IO.imageBaseDir; + } + } + if (forHtmlCode) { + if (cachedPath != null) { + return cachedPath; + } + if (path.contains("\\")) { + cachedPath = path.replaceAll("[\\\\]", "/"); + return cachedPath; + } + } + return path; + } + + public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth) { + if (!manaImages.containsKey(symbolWidth)) { + loadSymbolsImages(symbolWidth); + } + Map sizedSymbols = manaImages.get(symbolWidth); + if (manaCost.length() == 0) { + return; + } + manaCost = manaCost.replace("\\", ""); + manaCost = UI.getDisplayManaCost(manaCost); + StringTokenizer tok = new StringTokenizer(manaCost, " "); + while (tok.hasMoreTokens()) { + String symbol = tok.nextToken().substring(0); + // Check and load symbol in the width + Image image = sizedSymbols.get(symbol); + if (image == null) { + //log.error("Symbol not recognized \"" + symbol + "\" in mana cost: " + manaCost); + continue; + } + g.drawImage(image, x, y, null); + x += symbolWidth; + } + } + + public static String getStringManaCost(List manaCost) { + StringBuilder sb = new StringBuilder(); + for (String s : manaCost) { + sb.append(s); + } + return sb.toString().replace("{", "").replace("}", " ").trim(); + } + + public enum Type { + TABLE, + CHAT, + DIALOG, + TOOLTIP, + } + + public static synchronized String replaceSymbolsWithHTML(String value, Type type) { + value = value.replace("{source}", "|source|"); + value = value.replace("{this}", "|this|"); + String replaced = value; + boolean symbolFilesFound; + int symbolSize; + switch (type) { + case TABLE: + symbolSize = GUISizeHelper.symbolTableSize; + break; + case CHAT: + symbolSize = GUISizeHelper.symbolChatSize; + break; + case DIALOG: + symbolSize = GUISizeHelper.symbolDialogSize; + break; + case TOOLTIP: + symbolSize = GUISizeHelper.symbolTooltipSize; + break; + default: + symbolSize = 11; + break; + } + String resourcePath = "small"; + symbolFilesFound = smallSymbolsFound; + if (symbolSize > 25) { + resourcePath = "large"; + } else if (symbolSize > 15) { + resourcePath = "medium"; + symbolFilesFound = mediumSymbolsFound; + } + if (symbolFilesFound) { + replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll("$1$2"); + } + replaced = replaced.replace("|source|", "{source}"); + replaced = replaced.replace("|this|", "{this}"); + return replaced; + } + + public static String replaceSetCodeWithHTML(String set, String rarity, int size) { + String _set = set; + if (setImagesExist.containsKey(_set)) { + int factor = size / 15 + 1; + Integer width = setImagesExist.get(_set).width * factor; + Integer height = setImagesExist.get(_set).height * factor; + return "" + rarity + ""; + } else { + return set; + } + } + + public static Image getSetSymbolImage(String set) { + return setImages.get(set); + } + + public static BufferedImage getSizedManaSymbol(String symbol) { + if (!manaImages.containsKey(GUISizeHelper.symbolDialogSize)) { + loadSymbolsImages(GUISizeHelper.symbolDialogSize); + } + Map sizedSymbols = manaImages.get(GUISizeHelper.symbolDialogSize); + return sizedSymbols.get(symbol); + } +} From c3f32a39bd43d752c25a8a204b10e535660ddefa Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 3 Mar 2016 23:19:45 +0100 Subject: [PATCH 59/67] Merge. --- .../src/mage/sets/pdsslivers/WildPair.java | 52 +++++++ .../src/mage/sets/planarchaos/WildPair.java | 139 ++++++++++++++++++ .../abilities/enters/SkylineCascadeTest.java | 10 +- .../cards/abilities/keywords/MorphTest.java | 85 +++++++++++ .../test/cards/single/AliFromCairoTest.java | 40 +++++ 5 files changed, 321 insertions(+), 5 deletions(-) create mode 100644 Mage.Sets/src/mage/sets/pdsslivers/WildPair.java create mode 100644 Mage.Sets/src/mage/sets/planarchaos/WildPair.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/AliFromCairoTest.java diff --git a/Mage.Sets/src/mage/sets/pdsslivers/WildPair.java b/Mage.Sets/src/mage/sets/pdsslivers/WildPair.java new file mode 100644 index 0000000000..700d6dbf9f --- /dev/null +++ b/Mage.Sets/src/mage/sets/pdsslivers/WildPair.java @@ -0,0 +1,52 @@ +/* + * 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.pdsslivers; + +import java.util.UUID; + +/** + * + * @author fenhl + */ +public class WildPair extends mage.sets.planarchaos.WildPair { + + public WildPair(UUID ownerId) { + super(ownerId); + this.cardNumber = 30; + this.expansionSetCode = "H09"; + } + + public WildPair(final WildPair card) { + super(card); + } + + @Override + public WildPair copy() { + return new WildPair(this); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/planarchaos/WildPair.java b/Mage.Sets/src/mage/sets/planarchaos/WildPair.java new file mode 100644 index 0000000000..9f47914f80 --- /dev/null +++ b/Mage.Sets/src/mage/sets/planarchaos/WildPair.java @@ -0,0 +1,139 @@ +/* + * 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.planarchaos; + +import java.util.UUID; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; +import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.decorator.ConditionalTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.cards.CardImpl; +import mage.cards.CardsImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.filter.Filter; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.IntComparePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author fenhl + */ +public class WildPair extends CardImpl { + + public WildPair(UUID ownerID) { + super(ownerID, 30, "Wild Pair", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}{G}"); + this.expansionSetCode = "PLC"; + + // Whenever a creature enters the battlefield, if you cast it from your hand, you may search your library for a creature card with the same total power and toughness and put it onto the battlefield. If you do, shuffle your library. + this.addAbility(new ConditionalTriggeredAbility( + new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new WildPairEffect(), new FilterCreaturePermanent("a creature"), true), + new CastFromHandCondition(), + "Whenever a creature enters the battlefield, if you cast it from your hand, you may search your library for a creature card with the same total power and toughness and put it onto the battlefield. If you do, shuffle your library." + )); + } + + public WildPair(final WildPair card) { + super(card); + } + + @Override + public WildPair copy() { + return new WildPair(this); + } +} + +class WildPairEffect extends OneShotEffect { + + public WildPairEffect() { + super(Outcome.PutCreatureInPlay); + this.staticText = "search your library for a creature card with the same total power and toughness and put it onto the battlefield"; + } + + public WildPairEffect(final WildPairEffect effect) { + super(effect); + } + + @Override + public WildPairEffect copy() { + return new WildPairEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent permanent = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source)); + if (permanent != null) { + int totalPT = permanent.getPower().getValue() + permanent.getToughness().getValue(); + FilterCreatureCard filter = new FilterCreatureCard("creature card with total power and toughness " + totalPT); + filter.add(new TotalPowerAndToughnessPredicate(Filter.ComparisonType.Equal, totalPT)); + TargetCardInLibrary target = new TargetCardInLibrary(1, filter); + if (controller.searchLibrary(target, game)) { + if (target.getTargets().size() > 0) { + controller.moveCards(new CardsImpl(target.getTargets()), Zone.BATTLEFIELD, source, game); + } + } + controller.shuffleLibrary(game); + return true; + } + } + return false; + } +} + +/** + * + * @author fenhl + */ +class TotalPowerAndToughnessPredicate extends IntComparePredicate { + + public TotalPowerAndToughnessPredicate(Filter.ComparisonType type, int value) { + super(type, value); + } + + @Override + protected int getInputValue(MageObject input) { + return input.getPower().getValue() + input.getToughness().getValue(); + } + + @Override + public String toString() { + return "TotalPowerAndToughness" + super.toString(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java index 8703d86878..2dad85bb0a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SkylineCascadeTest.java @@ -29,14 +29,14 @@ public class SkylineCascadeTest extends CardTestPlayerBase { /** * Skyline Cascade enters the battlefield tapped. * When Skyline Cascade enters the battlefield, target creature an opponent controls doesn't untap during its controller's next untap step. - * Tap: Add {B} to your mana pool. + * Tap: Add {U} to your mana pool. */ addCard(Zone.HAND, playerB, "Skyline Cascade"); attack(1, playerA, "Savannah Lions"); playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Skyline Cascade"); - addTarget(playerA, "Savannah Lions"); + addTarget(playerB, "Savannah Lions"); setStopAt(3, PhaseStep.PRECOMBAT_MAIN); @@ -64,12 +64,12 @@ public class SkylineCascadeTest extends CardTestPlayerBase { /** * Skyline Cascade enters the battlefield tapped. * When Skyline Cascade enters the battlefield, target creature an opponent controls doesn't untap during its controller's next untap step. - * Tap: Add {B} to your mana pool. + * Tap: Add {U} to your mana pool. */ addCard(Zone.HAND, playerB, "Skyline Cascade"); playLand(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Skyline Cascade"); - addTarget(playerA, "Savannah Lions"); + addTarget(playerB, "Savannah Lions"); setStopAt(3, PhaseStep.PRECOMBAT_MAIN); @@ -78,4 +78,4 @@ public class SkylineCascadeTest extends CardTestPlayerBase { assertTapped("Savannah Lions", false); assertTapped("Skyline Cascade", true); } -} +} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java index 7b35f5ef6a..00f770a05f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java @@ -640,6 +640,91 @@ public class MorphTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Pine Walker", 1); assertPowerToughness(playerA, "Pine Walker", 5, 5); assertTapped("Pine Walker", false); + } + + /** + * Reflector Mage bouncing a creature that can be played as a morph should not prevent the card + * from being replayed as a morph. Morph creatures are nameless. + * + * Reported bug: + * Face-up morph creatures that are bounced by Reflector Mage should be able to be replayed as morphs + * without the "until the next turn" restriction." + */ + @Test + public void testReflectorMageBouncesFaceupCreatureReplayAsMorph() { + + // {1}{W}{U} When Reflector Mage enters the battlefield, return target creature an opponent controls to its owner's hand. + // That creature's owner can't cast spells with the same name as that creature until your next turn. + addCard(Zone.HAND, playerA, "Reflector Mage"); // 2/3 + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + + //Tap: Add {G}, {U}, or {R} to your mana pool. + // Morph 2 (You may cast this card face down as a 2/2 creature for 3. Turn it face up any time for its morph cost.) + // When Rattleclaw Mystic is turned face up, add {G}{U}{R} to your mana pool. + addCard(Zone.BATTLEFIELD, playerB, "Rattleclaw Mystic"); // 2/1 + addCard(Zone.BATTLEFIELD, playerB, "Forest"); + addCard(Zone.BATTLEFIELD, playerB, "Island"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reflector Mage"); + addTarget(playerA, "Rattleclaw Mystic"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Rattleclaw Mystic"); + setChoice(playerB, "Yes"); // cast it face down as 2/2 creature + setStopAt(2, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertPermanentCount(playerA, "Reflector Mage", 1); + assertPermanentCount(playerB, "Rattleclaw Mystic", 0); + assertHandCount(playerB, "Rattleclaw Mystic", 0); // should have been replayed + assertPermanentCount(playerB, "", 1); // Rattleclaw played as a morph + } + + /** + * Reflector Mage bouncing a creature that can be played as a morph should not prevent the card + * from being replayed as a morph. Morph creatures are nameless. + * + * Reported bug: + * Face-up morph creatures that are bounced by Reflector Mage should be able to be replayed as morphs + * without the "until the next turn" restriction." + * + * Testing bouncing a face-down creature played next turn face-up. + */ + @Test + public void testReflectorMageBouncesMorphCreatureReplayAsFaceup() { + + //Tap: Add {G}, {U}, or {R} to your mana pool. + // Morph 2 (You may cast this card face down as a 2/2 creature for 3. Turn it face up any time for its morph cost.) + // When Rattleclaw Mystic is turned face up, add {G}{U}{R} to your mana pool. + addCard(Zone.HAND, playerA, "Rattleclaw Mystic"); // 2/1 + addCard(Zone.BATTLEFIELD, playerA, "Forest"); + addCard(Zone.BATTLEFIELD, playerA, "Island"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + // {1}{W}{U} When Reflector Mage enters the battlefield, return target creature an opponent controls to its owner's hand. + // That creature's owner can't cast spells with the same name as that creature until your next turn. + addCard(Zone.HAND, playerB, "Reflector Mage"); // 2/3 + addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerB, "Island", 2); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rattleclaw Mystic"); + setChoice(playerA, "Yes"); // cast it face down as 2/2 creature + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Reflector Mage"); + addTarget(playerB, ""); + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Rattleclaw Mystic"); + setChoice(playerA, "No"); // cast it face down as 2/2 creature + + setStopAt(3, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertPermanentCount(playerB, "Reflector Mage", 1); + assertPermanentCount(playerA, "Rattleclaw Mystic", 1); + assertHandCount(playerA, "Rattleclaw Mystic", 0); // should have been replayed faceup } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/AliFromCairoTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/AliFromCairoTest.java new file mode 100644 index 0000000000..fefd0c65d2 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/AliFromCairoTest.java @@ -0,0 +1,40 @@ +package org.mage.test.cards.single; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author BetaSteward + */ +public class AliFromCairoTest extends CardTestPlayerBase { + + @Test + public void testCard() { + addCard(Zone.BATTLEFIELD, playerA, "Ali from Cairo", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 12); + addCard(Zone.BATTLEFIELD, playerB, "Soulfire Grand Master", 1); + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 12); + addCard(Zone.HAND, playerA, "Lightning Bolt", 7); + addCard(Zone.HAND, playerB, "Lightning Bolt", 7); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerA); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 1); + assertLife(playerB, 23); + } +} \ No newline at end of file From 4674b18a51225c221e0f3a76a52273d93e43411b Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 4 Mar 2016 01:07:52 +0100 Subject: [PATCH 60/67] * Fixed some prroblems with can't cast effects and morph ability (related to Reflector Mage, Exclusion Ritual and Alhammarret High Arbiter). --- .../magicorigins/AlhammarretHighArbiter.java | 2 +- .../mage/sets/newphyrexia/ExclusionRitual.java | 13 +++++++------ .../sets/oathofthegatewatch/ReflectorMage.java | 11 +++++++++-- Mage/src/main/java/mage/players/PlayerImpl.java | 17 +++++++++++++---- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/Mage.Sets/src/mage/sets/magicorigins/AlhammarretHighArbiter.java b/Mage.Sets/src/mage/sets/magicorigins/AlhammarretHighArbiter.java index 62dbe2cb35..8c3e6cd9fc 100644 --- a/Mage.Sets/src/mage/sets/magicorigins/AlhammarretHighArbiter.java +++ b/Mage.Sets/src/mage/sets/magicorigins/AlhammarretHighArbiter.java @@ -173,7 +173,7 @@ class AlhammarretHighArbiterCantCastEffect extends ContinuousRuleModifyingEffect @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == EventType.CAST_SPELL; + return event.getType() == EventType.CAST_SPELL_LATE; } @Override diff --git a/Mage.Sets/src/mage/sets/newphyrexia/ExclusionRitual.java b/Mage.Sets/src/mage/sets/newphyrexia/ExclusionRitual.java index 1477e7020d..48f068bf5c 100644 --- a/Mage.Sets/src/mage/sets/newphyrexia/ExclusionRitual.java +++ b/Mage.Sets/src/mage/sets/newphyrexia/ExclusionRitual.java @@ -28,8 +28,6 @@ package mage.sets.newphyrexia; import java.util.UUID; - -import mage.constants.*; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -37,6 +35,7 @@ import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.CardImpl; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.CardTypePredicate; @@ -50,6 +49,7 @@ import mage.target.TargetPermanent; * @author Loki */ public class ExclusionRitual extends CardImpl { + private static final FilterPermanent filter = new FilterPermanent("nonland permanent"); static { @@ -60,7 +60,6 @@ public class ExclusionRitual extends CardImpl { super(ownerId, 10, "Exclusion Ritual", Rarity.UNCOMMON, new CardType[]{CardType.ENCHANTMENT}, "{4}{W}{W}"); this.expansionSetCode = "NPH"; - // Imprint - When Exclusion Ritual enters the battlefield, exile target nonland permanent. Ability ability = new EntersBattlefieldTriggeredAbility(new ExclusionRitualImprintEffect(), false); ability.addTarget(new TargetPermanent(filter)); @@ -80,6 +79,7 @@ public class ExclusionRitual extends CardImpl { } class ExclusionRitualImprintEffect extends OneShotEffect { + ExclusionRitualImprintEffect() { super(Outcome.Exile); staticText = "exile target nonland permanent"; @@ -108,6 +108,7 @@ class ExclusionRitualImprintEffect extends OneShotEffect { } class ExclusionRitualReplacementEffect extends ContinuousRuleModifyingEffectImpl { + ExclusionRitualReplacementEffect() { super(Duration.WhileOnBattlefield, Outcome.Detriment); staticText = "Players can't cast spells with the same name as the exiled card"; @@ -116,12 +117,12 @@ class ExclusionRitualReplacementEffect extends ContinuousRuleModifyingEffectImpl ExclusionRitualReplacementEffect(final ExclusionRitualReplacementEffect effect) { super(effect); } - + @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.CAST_SPELL; + return event.getType() == GameEvent.EventType.CAST_SPELL_LATE; } - + @Override public boolean applies(GameEvent event, Ability source, Game game) { Permanent sourcePermanent = game.getPermanent(source.getSourceId()); diff --git a/Mage.Sets/src/mage/sets/oathofthegatewatch/ReflectorMage.java b/Mage.Sets/src/mage/sets/oathofthegatewatch/ReflectorMage.java index c7b05e1e41..e4a42df569 100644 --- a/Mage.Sets/src/mage/sets/oathofthegatewatch/ReflectorMage.java +++ b/Mage.Sets/src/mage/sets/oathofthegatewatch/ReflectorMage.java @@ -47,6 +47,7 @@ import mage.filter.predicate.permanent.ControllerPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; import mage.game.turn.Step; import mage.players.Player; import mage.target.common.TargetCreaturePermanent; @@ -110,7 +111,9 @@ class ReflectorMageEffect extends OneShotEffect { Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature != null) { controller.moveCards(targetCreature, Zone.HAND, source, game); - game.addEffect(new ExclusionRitualReplacementEffect(targetCreature.getName(), targetCreature.getOwnerId()), source); + if (!targetCreature.getName().isEmpty()) { // if the creature had no name, no restrict effect will be created + game.addEffect(new ExclusionRitualReplacementEffect(targetCreature.getName(), targetCreature.getOwnerId()), source); + } } return true; } @@ -138,13 +141,17 @@ class ExclusionRitualReplacementEffect extends ContinuousRuleModifyingEffectImpl @Override public boolean checksEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.CAST_SPELL; + return event.getType() == GameEvent.EventType.CAST_SPELL_LATE; } @Override public boolean applies(GameEvent event, Ability source, Game game) { Card card = game.getCard(event.getSourceId()); if (card != null) { + Spell spell = game.getState().getStack().getSpell(event.getSourceId()); + if (spell != null && spell.isFaceDown(game)) { + return false; // Face Down cast spell (Morph creature) has no name + } return card.getName().equals(creatureName); } return false; diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 9f3612fb82..b3cc247fb5 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -2580,9 +2580,17 @@ public abstract class PlayerImpl implements Player, Serializable { playable.add(ability); } } - } else if (card.getCardType().contains(CardType.LAND) && ability instanceof AlternativeSourceCosts) { - if (canLandPlayAlternateSourceCostsAbility(card, availableMana, ability, game)) { // e.g. Land with Morph - playable.add(ability); + } else if (ability instanceof AlternativeSourceCosts) { + if (card.getCardType().contains(CardType.LAND)) { + if (canLandPlayAlternateSourceCostsAbility(card, availableMana, ability, game)) { // e.g. Land with Morph + playable.add(ability); + } + } else if (card.getCardType().contains(CardType.CREATURE)) { // e.g. makes a card available for play by Morph if the card may not be cast normally + if (!playable.contains(card.getSpellAbility())) { + if (((AlternativeSourceCosts) ability).isAvailable(card.getSpellAbility(), game)) { + playable.add(card.getSpellAbility()); + } + } } } } @@ -2701,7 +2709,8 @@ public abstract class PlayerImpl implements Player, Serializable { * */ @Override - public Set getPlayableInHand(Game game) { + public Set getPlayableInHand(Game game + ) { Set playable = new HashSet<>(); if (!shouldSkipGettingPlayable(game)) { ManaOptions available = getManaAvailable(game); From f05afca73d884915ca0967d95f3ed0790dcfb90b Mon Sep 17 00:00:00 2001 From: drmDev Date: Thu, 3 Mar 2016 20:10:27 -0500 Subject: [PATCH 61/67] Disrupting Shoal tests for fuse cards (reported bug) --- .../counterspell/DisruptingShoalTest.java | 110 +++++++++++++++++- 1 file changed, 107 insertions(+), 3 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java index c1b2a801a9..aa3e257c2f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java @@ -75,8 +75,112 @@ public class DisruptingShoalTest extends CardTestPlayerBase { assertHandCount(playerA, "Spell Snare", 1); // Can't be cast -> no valid target assertGraveyardCount(playerA, "Pillarfield Ox", 1); - - - } + + /** + * Test that Disrupting Shoal can be played with alternate casting costs + * And the X Value can be equal to either half of a fuse card. + * + * Reported bug: "Casting Disrupting Shoal pitching Far // Away does not counter spells with converted mana cost 2 or 3, + * which it should. Instead it does counter spells with converted mana cost 5, which it shouldn't". + */ + @Test + public void testWithFuseCardCounterCMCTwo() { + + // CMC 2 and CMC 3 + addCard(Zone.HAND, playerA, "Grizzly Bears"); // 2/2 {1}{G} + + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + + /** + * Far {1}{U} Instant Return target creature to its owner's hand. Away + * {2}{B} Instant Target player sacrifices a creature. Fuse (You may + * cast one or both halves of this card from your hand.) + */ + addCard(Zone.HAND, playerB, "Disrupting Shoal", 1); + addCard(Zone.HAND, playerB, "Far // Away", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Disrupting Shoal", "Grizzly Bears", "Grizzly Bears"); + playerB.addChoice("Yes"); // use alternate costs = 2 CMC = Far + + setStopAt(1, PhaseStep.CLEANUP); + execute(); + + assertExileCount(playerB, 1); // Far // Away should be exiled as part of Disrupting alternative cost + assertGraveyardCount(playerB,"Disrupting Shoal", 1); + assertPermanentCount(playerA, "Grizzly Bears", 0); // should have been countered by Shoal + assertGraveyardCount(playerA, "Grizzly Bears", 1); + } + + /** + * Test that Disrupting Shoal can be played with alternate casting costs + * And the X Value can be equal to either half of a fuse card. + * + * Reported bug: "Casting Disrupting Shoal pitching Far // Away does not counter spells with converted mana cost 2 or 3, + * which it should. Instead it does counter spells with converted mana cost 5, which it shouldn't". + */ + @Test + public void testWithFuseCardCounterCMCThree() { + + addCard(Zone.HAND, playerA, "Centaur Courser"); // 3/3 {2}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + + /** + * Far {1}{U} Instant Return target creature to its owner's hand. Away + * {2}{B} Instant Target player sacrifices a creature. Fuse (You may + * cast one or both halves of this card from your hand.) + */ + addCard(Zone.HAND, playerB, "Disrupting Shoal", 1); + addCard(Zone.HAND, playerB, "Far // Away", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Centaur Courser"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Disrupting Shoal", "Centaur Courser", "Centaur Courser"); + playerB.addChoice("Yes"); // use alternate costs = 3 CMC = Away + + setStopAt(1, PhaseStep.CLEANUP); + execute(); + + assertExileCount(playerB, 1); // Far // Away should be exiled as part of Disrupting alternative cost + assertGraveyardCount(playerB,"Disrupting Shoal", 1); + assertPermanentCount(playerA, "Centaur Courser", 0); // should have been countered by Shoal + assertGraveyardCount(playerA, "Centaur Courser", 1); + } + + /** + * Test that Disrupting Shoal can be played with alternate casting costs + * And the X Value can be equal to either half of a fuse card. Not the combined cost of both. + * + * Reported bug: "Casting Disrupting Shoal pitching Far // Away does not counter spells with converted mana cost 2 or 3, + * which it should. Instead it does counter spells with converted mana cost 5, which it shouldn't". + */ + @Test + public void testWithFuseCardShouldNotCounterCMCFive() { + + addCard(Zone.HAND, playerA, "Air Elemental"); // 4/4 Flying {3}{U}{U} + addCard(Zone.BATTLEFIELD, playerA, "Island", 5); + + /** + * Far {1}{U} Instant Return target creature to its owner's hand. Away + * {2}{B} Instant Target player sacrifices a creature. Fuse (You may + * cast one or both halves of this card from your hand.) + */ + addCard(Zone.HAND, playerB, "Disrupting Shoal", 1); + addCard(Zone.HAND, playerB, "Far // Away", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Air Elemental"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Disrupting Shoal", "Air Elemental", "Air Elemental"); + playerB.addChoice("Yes"); // use alternate costs = 3 CMC = Away + + setStopAt(1, PhaseStep.CLEANUP); + execute(); + + assertExileCount(playerB, 1); // Far // Away should be exiled as part of Disrupting alternative cost + assertGraveyardCount(playerB,"Disrupting Shoal", 1); + assertPermanentCount(playerA, "Air Elemental", 0); // should have been countered by Shoal + assertGraveyardCount(playerA, "Air Elemental", 1); + } } \ No newline at end of file From f1bbc16a757f94df3ab739ced43d9d0602c45a1a Mon Sep 17 00:00:00 2001 From: drmDev Date: Thu, 3 Mar 2016 20:33:50 -0500 Subject: [PATCH 62/67] Fixed disrupting shoal test for CMC 5 --- .../abilities/oneshot/counterspell/DisruptingShoalTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java index aa3e257c2f..5900a91788 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/counterspell/DisruptingShoalTest.java @@ -173,14 +173,14 @@ public class DisruptingShoalTest extends CardTestPlayerBase { castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Air Elemental"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Disrupting Shoal", "Air Elemental", "Air Elemental"); - playerB.addChoice("Yes"); // use alternate costs = 3 CMC = Away + playerB.addChoice("Yes"); // use alternate costs = 2 or 3 CMC = Far // Away, not the combined cost! setStopAt(1, PhaseStep.CLEANUP); execute(); assertExileCount(playerB, 1); // Far // Away should be exiled as part of Disrupting alternative cost assertGraveyardCount(playerB,"Disrupting Shoal", 1); - assertPermanentCount(playerA, "Air Elemental", 0); // should have been countered by Shoal - assertGraveyardCount(playerA, "Air Elemental", 1); + assertPermanentCount(playerA, "Air Elemental", 1); // should NOT have been countered by Shoal + assertGraveyardCount(playerA, "Air Elemental", 0); } } \ No newline at end of file From a612eb4585cf9375f9bc89fcd764457e1df5832b Mon Sep 17 00:00:00 2001 From: rkfg Date: Fri, 4 Mar 2016 10:19:05 +0300 Subject: [PATCH 63/67] Fix CRLF mess again. --- .../org/mage/card/arcane/ManaSymbols.java | 622 +++++++++--------- 1 file changed, 311 insertions(+), 311 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index 7d133699f0..2921db77a3 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -1,311 +1,311 @@ -package org.mage.card.arcane; - -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.Rectangle; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.PathMatcher; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; -import java.util.regex.Pattern; -import javax.imageio.ImageIO; -import mage.cards.repository.ExpansionRepository; -import mage.client.dialog.PreferencesDialog; -import mage.client.util.GUISizeHelper; -import mage.client.util.ImageHelper; -import mage.client.util.gui.BufferedImageBuilder; -import org.apache.log4j.Logger; -import org.mage.plugins.card.constants.Constants; - -public class ManaSymbols { - - private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); - private static final Map> manaImages = new HashMap<>(); - private static boolean smallSymbolsFound = false; - private static boolean mediumSymbolsFound = false; - - private static final Map setImages = new HashMap<>(); - private static final Map setImagesExist = new HashMap<>(); - private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}"); - private static String cachedPath; - private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG", - "BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU", - "WP", "UP", "BP", "RP", "GP", "X", "C"}; - - public static void loadImages() { - renameSymbols(getSymbolsPath() + File.separator + "symbols"); - smallSymbolsFound = loadSymbolsImages(15); - mediumSymbolsFound = loadSymbolsImages(25); - - List setCodes = ExpansionRepository.instance.getSetCodes(); - if (setCodes == null) { - // the cards db file is probaly not included in the client. It will be created after the first connect to a server. - LOGGER.warn("No db information for sets found. Connect to a server to create database file on client side. Then try to restart the client."); - return; - } - for (String set : setCodes) { - File file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-C.jpg"); - try { - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - int width = image.getWidth(null); - if (width > 21) { - int h = image.getHeight(null); - if (h > 0) { - Rectangle r = new Rectangle(21, (int) (h * 21.0f / width)); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - setImages.put(set, resized); - } - } else { - setImages.put(set, image); - } - } catch (Exception e) { - } - String[] codes = new String[]{"C", "U", "R", "M"}; - try { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); - if (!file.exists()) { - file.mkdirs(); - } - - for (String code : codes) { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-" + code + ".png"); - if (file.exists()) { - continue; - } - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-" + code + ".jpg"); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - try { - int width = image.getWidth(null); - int height = image.getHeight(null); - if (height > 0) { - int dx = 0; - if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { - dx = 6; - } - Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - File newFile = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + File.separator + set + "-" + code + ".png"); - ImageIO.write(resized, "png", newFile); - } - } catch (Exception e) { - if (file.exists()) { - file.delete(); - } - } - } - - } catch (Exception e) { - } - } - - File file; - for (String set : ExpansionRepository.instance.getSetCodes()) { - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); - if (!file.exists()) { - break; - } - file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-C.png"); - try { - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - int width = image.getWidth(null); - int height = image.getHeight(null); - setImagesExist.put(set, new Dimension(width, height)); - } catch (Exception e) { - } - } - } - - private static boolean loadSymbolsImages(int size) { - boolean fileErrors = false; - HashMap sizedSymbols = new HashMap<>(); - String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL; - if (size > 25) { - resourcePath = Constants.RESOURCE_PATH_MANA_LARGE; - } else if (size > 15) { - resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM; - } - for (String symbol : symbols) { - File file = new File(getSymbolsPath() + resourcePath + "/" + symbol + ".gif"); - try { - - if (size == 15 || size == 25) { - BufferedImage notResized = ImageIO.read(file); - sizedSymbols.put(symbol, notResized); - } else { - Rectangle r = new Rectangle(size, size); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - sizedSymbols.put(symbol, resized); - } - } catch (Exception e) { - LOGGER.error("Error for symbol:" + symbol); - fileErrors = true; - } - } - manaImages.put(size, sizedSymbols); - return !fileErrors; - } - - private static void renameSymbols(String path) { - final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg"); - try { - Files.walkFileTree(Paths.get(path), new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (matcher.matches(file)) { - Path gifPath = file.resolveSibling(file.getFileName().toString().replaceAll("\\.jpg$", ".gif")); - Files.move(file, gifPath, StandardCopyOption.REPLACE_EXISTING); - } - return FileVisitResult.CONTINUE; - } - }); - } catch (IOException e) { - LOGGER.error("Couldn't rename mana symbols!"); - } - } - - private static String getSymbolsPath() { - return getSymbolsPath(false); - } - - private static String getSymbolsPath(boolean forHtmlCode) { - String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); - String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null); - if (path == null) { - if (forHtmlCode) { - // for html code we need to use double '//' symbols - // and seems it should be hard coded - as it is not the same as using File.separator - return "plugins/images/"; - } else { - return mage.client.constants.Constants.IO.imageBaseDir; - } - } - if (forHtmlCode) { - if (cachedPath != null) { - return cachedPath; - } - if (path.contains("\\")) { - cachedPath = path.replaceAll("[\\\\]", "/"); - return cachedPath; - } - } - return path; - } - - public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth) { - if (!manaImages.containsKey(symbolWidth)) { - loadSymbolsImages(symbolWidth); - } - Map sizedSymbols = manaImages.get(symbolWidth); - if (manaCost.length() == 0) { - return; - } - manaCost = manaCost.replace("\\", ""); - manaCost = UI.getDisplayManaCost(manaCost); - StringTokenizer tok = new StringTokenizer(manaCost, " "); - while (tok.hasMoreTokens()) { - String symbol = tok.nextToken().substring(0); - // Check and load symbol in the width - Image image = sizedSymbols.get(symbol); - if (image == null) { - //log.error("Symbol not recognized \"" + symbol + "\" in mana cost: " + manaCost); - continue; - } - g.drawImage(image, x, y, null); - x += symbolWidth; - } - } - - public static String getStringManaCost(List manaCost) { - StringBuilder sb = new StringBuilder(); - for (String s : manaCost) { - sb.append(s); - } - return sb.toString().replace("{", "").replace("}", " ").trim(); - } - - public enum Type { - TABLE, - CHAT, - DIALOG, - TOOLTIP, - } - - public static synchronized String replaceSymbolsWithHTML(String value, Type type) { - value = value.replace("{source}", "|source|"); - value = value.replace("{this}", "|this|"); - String replaced = value; - boolean symbolFilesFound; - int symbolSize; - switch (type) { - case TABLE: - symbolSize = GUISizeHelper.symbolTableSize; - break; - case CHAT: - symbolSize = GUISizeHelper.symbolChatSize; - break; - case DIALOG: - symbolSize = GUISizeHelper.symbolDialogSize; - break; - case TOOLTIP: - symbolSize = GUISizeHelper.symbolTooltipSize; - break; - default: - symbolSize = 11; - break; - } - String resourcePath = "small"; - symbolFilesFound = smallSymbolsFound; - if (symbolSize > 25) { - resourcePath = "large"; - } else if (symbolSize > 15) { - resourcePath = "medium"; - symbolFilesFound = mediumSymbolsFound; - } - if (symbolFilesFound) { - replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll( - "$1$2"); - } - replaced = replaced.replace("|source|", "{source}"); - replaced = replaced.replace("|this|", "{this}"); - return replaced; - } - - public static String replaceSetCodeWithHTML(String set, String rarity, int size) { - String _set = set; - if (setImagesExist.containsKey(_set)) { - int factor = size / 15 + 1; - Integer width = setImagesExist.get(_set).width * factor; - Integer height = setImagesExist.get(_set).height * factor; - return "" + rarity + ""; - } else { - return set; - } - } - - public static Image getSetSymbolImage(String set) { - return setImages.get(set); - } - - public static BufferedImage getSizedManaSymbol(String symbol) { - if (!manaImages.containsKey(GUISizeHelper.symbolDialogSize)) { - loadSymbolsImages(GUISizeHelper.symbolDialogSize); - } - Map sizedSymbols = manaImages.get(GUISizeHelper.symbolDialogSize); - return sizedSymbols.get(symbol); - } -} +package org.mage.card.arcane; + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.regex.Pattern; +import javax.imageio.ImageIO; +import mage.cards.repository.ExpansionRepository; +import mage.client.dialog.PreferencesDialog; +import mage.client.util.GUISizeHelper; +import mage.client.util.ImageHelper; +import mage.client.util.gui.BufferedImageBuilder; +import org.apache.log4j.Logger; +import org.mage.plugins.card.constants.Constants; + +public class ManaSymbols { + + private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); + private static final Map> manaImages = new HashMap<>(); + private static boolean smallSymbolsFound = false; + private static boolean mediumSymbolsFound = false; + + private static final Map setImages = new HashMap<>(); + private static final Map setImagesExist = new HashMap<>(); + private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}"); + private static String cachedPath; + private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG", + "BR", "G", "GU", "GW", "R", "RG", "RW", "S", "T", "U", "UB", "UR", "W", "WB", "WU", + "WP", "UP", "BP", "RP", "GP", "X", "C"}; + + public static void loadImages() { + renameSymbols(getSymbolsPath() + File.separator + "symbols"); + smallSymbolsFound = loadSymbolsImages(15); + mediumSymbolsFound = loadSymbolsImages(25); + + List setCodes = ExpansionRepository.instance.getSetCodes(); + if (setCodes == null) { + // the cards db file is probaly not included in the client. It will be created after the first connect to a server. + LOGGER.warn("No db information for sets found. Connect to a server to create database file on client side. Then try to restart the client."); + return; + } + for (String set : setCodes) { + File file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-C.jpg"); + try { + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + int width = image.getWidth(null); + if (width > 21) { + int h = image.getHeight(null); + if (h > 0) { + Rectangle r = new Rectangle(21, (int) (h * 21.0f / width)); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + setImages.put(set, resized); + } + } else { + setImages.put(set, image); + } + } catch (Exception e) { + } + String[] codes = new String[]{"C", "U", "R", "M"}; + try { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); + if (!file.exists()) { + file.mkdirs(); + } + + for (String code : codes) { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-" + code + ".png"); + if (file.exists()) { + continue; + } + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET + set + "-" + code + ".jpg"); + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + try { + int width = image.getWidth(null); + int height = image.getHeight(null); + if (height > 0) { + int dx = 0; + if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { + dx = 6; + } + Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + File newFile = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + File.separator + set + "-" + code + ".png"); + ImageIO.write(resized, "png", newFile); + } + } catch (Exception e) { + if (file.exists()) { + file.delete(); + } + } + } + + } catch (Exception e) { + } + } + + File file; + for (String set : ExpansionRepository.instance.getSetCodes()) { + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL); + if (!file.exists()) { + break; + } + file = new File(getSymbolsPath() + Constants.RESOURCE_PATH_SET_SMALL + set + "-C.png"); + try { + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + int width = image.getWidth(null); + int height = image.getHeight(null); + setImagesExist.put(set, new Dimension(width, height)); + } catch (Exception e) { + } + } + } + + private static boolean loadSymbolsImages(int size) { + boolean fileErrors = false; + HashMap sizedSymbols = new HashMap<>(); + String resourcePath = Constants.RESOURCE_PATH_MANA_SMALL; + if (size > 25) { + resourcePath = Constants.RESOURCE_PATH_MANA_LARGE; + } else if (size > 15) { + resourcePath = Constants.RESOURCE_PATH_MANA_MEDIUM; + } + for (String symbol : symbols) { + File file = new File(getSymbolsPath() + resourcePath + "/" + symbol + ".gif"); + try { + + if (size == 15 || size == 25) { + BufferedImage notResized = ImageIO.read(file); + sizedSymbols.put(symbol, notResized); + } else { + Rectangle r = new Rectangle(size, size); + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + sizedSymbols.put(symbol, resized); + } + } catch (Exception e) { + LOGGER.error("Error for symbol:" + symbol); + fileErrors = true; + } + } + manaImages.put(size, sizedSymbols); + return !fileErrors; + } + + private static void renameSymbols(String path) { + final PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.jpg"); + try { + Files.walkFileTree(Paths.get(path), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (matcher.matches(file)) { + Path gifPath = file.resolveSibling(file.getFileName().toString().replaceAll("\\.jpg$", ".gif")); + Files.move(file, gifPath, StandardCopyOption.REPLACE_EXISTING); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + LOGGER.error("Couldn't rename mana symbols!"); + } + } + + private static String getSymbolsPath() { + return getSymbolsPath(false); + } + + private static String getSymbolsPath(boolean forHtmlCode) { + String useDefault = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_USE_DEFAULT, "true"); + String path = useDefault.equals("true") ? null : PreferencesDialog.getCachedValue(PreferencesDialog.KEY_CARD_IMAGES_PATH, null); + if (path == null) { + if (forHtmlCode) { + // for html code we need to use double '//' symbols + // and seems it should be hard coded - as it is not the same as using File.separator + return "plugins/images/"; + } else { + return mage.client.constants.Constants.IO.imageBaseDir; + } + } + if (forHtmlCode) { + if (cachedPath != null) { + return cachedPath; + } + if (path.contains("\\")) { + cachedPath = path.replaceAll("[\\\\]", "/"); + return cachedPath; + } + } + return path; + } + + public static void draw(Graphics g, String manaCost, int x, int y, int symbolWidth) { + if (!manaImages.containsKey(symbolWidth)) { + loadSymbolsImages(symbolWidth); + } + Map sizedSymbols = manaImages.get(symbolWidth); + if (manaCost.length() == 0) { + return; + } + manaCost = manaCost.replace("\\", ""); + manaCost = UI.getDisplayManaCost(manaCost); + StringTokenizer tok = new StringTokenizer(manaCost, " "); + while (tok.hasMoreTokens()) { + String symbol = tok.nextToken().substring(0); + // Check and load symbol in the width + Image image = sizedSymbols.get(symbol); + if (image == null) { + //log.error("Symbol not recognized \"" + symbol + "\" in mana cost: " + manaCost); + continue; + } + g.drawImage(image, x, y, null); + x += symbolWidth; + } + } + + public static String getStringManaCost(List manaCost) { + StringBuilder sb = new StringBuilder(); + for (String s : manaCost) { + sb.append(s); + } + return sb.toString().replace("{", "").replace("}", " ").trim(); + } + + public enum Type { + TABLE, + CHAT, + DIALOG, + TOOLTIP, + } + + public static synchronized String replaceSymbolsWithHTML(String value, Type type) { + value = value.replace("{source}", "|source|"); + value = value.replace("{this}", "|this|"); + String replaced = value; + boolean symbolFilesFound; + int symbolSize; + switch (type) { + case TABLE: + symbolSize = GUISizeHelper.symbolTableSize; + break; + case CHAT: + symbolSize = GUISizeHelper.symbolChatSize; + break; + case DIALOG: + symbolSize = GUISizeHelper.symbolDialogSize; + break; + case TOOLTIP: + symbolSize = GUISizeHelper.symbolTooltipSize; + break; + default: + symbolSize = 11; + break; + } + String resourcePath = "small"; + symbolFilesFound = smallSymbolsFound; + if (symbolSize > 25) { + resourcePath = "large"; + } else if (symbolSize > 15) { + resourcePath = "medium"; + symbolFilesFound = mediumSymbolsFound; + } + if (symbolFilesFound) { + replaced = REPLACE_SYMBOLS_PATTERN.matcher(value).replaceAll( + "$1$2"); + } + replaced = replaced.replace("|source|", "{source}"); + replaced = replaced.replace("|this|", "{this}"); + return replaced; + } + + public static String replaceSetCodeWithHTML(String set, String rarity, int size) { + String _set = set; + if (setImagesExist.containsKey(_set)) { + int factor = size / 15 + 1; + Integer width = setImagesExist.get(_set).width * factor; + Integer height = setImagesExist.get(_set).height * factor; + return "" + rarity + ""; + } else { + return set; + } + } + + public static Image getSetSymbolImage(String set) { + return setImages.get(set); + } + + public static BufferedImage getSizedManaSymbol(String symbol) { + if (!manaImages.containsKey(GUISizeHelper.symbolDialogSize)) { + loadSymbolsImages(GUISizeHelper.symbolDialogSize); + } + Map sizedSymbols = manaImages.get(GUISizeHelper.symbolDialogSize); + return sizedSymbols.get(symbol); + } +} From db0674a94d8a7a8bc5692884871f034491748bf4 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 4 Mar 2016 12:21:16 +0100 Subject: [PATCH 64/67] Added .gittattributes file to deal with cr/lf problems and git. (We'll see.). --- .gitattributes | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..c18f26c92f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# These files are text and should be normalized (convert crlf => lf) +*.cs text diff=csharp +*.cmd text +*.txt text +*.xml text +*.md text +*.mf text +*.java text +*.form text + +# Images should be treated as binary +# (binary is a macro for -text -diff) +*.png binary +*.gif binary +*.jepg binary + +*.sdf binary \ No newline at end of file From 080a1b883ca712fb833d8ab899ca4f5b8e3d050d Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 4 Mar 2016 12:21:47 +0100 Subject: [PATCH 65/67] Wild Pair - fixed condition handling. --- .../MyojinOfCleansingFire.java | 4 +- .../MyojinOfInfiniteRage.java | 4 +- .../championsofkamigawa/MyojinOfLifesWeb.java | 4 +- .../MyojinOfNightsReach.java | 4 +- .../MyojinOfSeeingWinds.java | 4 +- .../mage/sets/commander/DreadCacodemon.java | 4 +- .../commander2014/AngelOfTheDireHour.java | 4 +- .../commander2014/BreachingLeviathan.java | 4 +- .../mage/sets/darksteel/FurnaceDragon.java | 4 +- .../sets/divinevsdemonic/ReiverDemon.java | 4 +- .../sets/dragonsmaze/ScionOfVituGhazi.java | 4 +- .../dragonsoftarkir/DeathbringerRegent.java | 4 +- .../mage/sets/modernmasters/Epochrasite.java | 4 +- .../src/mage/sets/planarchaos/WildPair.java | 50 +++++++++++++-- .../sets/saviorsofkamigawa/InameAsOne.java | 4 +- .../mage/sets/sorinvstibalt/CoalStoker.java | 4 +- .../tenthedition/PhageTheUntouchable.java | 4 +- .../EntersTheBattlefieldTriggerTest.java | 64 +++++++++++++------ ....java => CastFromHandSourceCondition.java} | 2 +- 19 files changed, 120 insertions(+), 60 deletions(-) rename Mage/src/main/java/mage/abilities/condition/common/{CastFromHandCondition.java => CastFromHandSourceCondition.java} (93%) diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfCleansingFire.java b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfCleansingFire.java index 17859d2364..4835f20c8b 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfCleansingFire.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfCleansingFire.java @@ -35,7 +35,7 @@ import mage.MageInt; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -74,7 +74,7 @@ public class MyojinOfCleansingFire extends CardImpl { this.getSpellAbility().addWatcher(new CastFromHandWatcher()); // Myojin of Cleansing Fire enters the battlefield with a divinity counter on it if you cast it from your hand. - this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandSourceCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); // Myojin of Cleansing Fire is indestructible as long as it has a divinity counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), new SourceHasCounterCondition(CounterType.DIVINITY), "{this} is indestructible as long as it has a divinity counter on it"))); diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfInfiniteRage.java b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfInfiniteRage.java index ac250e6c06..7be1669a4b 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfInfiniteRage.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfInfiniteRage.java @@ -35,7 +35,7 @@ import mage.MageInt; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -70,7 +70,7 @@ public class MyojinOfInfiniteRage extends CardImpl { this.getSpellAbility().addWatcher(new CastFromHandWatcher()); // Myojin of Infinite Rage enters the battlefield with a divinity counter on it if you cast it from your hand. - this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandSourceCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); // Myojin of Infinite Rage is indestructible as long as it has a divinity counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), new SourceHasCounterCondition(CounterType.DIVINITY), "{this} is indestructible as long as it has a divinity counter on it"))); diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfLifesWeb.java b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfLifesWeb.java index c4dac7d5da..00c411cb2b 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfLifesWeb.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfLifesWeb.java @@ -35,7 +35,7 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -75,7 +75,7 @@ public class MyojinOfLifesWeb extends CardImpl { this.getSpellAbility().addWatcher(new CastFromHandWatcher()); // Myojin of Life's Web enters the battlefield with a divinity counter on it if you cast it from your hand. - this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandSourceCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); // Myojin of Life's Web is indestructible as long as it has a divinity counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfNightsReach.java b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfNightsReach.java index ac83c62d9e..9e2cdb46aa 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfNightsReach.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfNightsReach.java @@ -35,7 +35,7 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -68,7 +68,7 @@ public class MyojinOfNightsReach extends CardImpl { this.getSpellAbility().addWatcher(new CastFromHandWatcher()); // Myojin of Night's Reach enters the battlefield with a divinity counter on it if you cast it from your hand. - this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandSourceCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); // Myojin of Night's Reach is indestructible as long as it has a divinity counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), new SourceHasCounterCondition(CounterType.DIVINITY), "{this} is indestructible as long as it has a divinity counter on it"))); diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfSeeingWinds.java b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfSeeingWinds.java index c33b5db0d6..609bf3e2b4 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfSeeingWinds.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/MyojinOfSeeingWinds.java @@ -35,7 +35,7 @@ import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.common.RemoveCountersSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; @@ -73,7 +73,7 @@ public class MyojinOfSeeingWinds extends CardImpl { this.getSpellAbility().addWatcher(new CastFromHandWatcher()); // Myojin of Seeing Winds enters the battlefield with a divinity counter on it if you cast it from your hand. - this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); + this.addAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect(new AddCountersSourceEffect(CounterType.DIVINITY.createInstance()), new CastFromHandSourceCondition(), ""), "{this} enters the battlefield with a divinity counter on it if you cast it from your hand")); // Myojin of Seeing Winds is indestructible as long as it has a divinity counter on it. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(IndestructibleAbility.getInstance(), Duration.WhileOnBattlefield), new SourceHasCounterCondition(CounterType.DIVINITY), "{this} is indestructible as long as it has a divinity counter on it"))); diff --git a/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java b/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java index 0af6bfc333..265e5ce1ba 100644 --- a/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java +++ b/Mage.Sets/src/mage/sets/commander/DreadCacodemon.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.effects.common.TapAllEffect; @@ -71,7 +71,7 @@ public class DreadCacodemon extends CardImpl { // if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new DestroyAllEffect(opponentsCreatures, false)); ability.addEffect(new TapAllEffect(otherCreaturesYouControl)); - this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandCondition(), + this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java b/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java index 04336e553d..49a589e5b5 100644 --- a/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java +++ b/Mage.Sets/src/mage/sets/commander2014/AngelOfTheDireHour.java @@ -30,7 +30,7 @@ package mage.sets.commander2014; import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ExileAllEffect; import mage.abilities.keyword.FlashAbility; @@ -62,7 +62,7 @@ public class AngelOfTheDireHour extends CardImpl { // When Angel of the Dire Hour enters the battlefield, if you cast it from your hand, exile all attacking creatures. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new ExileAllEffect(new FilterAttackingCreature("attacking creatures")), false), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, exile all attacking creatures."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java b/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java index e8e24c4d57..4bc14d7bfc 100644 --- a/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java +++ b/Mage.Sets/src/mage/sets/commander2014/BreachingLeviathan.java @@ -32,7 +32,7 @@ import mage.MageInt; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.OneShotEffect; @@ -66,7 +66,7 @@ public class BreachingLeviathan extends CardImpl { // When Breaching Leviathan enters the battlefield, if you cast it from your hand, tap all nonblue creatures. Those creatures don't untap during their controllers' next untap steps. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new BreachingLeviathanEffect(), false), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, tap all nonblue creatures. Those creatures don't untap during their controllers' next untap steps."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java b/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java index 0e9d02637b..77638cd428 100644 --- a/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java +++ b/Mage.Sets/src/mage/sets/darksteel/FurnaceDragon.java @@ -30,7 +30,7 @@ package mage.sets.darksteel; import java.util.UUID; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.ExileAllEffect; import mage.abilities.keyword.AffinityForArtifactsAbility; @@ -70,7 +70,7 @@ public class FurnaceDragon extends CardImpl { // When Furnace Dragon enters the battlefield, if you cast it from your hand, exile all artifacts. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new ExileAllEffect(filter), false), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, exile all artifacts."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java b/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java index 1278473e69..f3eb54962b 100644 --- a/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java +++ b/Mage.Sets/src/mage/sets/divinevsdemonic/ReiverDemon.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.ObjectColor; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.keyword.FlyingAbility; @@ -70,7 +70,7 @@ public class ReiverDemon extends CardImpl { // When Reiver Demon enters the battlefield, if you cast it from your hand, destroy all nonartifact, nonblack creatures. They can't be regenerated. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new DestroyAllEffect(filter, true), false), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, destroy all nonartifact, nonblack creatures. They can't be regenerated."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java b/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java index 4c5ea06c01..feb3b83919 100644 --- a/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java +++ b/Mage.Sets/src/mage/sets/dragonsmaze/ScionOfVituGhazi.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.abilities.TriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.PopulateEffect; @@ -57,7 +57,7 @@ public class ScionOfVituGhazi extends CardImpl { TriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new BirdToken()), false); ability.addEffect(new PopulateEffect("then")); - this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandCondition(), + this.addAbility(new ConditionalTriggeredAbility(ability, new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, put a 1/1 white Bird creature token with flying onto the battlefield, then populate."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java b/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java index b4b5e194f9..acae240ee7 100644 --- a/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java +++ b/Mage.Sets/src/mage/sets/dragonsoftarkir/DeathbringerRegent.java @@ -32,7 +32,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.Condition; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.DestroyAllEffect; import mage.abilities.keyword.FlyingAbility; @@ -88,7 +88,7 @@ class DeathbringerRegentCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - return new CastFromHandCondition().apply(game, source) + return new CastFromHandSourceCondition().apply(game, source) && game.getBattlefield().getAllActivePermanents(new FilterCreaturePermanent(), game).size() >= 6; } } diff --git a/Mage.Sets/src/mage/sets/modernmasters/Epochrasite.java b/Mage.Sets/src/mage/sets/modernmasters/Epochrasite.java index 6f652e82b4..42ca701acd 100644 --- a/Mage.Sets/src/mage/sets/modernmasters/Epochrasite.java +++ b/Mage.Sets/src/mage/sets/modernmasters/Epochrasite.java @@ -34,7 +34,7 @@ import mage.abilities.Ability; import mage.abilities.common.DiesTriggeredAbility; import mage.abilities.common.EntersBattlefieldAbility; import mage.abilities.condition.InvertCondition; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.GainSuspendEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; @@ -67,7 +67,7 @@ public class Epochrasite extends CardImpl { // Epochrasite enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand. this.addAbility(new EntersBattlefieldAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance(3)), - new InvertCondition(new CastFromHandCondition()), + new InvertCondition(new CastFromHandSourceCondition()), "{this} enters the battlefield with three +1/+1 counters on it if you didn't cast it from your hand",""), new CastFromHandWatcher()); diff --git a/Mage.Sets/src/mage/sets/planarchaos/WildPair.java b/Mage.Sets/src/mage/sets/planarchaos/WildPair.java index 9f47914f80..0f917e6940 100644 --- a/Mage.Sets/src/mage/sets/planarchaos/WildPair.java +++ b/Mage.Sets/src/mage/sets/planarchaos/WildPair.java @@ -31,15 +31,15 @@ import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.Condition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.cards.CardImpl; import mage.cards.CardsImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Rarity; +import mage.constants.SetTargetPointer; import mage.constants.Zone; import mage.filter.Filter; import mage.filter.common.FilterCreatureCard; @@ -47,25 +47,27 @@ import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.IntComparePredicate; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; import mage.players.Player; import mage.target.common.TargetCardInLibrary; +import mage.watchers.common.CastFromHandWatcher; /** * * @author fenhl */ public class WildPair extends CardImpl { - + public WildPair(UUID ownerID) { super(ownerID, 30, "Wild Pair", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}{G}"); this.expansionSetCode = "PLC"; // Whenever a creature enters the battlefield, if you cast it from your hand, you may search your library for a creature card with the same total power and toughness and put it onto the battlefield. If you do, shuffle your library. this.addAbility(new ConditionalTriggeredAbility( - new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new WildPairEffect(), new FilterCreaturePermanent("a creature"), true), - new CastFromHandCondition(), + new EntersBattlefieldAllTriggeredAbility(Zone.BATTLEFIELD, new WildPairEffect(), new FilterCreaturePermanent("a creature"), true, SetTargetPointer.PERMANENT, ""), + new CastFromHandTargetCondition(), "Whenever a creature enters the battlefield, if you cast it from your hand, you may search your library for a creature card with the same total power and toughness and put it onto the battlefield. If you do, shuffle your library." - )); + ), new CastFromHandWatcher()); } public WildPair(final WildPair card) { @@ -110,7 +112,7 @@ class WildPairEffect extends OneShotEffect { } } controller.shuffleLibrary(game); - return true; + return true; } } return false; @@ -137,3 +139,37 @@ class TotalPowerAndToughnessPredicate extends IntComparePredicate { return "TotalPowerAndToughness" + super.toString(); } } + +class CastFromHandTargetCondition implements Condition { + + @Override + public boolean apply(Game game, Ability source) { + UUID targetId = source.getEffects().get(0).getTargetPointer().getFirst(game, source); + Permanent permanent = game.getPermanentEntering(targetId); + int zccDiff = 0; + if (permanent == null) { + permanent = game.getPermanentOrLKIBattlefield(targetId); // can be alredy again removed from battlefield so also check LKI + zccDiff = -1; + } + if (permanent != null) { + // check that the spell is still in the LKI + Spell spell = game.getStack().getSpell(targetId); + if (spell == null || spell.getZoneChangeCounter(game) != permanent.getZoneChangeCounter(game) + zccDiff) { + if (game.getLastKnownInformation(targetId, Zone.STACK, permanent.getZoneChangeCounter(game) + zccDiff) == null) { + return false; + } + } + CastFromHandWatcher watcher = (CastFromHandWatcher) game.getState().getWatchers().get(CastFromHandWatcher.class.getName()); + if (watcher != null && watcher.spellWasCastFromHand(targetId)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return "you cast it from your hand"; + } + +} diff --git a/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java b/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java index 769130d982..e8ac8053b9 100644 --- a/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java +++ b/Mage.Sets/src/mage/sets/saviorsofkamigawa/InameAsOne.java @@ -33,7 +33,7 @@ import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.DiesTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; @@ -77,7 +77,7 @@ public class InameAsOne extends CardImpl { // When Iname as One enters the battlefield, if you cast it from your hand, you may search your library for a Spirit permanent card, put it onto the battlefield, then shuffle your library. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(0, 1, filter)), true), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, you may search your library for a Spirit permanent card, put it onto the battlefield, then shuffle your library."), new CastFromHandWatcher()); diff --git a/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java b/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java index 403d9c7a95..a8bae0da41 100644 --- a/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java +++ b/Mage.Sets/src/mage/sets/sorinvstibalt/CoalStoker.java @@ -31,7 +31,7 @@ import java.util.UUID; import mage.MageInt; import mage.Mana; import mage.abilities.common.EntersBattlefieldTriggeredAbility; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.BasicManaEffect; import mage.cards.CardImpl; @@ -55,7 +55,7 @@ public class CoalStoker extends CardImpl { // When Coal Stoker enters the battlefield, if you cast it from your hand, add {R}{R}{R} to your mana pool. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new BasicManaEffect(new Mana(3, 0, 0, 0, 0, 0, 0, 0)), false), - new CastFromHandCondition(), + new CastFromHandSourceCondition(), "When {this} enters the battlefield, if you cast it from your hand, add {R}{R}{R} to your mana pool."), new CastFromHandWatcher()); } diff --git a/Mage.Sets/src/mage/sets/tenthedition/PhageTheUntouchable.java b/Mage.Sets/src/mage/sets/tenthedition/PhageTheUntouchable.java index 61e5130d2e..ce4651de8f 100644 --- a/Mage.Sets/src/mage/sets/tenthedition/PhageTheUntouchable.java +++ b/Mage.Sets/src/mage/sets/tenthedition/PhageTheUntouchable.java @@ -33,7 +33,7 @@ import mage.abilities.common.DealsCombatDamageToACreatureTriggeredAbility; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.condition.InvertCondition; -import mage.abilities.condition.common.CastFromHandCondition; +import mage.abilities.condition.common.CastFromHandSourceCondition; import mage.abilities.decorator.ConditionalTriggeredAbility; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.common.LoseGameSourceControllerEffect; @@ -62,7 +62,7 @@ public class PhageTheUntouchable extends CardImpl { // When Phage the Untouchable enters the battlefield, if you didn't cast it from your hand, you lose the game. this.addAbility(new ConditionalTriggeredAbility( new EntersBattlefieldTriggeredAbility(new LoseGameSourceControllerEffect(), false), - new InvertCondition(new CastFromHandCondition()), + new InvertCondition(new CastFromHandSourceCondition()), "When {this} enters the battlefield, if you didn't cast it from your hand, you lose the game" ), new CastFromHandWatcher()); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java index 1fe8be8bb1..123ce672da 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EntersTheBattlefieldTriggerTest.java @@ -130,72 +130,73 @@ public class EntersTheBattlefieldTriggerTest extends CardTestPlayerBase { assertLife(playerA, 15); assertLife(playerB, 20); } - + /** * Dread Cacodemon's abilities should only trigger when cast from hand. - * - * Testing when cast from hand abilities take effect. - * Cast from hand destroys opponents creatures and taps all other creatures owner controls. + * + * Testing when cast from hand abilities take effect. Cast from hand + * destroys opponents creatures and taps all other creatures owner controls. */ @Test public void testDreadCacodemonConditionTrue() { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); - + // When Dread Cacodemon enters the battlefield, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. addCard(Zone.HAND, playerA, "Dread Cacodemon", 1); // 8/8 - {7}{B}{B}{B} - + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - + // Protection from white, first strike addCard(Zone.BATTLEFIELD, playerA, "Black Knight", 2); // {B}{B} // Deathtouch addCard(Zone.BATTLEFIELD, playerB, "Typhoid Rats", 2); // {B} - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Dread Cacodemon"); setStopAt(1, PhaseStep.END_TURN); - + execute(); assertPermanentCount(playerB, "Typhoid Rats", 0); assertPermanentCount(playerA, "Dread Cacodemon", 1); - assertPermanentCount(playerA, "Black Knight", 2); + assertPermanentCount(playerA, "Black Knight", 2); assertTappedCount("Black Knight", true, 2); assertTapped("Dread Cacodemon", false); } - - /** + + /** * Dread Cacodemon's abilities should only trigger when cast from hand. - * + * * Testing when card is not cast from hand, abilities do not take effect. - * All opponents creatures remain alive and owner's creatures are not tapped. + * All opponents creatures remain alive and owner's creatures are not + * tapped. */ @Test public void testDreadCacodemonConditionFalse() { addCard(Zone.BATTLEFIELD, playerA, "Swamp", 10); - + // When Dread Cacodemon enters the battlefield, if you cast it from your hand, destroy all creatures your opponents control, then tap all other creatures you control. addCard(Zone.GRAVEYARD, playerA, "Dread Cacodemon", 1); // 8/8 - {7}{B}{B}{B} // Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost. addCard(Zone.HAND, playerA, "Reanimate", 1); // {B} - + addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2); - + // Protection from white, first strike addCard(Zone.BATTLEFIELD, playerA, "Black Knight", 2); // {B}{B} // Deathtouch addCard(Zone.BATTLEFIELD, playerB, "Typhoid Rats", 2); // {B} - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reanimate", "Dread Cacodemon"); setStopAt(1, PhaseStep.END_TURN); - + execute(); assertPermanentCount(playerB, "Typhoid Rats", 2); assertGraveyardCount(playerA, "Reanimate", 1); assertPermanentCount(playerA, "Dread Cacodemon", 1); - assertPermanentCount(playerA, "Black Knight", 2); + assertPermanentCount(playerA, "Black Knight", 2); assertTappedCount("Black Knight", false, 2); assertTapped("Dread Cacodemon", false); @@ -203,4 +204,27 @@ public class EntersTheBattlefieldTriggerTest extends CardTestPlayerBase { assertLife(playerB, 20); } + /** + * Test that the cast from hand condition works for target permanent + * + */ + @Test + public void testWildPair() { + + // Whenever a creature enters the battlefield, if you cast it from your hand, you may search your library for a creature card with the same total power and toughness and put it onto the battlefield. If you do, shuffle your library. + addCard(Zone.BATTLEFIELD, playerA, "Wild Pair"); + addCard(Zone.HAND, playerA, "Silvercoat Lion", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + setChoice(playerA, "Silvercoat Lion"); + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + + execute(); + + assertPermanentCount(playerA, "Silvercoat Lion", 2); + + } + } diff --git a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandSourceCondition.java similarity index 93% rename from Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java rename to Mage/src/main/java/mage/abilities/condition/common/CastFromHandSourceCondition.java index d14fc200ac..99fddbba13 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/CastFromHandCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/CastFromHandSourceCondition.java @@ -13,7 +13,7 @@ import mage.watchers.common.CastFromHandWatcher; * * @author Loki */ -public class CastFromHandCondition implements Condition { +public class CastFromHandSourceCondition implements Condition { @Override public boolean apply(Game game, Ability source) { From 5735efa18d7a21a91cd06caa04c1ef68f7347f76 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Fri, 4 Mar 2016 12:47:05 +0100 Subject: [PATCH 66/67] Fixed some wrong counter handling not firing game events from removing counters (fixes #1587). --- .../src/mage/sets/commander2014/AEtherSnap.java | 9 ++++----- .../mage/sets/commander2015/ThiefOfBlood.java | 17 +++++++++-------- .../sets/innistrad/LudevicsTestSubject.java | 10 +++++----- .../src/mage/sets/mirrodin/OblivionStone.java | 8 ++++---- .../src/mage/sets/newphyrexia/HexParasite.java | 4 ++-- .../saviorsofkamigawa/DescendantOfMasumaro.java | 4 ++-- .../mage/sets/stronghold/CrovaxTheCursed.java | 11 ++++------- Mage.Sets/src/mage/sets/tempest/Magmasaur.java | 4 ++-- 8 files changed, 32 insertions(+), 35 deletions(-) diff --git a/Mage.Sets/src/mage/sets/commander2014/AEtherSnap.java b/Mage.Sets/src/mage/sets/commander2014/AEtherSnap.java index df8abfdccc..f9440c4208 100644 --- a/Mage.Sets/src/mage/sets/commander2014/AEtherSnap.java +++ b/Mage.Sets/src/mage/sets/commander2014/AEtherSnap.java @@ -53,7 +53,6 @@ public class AEtherSnap extends CardImpl { super(ownerId, 133, "AEther Snap", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{3}{B}{B}"); this.expansionSetCode = "C14"; - // Remove all counters from all permanents and exile all tokens. this.getSpellAbility().addEffect(new AEtherSnapEffect()); } @@ -88,13 +87,13 @@ class AEtherSnapEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - for (Permanent permanent :game.getBattlefield().getActivePermanents(new FilterPermanent(), controller.getId(), source.getSourceId(), game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterPermanent(), controller.getId(), source.getSourceId(), game)) { if (permanent instanceof PermanentToken) { controller.moveCardToExileWithInfo(permanent, null, "", source.getSourceId(), game, Zone.BATTLEFIELD, true); - } else if (!permanent.getCounters().isEmpty()){ + } else if (!permanent.getCounters().isEmpty()) { Counters counters = permanent.getCounters().copy(); - for (Counter counter: counters.values()) { - permanent.getCounters().removeCounter(counter.getName(), counter.getCount()); + for (Counter counter : counters.values()) { + permanent.removeCounters(counter, game); } } } diff --git a/Mage.Sets/src/mage/sets/commander2015/ThiefOfBlood.java b/Mage.Sets/src/mage/sets/commander2015/ThiefOfBlood.java index 31d1c181cd..1d68088026 100644 --- a/Mage.Sets/src/mage/sets/commander2015/ThiefOfBlood.java +++ b/Mage.Sets/src/mage/sets/commander2015/ThiefOfBlood.java @@ -57,10 +57,10 @@ public class ThiefOfBlood extends CardImpl { this.power = new MageInt(1); this.toughness = new MageInt(1); this.subtype.add("Vampire"); - + // Flying this.addAbility(FlyingAbility.getInstance()); - + // As Thief of Blood enters the battlefield, remove all counters from all permanents. Thief of Blood enters the battlefield with a +1/+1 counter on it for each counter removed this way. this.addAbility(new EntersBattlefieldAbility(new ThiefOfBloodEffect(), null, "As {this} enters the battlefield, remove all counters from all permanents. {this} enters the battlefield with a +1/+1 counter on it for each counter removed this way", null)); } @@ -76,33 +76,34 @@ public class ThiefOfBlood extends CardImpl { } class ThiefOfBloodEffect extends OneShotEffect { - + private static final FilterPermanent filter = new FilterPermanent("permanent with a counter"); + static { filter.add(new CounterPredicate(null)); } - + ThiefOfBloodEffect() { super(Outcome.BoostCreature); this.staticText = "remove all counters from all permanents. {this} enters the battlefield with a +1/+1 counter on it for each counter removed this way"; } - + ThiefOfBloodEffect(final ThiefOfBloodEffect effect) { super(effect); } - + @Override public ThiefOfBloodEffect copy() { return new ThiefOfBloodEffect(this); } - + @Override public boolean apply(Game game, Ability source) { int countersRemoved = 0; for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, source.getControllerId(), game)) { Counters counters = permanent.getCounters().copy(); for (Counter counter : counters.values()) { - permanent.getCounters().removeCounter(counter.getName(), counter.getCount()); + permanent.removeCounters(counter.getName(), counter.getCount(), game); countersRemoved += counter.getCount(); } } diff --git a/Mage.Sets/src/mage/sets/innistrad/LudevicsTestSubject.java b/Mage.Sets/src/mage/sets/innistrad/LudevicsTestSubject.java index 173bf694c5..c714f73699 100644 --- a/Mage.Sets/src/mage/sets/innistrad/LudevicsTestSubject.java +++ b/Mage.Sets/src/mage/sets/innistrad/LudevicsTestSubject.java @@ -28,9 +28,6 @@ package mage.sets.innistrad; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -41,7 +38,9 @@ import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.DefenderAbility; import mage.abilities.keyword.TransformAbility; import mage.cards.CardImpl; +import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Rarity; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; @@ -83,6 +82,7 @@ public class LudevicsTestSubject extends CardImpl { } class LudevicsTestSubjectEffect extends OneShotEffect { + LudevicsTestSubjectEffect() { super(Outcome.Benefit); staticText = "Then if there are five or more hatchling counters on it, remove all of them and transform it"; @@ -97,7 +97,7 @@ class LudevicsTestSubjectEffect extends OneShotEffect { Permanent p = game.getPermanent(source.getSourceId()); if (p != null) { if (p.getCounters().getCount(CounterType.HATCHLING) >= 5) { - p.getCounters().removeCounter(CounterType.HATCHLING, p.getCounters().getCount(CounterType.HATCHLING)); + p.removeCounters(CounterType.HATCHLING.getName(), p.getCounters().getCount(CounterType.HATCHLING), game); TransformSourceEffect effect = new TransformSourceEffect(true); return effect.apply(game, source); } @@ -109,4 +109,4 @@ class LudevicsTestSubjectEffect extends OneShotEffect { public LudevicsTestSubjectEffect copy() { return new LudevicsTestSubjectEffect(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/mirrodin/OblivionStone.java b/Mage.Sets/src/mage/sets/mirrodin/OblivionStone.java index d0ef92ef31..f272a735fc 100644 --- a/Mage.Sets/src/mage/sets/mirrodin/OblivionStone.java +++ b/Mage.Sets/src/mage/sets/mirrodin/OblivionStone.java @@ -28,9 +28,6 @@ package mage.sets.mirrodin; import java.util.UUID; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeSourceCost; @@ -39,7 +36,9 @@ import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.cards.CardImpl; +import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Rarity; import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; @@ -78,6 +77,7 @@ public class OblivionStone extends CardImpl { } class OblivionStoneEffect extends OneShotEffect { + OblivionStoneEffect() { super(Outcome.DestroyPermanent); staticText = "Destroy each nonland permanent without a fate counter on it, then remove all fate counters from all permanents"; @@ -96,7 +96,7 @@ class OblivionStoneEffect extends OneShotEffect { } for (Permanent p : game.getBattlefield().getAllActivePermanents()) { if (p.getCounters().containsKey(CounterType.FATE)) { - p.getCounters().removeCounter(CounterType.FATE, p.getCounters().getCount(CounterType.FATE)); + p.removeCounters(CounterType.FATE.getName(), p.getCounters().getCount(CounterType.FATE), game); } } return true; diff --git a/Mage.Sets/src/mage/sets/newphyrexia/HexParasite.java b/Mage.Sets/src/mage/sets/newphyrexia/HexParasite.java index dd9bf4bfea..7993a20441 100644 --- a/Mage.Sets/src/mage/sets/newphyrexia/HexParasite.java +++ b/Mage.Sets/src/mage/sets/newphyrexia/HexParasite.java @@ -103,13 +103,13 @@ class HexParasiteEffect extends OneShotEffect { for (String counterName : counterNames) { if (player.chooseUse(Outcome.Neutral, "Do you want to remove " + counterName + " counters?", source, game)) { if (permanent.getCounters().get(counterName).getCount() == 1 || toRemove == 1) { - permanent.getCounters().removeCounter(counterName, 1); + permanent.removeCounters(counterName, 1, game); removed++; } else { int amount = player.getAmount(1, Math.min(permanent.getCounters().get(counterName).getCount(), toRemove - removed), "How many?", game); if (amount > 0) { removed += amount; - permanent.getCounters().removeCounter(counterName, amount); + permanent.removeCounters(counterName, amount, game); } } } diff --git a/Mage.Sets/src/mage/sets/saviorsofkamigawa/DescendantOfMasumaro.java b/Mage.Sets/src/mage/sets/saviorsofkamigawa/DescendantOfMasumaro.java index 30fe71593a..46403355e8 100644 --- a/Mage.Sets/src/mage/sets/saviorsofkamigawa/DescendantOfMasumaro.java +++ b/Mage.Sets/src/mage/sets/saviorsofkamigawa/DescendantOfMasumaro.java @@ -101,8 +101,8 @@ class DescendantOfMasumaroEffect extends OneShotEffect { } Player targetOpponent = game.getPlayer(getTargetPointer().getFirst(game, source)); if (targetOpponent != null && targetOpponent.getHand().size() > 0) { - sourcePermanent.getCounters().removeCounter(CounterType.P1P1, targetOpponent.getHand().size()); - game.informPlayers(controller.getLogName() + " removes " + targetOpponent.getHand().size() + " +1/+1 counters from " + sourcePermanent.getLogName()); + sourcePermanent.removeCounters(CounterType.P1P1.getName(), targetOpponent.getHand().size(), game); + game.informPlayers(controller.getLogName() + " removes " + targetOpponent.getHand().size() + " +1/+1 counters from " + sourcePermanent.getLogName()); } return true; } diff --git a/Mage.Sets/src/mage/sets/stronghold/CrovaxTheCursed.java b/Mage.Sets/src/mage/sets/stronghold/CrovaxTheCursed.java index e52f5a3b31..907e6cfd8c 100644 --- a/Mage.Sets/src/mage/sets/stronghold/CrovaxTheCursed.java +++ b/Mage.Sets/src/mage/sets/stronghold/CrovaxTheCursed.java @@ -73,10 +73,9 @@ public class CrovaxTheCursed extends CardImpl { Ability ability = new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new CrovaxTheCursedEffect(), TargetController.YOU, false); this.addAbility(ability); - // {B}: Crovax gains flying until end of turn. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.EndOfTurn), new ManaCostsImpl("{B}"))); - + } public CrovaxTheCursed(final CrovaxTheCursed card) { @@ -118,11 +117,9 @@ class CrovaxTheCursedEffect extends OneShotEffect { game.informPlayers(controller.getLogName() + " puts a +1/+1 counter on " + sourceObject.getName()); } } - } else { - if (sourceObject != null && sourceObject.getCounters().containsKey(CounterType.P1P1)) { - sourceObject.getCounters().removeCounter(CounterType.P1P1, 1); - game.informPlayers(controller.getLogName() + " removes a +1/+1 counter from " + sourceObject.getName()); - } + } else if (sourceObject != null && sourceObject.getCounters().containsKey(CounterType.P1P1)) { + sourceObject.removeCounters(CounterType.P1P1.getName(), 1, game); + game.informPlayers(controller.getLogName() + " removes a +1/+1 counter from " + sourceObject.getName()); } return true; } diff --git a/Mage.Sets/src/mage/sets/tempest/Magmasaur.java b/Mage.Sets/src/mage/sets/tempest/Magmasaur.java index c305535b67..5d3b1d014f 100644 --- a/Mage.Sets/src/mage/sets/tempest/Magmasaur.java +++ b/Mage.Sets/src/mage/sets/tempest/Magmasaur.java @@ -107,10 +107,10 @@ class MagmasaurEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - Permanent sourceObject = (Permanent)source.getSourceObjectIfItStillExists(game); + Permanent sourceObject = (Permanent) source.getSourceObjectIfItStillExists(game); if (sourceObject != null && controller != null) { if (controller.chooseUse(outcome, "Remove a +1/+1 counter from " + sourceObject.getLogName() + "?", source, game)) { - sourceObject.getCounters().removeCounter(CounterType.P1P1, 1); + sourceObject.removeCounters(CounterType.P1P1.getName(), 1, game); } else { int counters = sourceObject.getCounters().getCount(CounterType.P1P1); sourceObject.sacrifice(source.getSourceId(), game); From 20e7130bb1f98b20a60484afeeb5ebeabdbbd9e1 Mon Sep 17 00:00:00 2001 From: rkfg Date: Fri, 4 Mar 2016 19:34:04 +0300 Subject: [PATCH 67/67] As per 6f9e316, not only this file doesn't help, it makes it impossible to reset --hard. --- .gitattributes | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index c18f26c92f..0000000000 --- a/.gitattributes +++ /dev/null @@ -1,17 +0,0 @@ -# These files are text and should be normalized (convert crlf => lf) -*.cs text diff=csharp -*.cmd text -*.txt text -*.xml text -*.md text -*.mf text -*.java text -*.form text - -# Images should be treated as binary -# (binary is a macro for -text -diff) -*.png binary -*.gif binary -*.jepg binary - -*.sdf binary \ No newline at end of file