From 780ed417c73beea855e5f880a6253b266787213e Mon Sep 17 00:00:00 2001 From: myk Date: Fri, 29 Mar 2013 17:47:02 +0000 Subject: [PATCH 001/123] show front face of facedown cards in VPicture when player can legitmately look at them --- src/main/java/forge/Card.java | 8 ++++++++ src/main/java/forge/ImageCache.java | 16 ++++++++++++---- src/main/java/forge/gui/CardPicturePanel.java | 6 +++--- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index d4c11d29f3d..7889df8588c 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -8426,6 +8426,14 @@ public class Card extends GameEntity implements Comparable { public final String getImageKey() { return this.getCharacteristics().getImageKey(); } + + public final String getImageKey(CardCharacteristicName state) { + CardCharacteristics characteristics = characteristicsMap.get(state); + if (null == characteristics) { + return null; + } + return characteristics.getImageKey(); + } /** *

diff --git a/src/main/java/forge/ImageCache.java b/src/main/java/forge/ImageCache.java index 5997146fd3d..d53a016b5a1 100644 --- a/src/main/java/forge/ImageCache.java +++ b/src/main/java/forge/ImageCache.java @@ -99,21 +99,29 @@ public class ImageCache { } /** - * retrieve an image from the cache. returns null if the image is not found in the cache + * retrieve an image from the cache. returns a default image if the image is not found in the cache * and cannot be loaded from disk. pass -1 for width and/or height to avoid resizing in that dimension. */ public static BufferedImage getImage(Card card, int width, int height) { + return getImage(card, width, height, false); + } + + /** + * retrieve an image from the cache. returns a default image if the image is not found in the cache + * and cannot be loaded from disk. pass -1 for width and/or height to avoid resizing in that dimension. + */ + public static BufferedImage getImage(Card card, int width, int height, boolean ignoreFaceDown) { final String key; - if (!card.canBeShownTo(Singletons.getControl().getPlayer()) || card.isFaceDown()) { + if (!card.canBeShownTo(Singletons.getControl().getPlayer()) || (!ignoreFaceDown && card.isFaceDown())) { key = TOKEN_PREFIX + NewConstants.CACHE_MORPH_IMAGE_FILE; } else { - key = card.getImageKey(); + key = ignoreFaceDown ? card.getImageKey(CardCharacteristicName.Original) : card.getImageKey(); } return scaleImage(key, width, height, true); } /** - * retrieve an image from the cache. returns null if the image is not found in the cache + * retrieve an image from the cache. returns a default image if the image is not found in the cache * and cannot be loaded from disk. pass -1 for width and/or height to avoid resizing in that dimension. */ public static BufferedImage getImage(InventoryItem ii, int width, int height) { diff --git a/src/main/java/forge/gui/CardPicturePanel.java b/src/main/java/forge/gui/CardPicturePanel.java index 663108d6ff9..2fda6312c47 100644 --- a/src/main/java/forge/gui/CardPicturePanel.java +++ b/src/main/java/forge/gui/CardPicturePanel.java @@ -86,9 +86,9 @@ public final class CardPicturePanel extends JPanel { if (displayed instanceof InventoryItem) { image = ImageCache.getImage((InventoryItem)this.displayed, this.getWidth() - i.left - i.right, this.getHeight() - i.top - i.bottom); - } else if ( displayed instanceof Card ) { - image = ImageCache.getImage((Card)this.displayed, this.getWidth() - i.left - i.right - 2, this.getHeight() - i.top - - i.bottom - 2); + } else if (displayed instanceof Card) { + image = ImageCache.getImage((Card)this.displayed, + this.getWidth() - i.left - i.right - 2, this.getHeight() - i.top - i.bottom - 2, true); } if (image != this.currentImage) { From 66afdd9c225658a39e45dbde802e6c0519ed8e4d Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Fri, 29 Mar 2013 21:17:52 +0000 Subject: [PATCH 002/123] revert 20668 set Morphs visible to player flippable on CPicture and CDetail If you mouse over a card with SHIFT pressed, MatchUI will attempt to show you the other side of a card. --- src/main/java/forge/Card.java | 8 -------- src/main/java/forge/ImageCache.java | 16 ++++------------ .../forge/card/cardfactory/CardFactoryUtil.java | 3 --- src/main/java/forge/gui/CardPicturePanel.java | 6 +++--- src/main/java/forge/gui/match/CMatchUI.java | 6 +++++- .../forge/gui/match/controllers/CDetail.java | 3 ++- .../forge/gui/match/controllers/CPicture.java | 16 ++++++++++++---- .../forge/gui/match/nonsingleton/CCommand.java | 4 +++- .../forge/gui/match/nonsingleton/CField.java | 4 +++- 9 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index 7889df8588c..d4c11d29f3d 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -8426,14 +8426,6 @@ public class Card extends GameEntity implements Comparable { public final String getImageKey() { return this.getCharacteristics().getImageKey(); } - - public final String getImageKey(CardCharacteristicName state) { - CardCharacteristics characteristics = characteristicsMap.get(state); - if (null == characteristics) { - return null; - } - return characteristics.getImageKey(); - } /** *

diff --git a/src/main/java/forge/ImageCache.java b/src/main/java/forge/ImageCache.java index d53a016b5a1..5997146fd3d 100644 --- a/src/main/java/forge/ImageCache.java +++ b/src/main/java/forge/ImageCache.java @@ -99,29 +99,21 @@ public class ImageCache { } /** - * retrieve an image from the cache. returns a default image if the image is not found in the cache + * retrieve an image from the cache. returns null if the image is not found in the cache * and cannot be loaded from disk. pass -1 for width and/or height to avoid resizing in that dimension. */ public static BufferedImage getImage(Card card, int width, int height) { - return getImage(card, width, height, false); - } - - /** - * retrieve an image from the cache. returns a default image if the image is not found in the cache - * and cannot be loaded from disk. pass -1 for width and/or height to avoid resizing in that dimension. - */ - public static BufferedImage getImage(Card card, int width, int height, boolean ignoreFaceDown) { final String key; - if (!card.canBeShownTo(Singletons.getControl().getPlayer()) || (!ignoreFaceDown && card.isFaceDown())) { + if (!card.canBeShownTo(Singletons.getControl().getPlayer()) || card.isFaceDown()) { key = TOKEN_PREFIX + NewConstants.CACHE_MORPH_IMAGE_FILE; } else { - key = ignoreFaceDown ? card.getImageKey(CardCharacteristicName.Original) : card.getImageKey(); + key = card.getImageKey(); } return scaleImage(key, width, height, true); } /** - * retrieve an image from the cache. returns a default image if the image is not found in the cache + * retrieve an image from the cache. returns null if the image is not found in the cache * and cannot be loaded from disk. pass -1 for width and/or height to avoid resizing in that dimension. */ public static BufferedImage getImage(InventoryItem ii, int width, int height) { diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index 71078439ed6..a5eff8cf730 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -63,7 +63,6 @@ import forge.card.spellability.Target; import forge.card.trigger.Trigger; import forge.card.trigger.TriggerHandler; import forge.card.trigger.TriggerType; -import forge.control.input.InputBase; import forge.control.input.InputSelectCards; import forge.control.input.InputSelectCardsFromList; import forge.game.GameState; @@ -78,9 +77,7 @@ import forge.game.zone.PlayerZone; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; -import forge.gui.match.CMatchUI; import forge.util.Aggregates; -import forge.view.ButtonUtil; /** *

diff --git a/src/main/java/forge/gui/CardPicturePanel.java b/src/main/java/forge/gui/CardPicturePanel.java index 2fda6312c47..663108d6ff9 100644 --- a/src/main/java/forge/gui/CardPicturePanel.java +++ b/src/main/java/forge/gui/CardPicturePanel.java @@ -86,9 +86,9 @@ public final class CardPicturePanel extends JPanel { if (displayed instanceof InventoryItem) { image = ImageCache.getImage((InventoryItem)this.displayed, this.getWidth() - i.left - i.right, this.getHeight() - i.top - i.bottom); - } else if (displayed instanceof Card) { - image = ImageCache.getImage((Card)this.displayed, - this.getWidth() - i.left - i.right - 2, this.getHeight() - i.top - i.bottom - 2, true); + } else if ( displayed instanceof Card ) { + image = ImageCache.getImage((Card)this.displayed, this.getWidth() - i.left - i.right - 2, this.getHeight() - i.top + - i.bottom - 2); } if (image != this.currentImage) { diff --git a/src/main/java/forge/gui/match/CMatchUI.java b/src/main/java/forge/gui/match/CMatchUI.java index 6256c649b87..505a8deac99 100644 --- a/src/main/java/forge/gui/match/CMatchUI.java +++ b/src/main/java/forge/gui/match/CMatchUI.java @@ -236,8 +236,12 @@ public enum CMatchUI { } public void setCard(final Card c) { + setCard(c, false); + } + + public void setCard(final Card c, final boolean showFlipped ) { CDetail.SINGLETON_INSTANCE.showCard(c); - CPicture.SINGLETON_INSTANCE.showCard(c); + CPicture.SINGLETON_INSTANCE.showCard(c, showFlipped); } public void setCard(final InventoryItem c) { diff --git a/src/main/java/forge/gui/match/controllers/CDetail.java b/src/main/java/forge/gui/match/controllers/CDetail.java index f34b628af02..d27cbcc0d04 100644 --- a/src/main/java/forge/gui/match/controllers/CDetail.java +++ b/src/main/java/forge/gui/match/controllers/CDetail.java @@ -22,6 +22,7 @@ import java.awt.event.MouseEvent; import forge.Card; import forge.Command; +import forge.Singletons; import forge.gui.framework.ICDoc; import forge.gui.match.views.VDetail; import forge.item.IPaperCard; @@ -44,7 +45,7 @@ public enum CDetail implements ICDoc { * @param c   Card object */ public void showCard(final Card c) { - view.getLblFlipcard().setVisible(c != null && (c.isDoubleFaced() || c.isFlipCard())); + view.getLblFlipcard().setVisible(c != null && (c.isDoubleFaced() || c.isFlipCard() || c.isFaceDown() && c.canBeShownTo(Singletons.getControl().getPlayer()))); view.getPnlDetail().setCard(c); view.getParentCell().repaintSelf(); } diff --git a/src/main/java/forge/gui/match/controllers/CPicture.java b/src/main/java/forge/gui/match/controllers/CPicture.java index 8dae6eb2f74..6444324dddb 100644 --- a/src/main/java/forge/gui/match/controllers/CPicture.java +++ b/src/main/java/forge/gui/match/controllers/CPicture.java @@ -22,6 +22,7 @@ import java.awt.event.MouseEvent; import forge.Card; import forge.CardCharacteristicName; import forge.Command; +import forge.Singletons; import forge.gui.framework.ICDoc; import forge.gui.match.views.VPicture; import forge.item.IPaperCard; @@ -38,6 +39,7 @@ public enum CPicture implements ICDoc { private Card currentCard = null; private boolean flipped = false; private boolean canFlip = false; + private boolean cameFaceDown = false; /** * Shows card details and/or picture in sidebar cardview tabber. @@ -45,18 +47,22 @@ public enum CPicture implements ICDoc { * @param c *   Card object */ - public void showCard(final Card c) { - canFlip = c != null && (c.isDoubleFaced() || c.isFlipCard()); + public void showCard(final Card c, boolean showFlipped) { + cameFaceDown = c.isFaceDown() && c.canBeShownTo(Singletons.getControl().getPlayer()); + canFlip = c != null && (c.isDoubleFaced() || c.isFlipCard() || cameFaceDown); currentCard = c; flipped = canFlip && (c.getCurState() == CardCharacteristicName.Transformed || - c.getCurState() == CardCharacteristicName.Flipped); + c.getCurState() == CardCharacteristicName.Flipped || c.getCurState() == CardCharacteristicName.FaceDown); VPicture.SINGLETON_INSTANCE.getLblFlipcard().setVisible(canFlip); VPicture.SINGLETON_INSTANCE.getPnlPicture().setCard(c); + + if ( showFlipped && canFlip ) + flipCard(); } public void showCard(final InventoryItem item) { if (item instanceof IPaperCard) { - showCard(((IPaperCard)item).getMatchingForgeCard()); + showCard(((IPaperCard)item).getMatchingForgeCard(), false); return; } @@ -101,6 +107,8 @@ public enum CPicture implements ICDoc { newState = CardCharacteristicName.Transformed; } else if (currentCard.isFlipCard()) { newState = CardCharacteristicName.Flipped; + } else if ( cameFaceDown ) { + newState = CardCharacteristicName.FaceDown; } else { // if this is hit, then then showCard has been modified to handle additional types, but // this function is missing an else if statement above diff --git a/src/main/java/forge/gui/match/nonsingleton/CCommand.java b/src/main/java/forge/gui/match/nonsingleton/CCommand.java index 99546328178..0d5559ecf49 100644 --- a/src/main/java/forge/gui/match/nonsingleton/CCommand.java +++ b/src/main/java/forge/gui/match/nonsingleton/CCommand.java @@ -17,6 +17,7 @@ */ package forge.gui.match.nonsingleton; +import java.awt.Event; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; @@ -99,9 +100,10 @@ public class CCommand implements ICDoc { /** */ private void cardoverAction(MouseEvent e) { + boolean isShiftDown = (e.getModifiers() & Event.SHIFT_MASK) != 0; final Card c = CCommand.this.view.getTabletop().getHoveredCard(e); if (c != null) { - CMatchUI.SINGLETON_INSTANCE.setCard(c); + CMatchUI.SINGLETON_INSTANCE.setCard(c, isShiftDown); } } diff --git a/src/main/java/forge/gui/match/nonsingleton/CField.java b/src/main/java/forge/gui/match/nonsingleton/CField.java index 6b3a0561f9e..51e5f29125a 100644 --- a/src/main/java/forge/gui/match/nonsingleton/CField.java +++ b/src/main/java/forge/gui/match/nonsingleton/CField.java @@ -17,6 +17,7 @@ */ package forge.gui.match.nonsingleton; +import java.awt.Event; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -375,9 +376,10 @@ public class CField implements ICDoc { /** */ private void cardoverAction(MouseEvent e) { + boolean isShiftDown = (e.getModifiers() & Event.SHIFT_MASK) != 0; final Card c = CField.this.view.getTabletop().getHoveredCard(e); if (c != null) { - CMatchUI.SINGLETON_INSTANCE.setCard(c); + CMatchUI.SINGLETON_INSTANCE.setCard(c, isShiftDown); } } From df82cd3fb4c90ae792b695dc3d92414f7dc0001d Mon Sep 17 00:00:00 2001 From: swordshine Date: Sat, 30 Mar 2013 04:59:02 +0000 Subject: [PATCH 003/123] - Added Soulbright Flamekin --- .gitattributes | 1 + res/cardsfolder/s/soulbright_flamekin.txt | 16 ++++++++++++++++ .../forge/card/ability/effects/ManaEffect.java | 7 +++++++ 3 files changed, 24 insertions(+) create mode 100644 res/cardsfolder/s/soulbright_flamekin.txt diff --git a/.gitattributes b/.gitattributes index 58b2e4bc3f9..f171d371d86 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9946,6 +9946,7 @@ res/cardsfolder/s/soul_tithe.txt -text res/cardsfolder/s/soul_warden.txt svneol=native#text/plain res/cardsfolder/s/soulblast.txt -text res/cardsfolder/s/soulbound_guardians.txt svneol=native#text/plain +res/cardsfolder/s/soulbright_flamekin.txt -text res/cardsfolder/s/soulcage_fiend.txt -text res/cardsfolder/s/soulcatcher.txt svneol=native#text/plain res/cardsfolder/s/soulcatchers_aerie.txt svneol=native#text/plain diff --git a/res/cardsfolder/s/soulbright_flamekin.txt b/res/cardsfolder/s/soulbright_flamekin.txt new file mode 100644 index 00000000000..358d61a0e3c --- /dev/null +++ b/res/cardsfolder/s/soulbright_flamekin.txt @@ -0,0 +1,16 @@ +Name:Soulbright Flamekin +ManaCost:1 R +Types:Creature Elemental Shaman +PT:2/1 +A:AB$ Pump | Cost$ 2 | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Trample | SubAbility$ StoreNum | SpellDescription$ Target creature gains trample until end of turn. If this is the third time this ability has resolved this turn, you may add R R R R R R R R to your mana pool. +SVar:StoreNum:DB$ StoreSVar | SVar$ SoulbrightNum | Type$ CountSVar | Expression$ SoulbrightNum/Plus.1 | SubAbility$ SoulbrightMana +SVar:SoulbrightMana:DB$ Mana | Produced$ R R R R R R R R | ConditionCheckSVar$ SoulbrightNum | ConditionSVarCompare$ EQ3 | Optional$ True +SVar:SoulbrightNum:Number$0 +T:Mode$ Phase | Phase$ Cleanup | Execute$ TrigReset | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReset | Static$ True +SVar:TrigReset:AB$ StoreSVar | Cost$ 0 | SVar$ SoulbrightNum | Type$ Number | Expression$ 0 +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/soulbright_flamekin.jpg +Oracle:{2}: Target creature gains trample until end of turn. If this is the third time this ability has resolved this turn, you may add {R}{R}{R}{R}{R}{R}{R}{R} to your mana pool. +SetInfo:DD2 Common +SetInfo:LRW Common \ No newline at end of file diff --git a/src/main/java/forge/card/ability/effects/ManaEffect.java b/src/main/java/forge/card/ability/effects/ManaEffect.java index 6af32728246..669217c6e61 100644 --- a/src/main/java/forge/card/ability/effects/ManaEffect.java +++ b/src/main/java/forge/card/ability/effects/ManaEffect.java @@ -18,6 +18,7 @@ import forge.game.ai.ComputerUtilCard; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; +import forge.gui.GuiDialog; public class ManaEffect extends SpellAbilityEffect { @@ -37,7 +38,13 @@ public class ManaEffect extends SpellAbilityEffect { final List tgtPlayers = getTargetPlayers(sa); final Target tgt = sa.getTarget(); + final boolean optional = sa.hasParam("Optional"); + if (optional) { + if (!GuiDialog.confirm(sa.getSourceCard(), "Do you want to add mana to your mana pool?")) { + return; + } + } if (abMana.isComboMana()) { for (Player p : tgtPlayers) { int amount = sa.hasParam("Amount") ? AbilityUtils.calculateAmount(card, sa.getParam("Amount"), sa) : 1; From 7e5701953bbbbc0b8db2853fdfeb4684ff139705 Mon Sep 17 00:00:00 2001 From: Hellfish Date: Sat, 30 Mar 2013 08:04:17 +0000 Subject: [PATCH 004/123] *Added Color Identity checks --- res/cardsfolder/b/bosh_iron_golem.txt | 1 + res/cardsfolder/m/memnarch.txt | 1 + res/cardsfolder/r/rhys_the_exiled.txt | 1 + res/cardsfolder/t/thelon_of_havenwood.txt | 1 + src/main/java/forge/card/CardFace.java | 12 +++++- src/main/java/forge/card/CardRules.java | 13 +++++++ .../java/forge/card/CardRulesPredicates.java | 3 ++ src/main/java/forge/card/CardRulesReader.java | 6 +++ .../java/forge/card/ICardCharacteristics.java | 1 + src/main/java/forge/deck/DeckFormat.java | 37 +++++++++++++++++-- .../controllers/CEditorConstructed.java | 13 ++++++- 11 files changed, 83 insertions(+), 6 deletions(-) diff --git a/res/cardsfolder/b/bosh_iron_golem.txt b/res/cardsfolder/b/bosh_iron_golem.txt index 635bebc6bef..6128f26923a 100644 --- a/res/cardsfolder/b/bosh_iron_golem.txt +++ b/res/cardsfolder/b/bosh_iron_golem.txt @@ -1,5 +1,6 @@ Name:Bosh, Iron Golem ManaCost:8 +ColorIdentity:Red Types:Legendary Artifact Creature Golem PT:6/7 K:Trample diff --git a/res/cardsfolder/m/memnarch.txt b/res/cardsfolder/m/memnarch.txt index 5e8efff8ee3..f1048bd1d7d 100644 --- a/res/cardsfolder/m/memnarch.txt +++ b/res/cardsfolder/m/memnarch.txt @@ -1,5 +1,6 @@ Name:Memnarch ManaCost:7 +ColorIdentity:Blue Types:Legendary Artifact Creature Wizard PT:4/5 A:AB$ Animate | Cost$ 1 U U | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | Types$ Artifact | Permanent$ True | SpellDescription$ Target permanent becomes an artifact in addition to its other types. (This effect lasts indefinitely.) diff --git a/res/cardsfolder/r/rhys_the_exiled.txt b/res/cardsfolder/r/rhys_the_exiled.txt index a6201bfb7c7..b46c8d02484 100644 --- a/res/cardsfolder/r/rhys_the_exiled.txt +++ b/res/cardsfolder/r/rhys_the_exiled.txt @@ -1,5 +1,6 @@ Name:Rhys the Exiled ManaCost:2 G +ColorIdentity:Green,Black Types:Legendary Creature Elf Warrior PT:3/2 T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigGainLife | TriggerDescription$ Whenever CARDNAME attacks, you gain 1 life for each Elf you control. diff --git a/res/cardsfolder/t/thelon_of_havenwood.txt b/res/cardsfolder/t/thelon_of_havenwood.txt index 026df543744..9f9c3ac8c43 100644 --- a/res/cardsfolder/t/thelon_of_havenwood.txt +++ b/res/cardsfolder/t/thelon_of_havenwood.txt @@ -1,5 +1,6 @@ Name:Thelon of Havenwood ManaCost:G G +ColorIdentity:Green,Black Types:Legendary Creature Elf Druid PT:2/2 S:Mode$ Continuous | Affected$ Creature.Fungus | AffectedZone$ Battlefield | AddPower$ AffectedX | AddToughness$ AffectedX | Description$ Each Fungus creature gets +1/+1 for each spore counter on it. diff --git a/src/main/java/forge/card/CardFace.java b/src/main/java/forge/card/CardFace.java index da406256ae8..9158c28d22b 100644 --- a/src/main/java/forge/card/CardFace.java +++ b/src/main/java/forge/card/CardFace.java @@ -29,6 +29,7 @@ final class CardFace implements ICardFace { private CardType type = null; private ManaCost manaCost = ManaCost.NO_COST; private ColorSet color = null; + private ColorSet colorIdentity = null; private String oracleText = null; private int iPower = -1; @@ -59,6 +60,14 @@ final class CardFace implements ICardFace { @Override public ManaCost getManaCost() { return this.manaCost; } @Override public ColorSet getColor() { return this.color; } + @Override + public ColorSet getColorIdentity() { + if(this.colorIdentity != null) + return this.colorIdentity; + + return this.color; + } + // these are raw and unparsed used for Card creation @Override public Iterable getKeywords() { return keywords; } @Override public Iterable getAbilities() { return abilities; } @@ -77,6 +86,7 @@ final class CardFace implements ICardFace { public void setType(CardType type0) { this.type = type0; } public void setManaCost(ManaCost manaCost0) { this.manaCost = manaCost0; } public void setColor(ColorSet color0) { this.color = color0; } + public void setColorIdentity(ColorSet color0) { this.colorIdentity = color0; } public void setOracleText(String text) { this.oracleText = text; } public void setInitialLoyalty(int value) { this.initialLoyalty = value; } @@ -114,6 +124,4 @@ final class CardFace implements ICardFace { if ( variables == null ) variables = emptyMap; if ( null == nonAbilityText ) nonAbilityText = ""; } - - } diff --git a/src/main/java/forge/card/CardRules.java b/src/main/java/forge/card/CardRules.java index 7c1549e469c..b7302e01037 100644 --- a/src/main/java/forge/card/CardRules.java +++ b/src/main/java/forge/card/CardRules.java @@ -177,4 +177,17 @@ public final class CardRules implements ICardCharacteristics { public final List getAbilities() { return null; } + + @Override + public ColorSet getColorIdentity() { + if(this.otherPart != null) + { + return ColorSet.fromMask(mainPart.getColorIdentity().getColor() | otherPart.getColorIdentity().getColor()); + } + else + { + return mainPart.getColorIdentity(); + } + + } } diff --git a/src/main/java/forge/card/CardRulesPredicates.java b/src/main/java/forge/card/CardRulesPredicates.java index a6723e4515a..733fb683029 100644 --- a/src/main/java/forge/card/CardRulesPredicates.java +++ b/src/main/java/forge/card/CardRulesPredicates.java @@ -455,6 +455,9 @@ public final class CardRulesPredicates { /** The Constant isCreature. */ public static final Predicate IS_CREATURE = CardRulesPredicates .coreType(true, CardCoreType.Creature); + + public static final Predicate IS_LEGENDARY = CardRulesPredicates + .superType(true, CardSuperType.Legendary); /** The Constant isArtifact. */ public static final Predicate IS_ARTIFACT = CardRulesPredicates diff --git a/src/main/java/forge/card/CardRulesReader.java b/src/main/java/forge/card/CardRulesReader.java index fb1ff50e9c2..9b57188c3fb 100644 --- a/src/main/java/forge/card/CardRulesReader.java +++ b/src/main/java/forge/card/CardRulesReader.java @@ -138,6 +138,12 @@ public class CardRulesReader { ColorSet newCol = ColorSet.fromNames(value.split(",")); this.faces[this.curFace].setColor(newCol); } + else if ("ColorIdentity".equals(key)) { + // This is forge.card.CardColor not forge.CardColor. + // Why do we have two classes with the same name? + ColorSet newCol = ColorSet.fromNames(value.split(",")); + this.faces[this.curFace].setColorIdentity(newCol); + } break; case 'D': diff --git a/src/main/java/forge/card/ICardCharacteristics.java b/src/main/java/forge/card/ICardCharacteristics.java index 3b97955f77d..b38923225be 100644 --- a/src/main/java/forge/card/ICardCharacteristics.java +++ b/src/main/java/forge/card/ICardCharacteristics.java @@ -7,6 +7,7 @@ public interface ICardCharacteristics { CardType getType(); ManaCost getManaCost(); ColorSet getColor(); + ColorSet getColorIdentity(); int getIntPower(); int getIntToughness(); diff --git a/src/main/java/forge/deck/DeckFormat.java b/src/main/java/forge/deck/DeckFormat.java index 4e4ee50cc4a..e095f4dd70b 100644 --- a/src/main/java/forge/deck/DeckFormat.java +++ b/src/main/java/forge/deck/DeckFormat.java @@ -17,6 +17,7 @@ */ package forge.deck; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map.Entry; @@ -25,6 +26,7 @@ import org.apache.commons.lang.math.IntRange; import forge.Singletons; import forge.card.CardCoreType; +import forge.card.ColorSet; import forge.item.CardDb; import forge.item.CardPrinted; import forge.item.IPaperCard; @@ -137,18 +139,47 @@ public enum DeckFormat { if (null == cmd || cmd.isEmpty()) { return "is missing a commander"; } - if (!cmd.get(0).getRules().getType().isLegendary()) { + if (!cmd.get(0).getRules().getType().isLegendary() + || !cmd.get(0).getRules().getType().isCreature()) { return "has a commander that is not a legendary creature"; } - //TODO:Enforce color identity + ColorSet cmdCI = cmd.get(0).getRules().getColorIdentity(); + List erroneousCI = new ArrayList(); + + for(Entry cp : deck.get(DeckSection.Main)) { + if(!cp.getKey().getRules().getColorIdentity().hasNoColorsExcept(cmdCI.getColor())) + { + erroneousCI.add(cp.getKey()); + } + } + for(Entry cp : deck.get(DeckSection.Sideboard)) { + if(!cp.getKey().getRules().getColorIdentity().hasNoColorsExcept(cmdCI.getColor())) + { + erroneousCI.add(cp.getKey()); + } + } + + if(erroneousCI.size() > 0) + { + StringBuilder sb = new StringBuilder("contains the cards " + System.lineSeparator()); + + for(CardPrinted cp : erroneousCI) + { + sb.append(cp.getName() + ", " + System.lineSeparator()); + } + + sb.append(" that do not match the commanders color identity."); + + return sb.toString(); + } break; case Planechase: //Must contain at least 10 planes/phenomenons, but max 2 phenomenons. Singleton. final CardPool planes = deck.get(DeckSection.Planes); if (planes == null || planes.countAll() < 10) { - return "should gave at least 10 planes"; + return "should have at least 10 planes"; } int phenoms = 0; for (Entry cp : planes) { diff --git a/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java b/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java index a2683e15652..0e001fade13 100644 --- a/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java +++ b/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java @@ -68,6 +68,7 @@ public final class CEditorConstructed extends ACEditorBase { private final ItemPoolView avatarPool; private final ItemPoolView planePool; private final ItemPoolView schemePool; + private final ItemPoolView commanderPool; //=========== Constructor /** @@ -89,7 +90,8 @@ public final class CEditorConstructed extends ACEditorBase { avatarPool = ItemPool.createFrom(CardDb.variants().getAllCards(Predicates.compose(CardRulesPredicates.Presets.IS_VANGUARD, CardPrinted.FN_GET_RULES)),CardPrinted.class); planePool = ItemPool.createFrom(CardDb.variants().getAllCards(Predicates.compose(CardRulesPredicates.Presets.IS_PLANE_OR_PHENOMENON, CardPrinted.FN_GET_RULES)),CardPrinted.class); schemePool = ItemPool.createFrom(CardDb.variants().getAllCards(Predicates.compose(CardRulesPredicates.Presets.IS_SCHEME, CardPrinted.FN_GET_RULES)),CardPrinted.class); - + commanderPool = ItemPool.createFrom(CardDb.instance().getAllCards(Predicates.compose(Predicates.and(CardRulesPredicates.Presets.IS_CREATURE,CardRulesPredicates.Presets.IS_LEGENDARY), CardPrinted.FN_GET_RULES)),CardPrinted.class); + boolean wantUnique = SEditorIO.getPref(EditorPreference.display_unique_only); final EditorTableView tblCatalog = new EditorTableView(wantUnique, CardPrinted.class); @@ -276,6 +278,15 @@ public final class CEditorConstructed extends ACEditorBase { title = "Scheme"; tabtext = "Card Catalog"; break; + case Commander: + lstCatalogCols.remove(SColumnUtil.getColumn(ColumnName.CAT_QUANTITY)); + this.getTableCatalog().setAvailableColumns(lstCatalogCols); + this.getTableCatalog().setDeck(commanderPool,true); + this.getTableDeck().setDeck(this.controller.getModel().getOrCreate(DeckSection.Commander)); + showOptions = false; + title = "Commander"; + tabtext = "Card Catalog"; + break; } VCardCatalog.SINGLETON_INSTANCE.getTabLabel().setText(tabtext); From a7d6ca68f9f697a5d2c2ac3dbadf8eb84b0e0e53 Mon Sep 17 00:00:00 2001 From: myk Date: Sat, 30 Mar 2013 09:01:42 +0000 Subject: [PATCH 005/123] fix build errors on java 6 --- src/main/java/forge/deck/DeckFormat.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/forge/deck/DeckFormat.java b/src/main/java/forge/deck/DeckFormat.java index e095f4dd70b..f0514d80c90 100644 --- a/src/main/java/forge/deck/DeckFormat.java +++ b/src/main/java/forge/deck/DeckFormat.java @@ -162,15 +162,13 @@ public enum DeckFormat { if(erroneousCI.size() > 0) { - StringBuilder sb = new StringBuilder("contains the cards " + System.lineSeparator()); + StringBuilder sb = new StringBuilder("contains card that do not match the commanders color identity:"); for(CardPrinted cp : erroneousCI) { - sb.append(cp.getName() + ", " + System.lineSeparator()); + sb.append("\n").append(cp.getName()); } - sb.append(" that do not match the commanders color identity."); - return sb.toString(); } From ba5c9fbc8479ddba0efcbe3fb30b2b839e4a44f7 Mon Sep 17 00:00:00 2001 From: swordshine Date: Sat, 30 Mar 2013 09:02:17 +0000 Subject: [PATCH 006/123] - Fixed Wound Reflection --- res/cardsfolder/w/wound_reflection.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/cardsfolder/w/wound_reflection.txt b/res/cardsfolder/w/wound_reflection.txt index 0e10d06490c..fee9e1b1822 100644 --- a/res/cardsfolder/w/wound_reflection.txt +++ b/res/cardsfolder/w/wound_reflection.txt @@ -2,7 +2,7 @@ Name:Wound Reflection ManaCost:5 B Types:Enchantment T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | Execute$ RepeatOpps | TriggerDescription$ At the beginning of each end step, each opponent loses life equal to the life he or she lost this turn. (Damage causes loss of life.) -SVar:RepeatOpps:AB$ RepeatEach | Cost$ 0 | RepeatPlayers$ Player | RepeatSubAbility$ TrigLoseLife +SVar:RepeatOpps:AB$ RepeatEach | Cost$ 0 | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ TrigLoseLife SVar:TrigLoseLife:DB$ LoseLife | Defined$ Remembered | LifeAmount$ X | References$ X SVar:X:PlayerCountRemembered$LifeLostThisTurn SVar:Picture:http://www.wizards.com/global/images/magic/general/wound_reflection.jpg From c238925c22d737c5ae5dfab78cba9f69d6db21e2 Mon Sep 17 00:00:00 2001 From: swordshine Date: Sat, 30 Mar 2013 12:37:09 +0000 Subject: [PATCH 007/123] - Added Koskun Falls --- .gitattributes | 1 + res/cardsfolder/k/koskun_falls.txt | 11 +++++++++ src/main/java/forge/game/GameActionUtil.java | 26 ++++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 res/cardsfolder/k/koskun_falls.txt diff --git a/.gitattributes b/.gitattributes index f171d371d86..abca738a842 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5779,6 +5779,7 @@ res/cardsfolder/k/korlash_heir_to_blackblade.txt svneol=native#text/plain res/cardsfolder/k/kormus_bell.txt svneol=native#text/plain res/cardsfolder/k/korozda_guildmage.txt -text res/cardsfolder/k/korozda_monitor.txt -text +res/cardsfolder/k/koskun_falls.txt -text res/cardsfolder/k/koskun_keep.txt svneol=native#text/plain res/cardsfolder/k/koth_of_the_hammer.txt svneol=native#text/plain res/cardsfolder/k/koths_courier.txt svneol=native#text/plain diff --git a/res/cardsfolder/k/koskun_falls.txt b/res/cardsfolder/k/koskun_falls.txt new file mode 100644 index 00000000000..0ae87ef0de7 --- /dev/null +++ b/res/cardsfolder/k/koskun_falls.txt @@ -0,0 +1,11 @@ +Name:Koskun Falls +ManaCost:2 B B +Types:World Enchantment +S:Mode$ CantAttackUnless | ValidCard$ Creature | Target$ You | Cost$ 2 | Description$ Creatures can't attack you unless their controller pays 2 for each creature he or she controls that's attacking you. +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSac | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you tap an untapped creature you control. +SVar:TrigSac:AB$ Sacrifice | Cost$ 0 | Defined$ Self | UnlessCost$ tapXType<1/Creature> | UnlessPayer$ You +SVar:NeedsToPlayVar:Y GE1 +SVar:Y:Count$Valid Creature.YouCtrl +SVar:Picture:http://www.wizards.com/global/images/magic/general/koskun_fallss.jpg +Oracle:At the beginning of your upkeep, sacrifice Koskun Falls unless you tap an untapped creature you control.\nCreatures can't attack you unless their controller pays {2} for each creature he or she controls that's attacking you. +SetInfo:HML Rare \ No newline at end of file diff --git a/src/main/java/forge/game/GameActionUtil.java b/src/main/java/forge/game/GameActionUtil.java index 01137a5afa6..3131c1cde59 100644 --- a/src/main/java/forge/game/GameActionUtil.java +++ b/src/main/java/forge/game/GameActionUtil.java @@ -51,6 +51,7 @@ import forge.card.cost.CostPutCounter; import forge.card.cost.CostRemoveCounter; import forge.card.cost.CostReturn; import forge.card.cost.CostSacrifice; +import forge.card.cost.CostTapType; import forge.card.cost.CostUtil; import forge.card.mana.ManaCost; import forge.card.spellability.Ability; @@ -563,6 +564,31 @@ public final class GameActionUtil { cpl.reportPaidCardsTo(sourceAbility); } + else if (part instanceof CostTapType) { + List choiceList = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), p, source); + choiceList = CardLists.filter(choiceList, Presets.UNTAPPED); + int amount = getAmountFromPartX(part, source, sourceAbility); + + if (choiceList.size() < amount) { + // unable to pay (not enough cards) + return false; + } + + InputSelectCards inp = new InputSelectCardsFromList(amount, amount, choiceList); + inp.setMessage("Select %d card(s) to tap"); + inp.setCancelAllowed(true); + + FThreads.setInputAndWait(inp); + if (inp.hasCancelled() || inp.getSelected().size() != amount) + return false; + + CostPartWithList cpl = (CostPartWithList)part; + for (Card c : inp.getSelected()) { + cpl.executePayment(sourceAbility, c); + } + cpl.reportPaidCardsTo(sourceAbility); + } + else if (part instanceof CostPartMana ) { if (!((CostPartMana) part).getManaToPay().equals("0")) // non-zero costs require input mayRemovePart = false; From 2308a3951035d51429dd8b2db7e1754cda833201 Mon Sep 17 00:00:00 2001 From: swordshine Date: Sat, 30 Mar 2013 12:39:08 +0000 Subject: [PATCH 008/123] - Fix a typo --- res/cardsfolder/k/koskun_falls.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/cardsfolder/k/koskun_falls.txt b/res/cardsfolder/k/koskun_falls.txt index 0ae87ef0de7..759f892b56c 100644 --- a/res/cardsfolder/k/koskun_falls.txt +++ b/res/cardsfolder/k/koskun_falls.txt @@ -6,6 +6,6 @@ T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | E SVar:TrigSac:AB$ Sacrifice | Cost$ 0 | Defined$ Self | UnlessCost$ tapXType<1/Creature> | UnlessPayer$ You SVar:NeedsToPlayVar:Y GE1 SVar:Y:Count$Valid Creature.YouCtrl -SVar:Picture:http://www.wizards.com/global/images/magic/general/koskun_fallss.jpg +SVar:Picture:http://www.wizards.com/global/images/magic/general/koskun_falls.jpg Oracle:At the beginning of your upkeep, sacrifice Koskun Falls unless you tap an untapped creature you control.\nCreatures can't attack you unless their controller pays {2} for each creature he or she controls that's attacking you. SetInfo:HML Rare \ No newline at end of file From 81d1e491cddbf7783c8770c8ac513af0c7f12596 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 30 Mar 2013 13:01:57 +0000 Subject: [PATCH 009/123] - Added a fluff piece to the changes.txt file. - Added new card names to changes.txt. --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 8bbcd07304c..4b63e80e122 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -47,6 +47,10 @@ The Import Pictures dialog, accessed via the Content Downloaders submenu, has re An importer option was added for including pictures in set-specific card directories that don't map to any currently known card. This handles the case where people have collected complete sets of pics in anticipation of when Forge supports them. +- Flip, Transform and Morph cards - +When you mouse over a flip, transform or Morph (controlled by you) card in battlefield, you may hold SHIFT to see other state of that card at the side panel that displays card picture and details. + + --------- New Cards --------- @@ -58,6 +62,8 @@ Breathstealer's Crypt Moonlight Bargain Auriok Siege Sled Burning-Tree Bloodscale +Soulbright Flamekin +Koskun Falls ---------- From cf3db753c472cf7fbe4d9d39f5a9e7bec62f5354 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sat, 30 Mar 2013 17:32:57 +0000 Subject: [PATCH 010/123] removed global references in 2 files --- .../forge/control/input/InputCleanup.java | 23 ++++++++++--------- .../forge/control/input/InputPayManaBase.java | 3 +-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/forge/control/input/InputCleanup.java b/src/main/java/forge/control/input/InputCleanup.java index 5e8f98fddbb..beee3f058a9 100644 --- a/src/main/java/forge/control/input/InputCleanup.java +++ b/src/main/java/forge/control/input/InputCleanup.java @@ -19,12 +19,10 @@ package forge.control.input; import forge.Card; import forge.FThreads; -import forge.Singletons; import forge.game.GameState; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; -import forge.gui.match.CMatchUI; import forge.view.ButtonUtil; /** @@ -39,6 +37,8 @@ public class InputCleanup extends InputBase { /** Constant serialVersionUID=-4164275418971547948L. */ private static final long serialVersionUID = -4164275418971547948L; private final GameState game; + final Player activePlayer; + final Player turnOwner; /** * TODO: Write javadoc for Constructor. @@ -47,19 +47,20 @@ public class InputCleanup extends InputBase { public InputCleanup(GameState game) { super(); this.game = game; + activePlayer = game.getPhaseHandler().getPriorityPlayer(); + turnOwner = game.getPhaseHandler().getPlayerTurn(); } /** {@inheritDoc} */ @Override public final void showMessage() { - final Player active = game.getPhaseHandler().getPriorityPlayer(); - final Player turnOwner = game.getPhaseHandler().getPlayerTurn(); - final int n = active.getCardsIn(ZoneType.Hand).size(); - final int max = active.getMaxHandSize(); + + final int n = activePlayer.getCardsIn(ZoneType.Hand).size(); + final int max = activePlayer.getMaxHandSize(); // goes to the next phase - if (active.isUnlimitedHandSize() || n <= max || n <= 0 || active != turnOwner) { - active.getController().passPriority(); + if (activePlayer.isUnlimitedHandSize() || n <= max || n <= 0 || activePlayer != turnOwner) { + activePlayer.getController().passPriority(); return; } ButtonUtil.disableAll(); @@ -69,20 +70,20 @@ public class InputCleanup extends InputBase { final StringBuffer sb = new StringBuffer(); sb.append("Cleanup Phase: You can only have a maximum of ").append(max); sb.append(" cards, you currently have ").append(n).append(" cards in your hand - select a card to discard"); - CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString()); + showMessage(sb.toString()); } /** {@inheritDoc} */ @Override public final void selectCard(final Card card) { Zone zone = game.getZoneOf(card); - if (!zone.is(ZoneType.Hand, Singletons.getControl().getPlayer())) + if (!zone.is(ZoneType.Hand, turnOwner)) return; FThreads.invokeInNewThread(new Runnable() { @Override public void run() { - card.getController().discard(card, null); + turnOwner.discard(card, null); } }, true); } diff --git a/src/main/java/forge/control/input/InputPayManaBase.java b/src/main/java/forge/control/input/InputPayManaBase.java index 29d43729c83..424667b1318 100644 --- a/src/main/java/forge/control/input/InputPayManaBase.java +++ b/src/main/java/forge/control/input/InputPayManaBase.java @@ -7,7 +7,6 @@ import forge.Card; import forge.CardUtil; import forge.Constant; import forge.FThreads; -import forge.Singletons; import forge.card.MagicColor; import forge.card.ability.ApiType; import forge.card.mana.ManaCostBeingPaid; @@ -116,7 +115,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I * @return ManaCost the amount of mana remaining to be paid after the mana is activated */ protected void useManaFromPool(String color, ManaCostBeingPaid manaCost) { - ManaPool mp = Singletons.getControl().getPlayer().getManaPool(); + ManaPool mp = whoPays.getManaPool(); // Convert Color to short String String manaStr = "1"; From 750226b60f3b67f3ab74b9d996503b5407dcf18f Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sat, 30 Mar 2013 18:12:34 +0000 Subject: [PATCH 011/123] revert previous change - because game won't start with it --- .../forge/control/input/InputCleanup.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/java/forge/control/input/InputCleanup.java b/src/main/java/forge/control/input/InputCleanup.java index beee3f058a9..5e8f98fddbb 100644 --- a/src/main/java/forge/control/input/InputCleanup.java +++ b/src/main/java/forge/control/input/InputCleanup.java @@ -19,10 +19,12 @@ package forge.control.input; import forge.Card; import forge.FThreads; +import forge.Singletons; import forge.game.GameState; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; +import forge.gui.match.CMatchUI; import forge.view.ButtonUtil; /** @@ -37,8 +39,6 @@ public class InputCleanup extends InputBase { /** Constant serialVersionUID=-4164275418971547948L. */ private static final long serialVersionUID = -4164275418971547948L; private final GameState game; - final Player activePlayer; - final Player turnOwner; /** * TODO: Write javadoc for Constructor. @@ -47,20 +47,19 @@ public class InputCleanup extends InputBase { public InputCleanup(GameState game) { super(); this.game = game; - activePlayer = game.getPhaseHandler().getPriorityPlayer(); - turnOwner = game.getPhaseHandler().getPlayerTurn(); } /** {@inheritDoc} */ @Override public final void showMessage() { + final Player active = game.getPhaseHandler().getPriorityPlayer(); + final Player turnOwner = game.getPhaseHandler().getPlayerTurn(); - - final int n = activePlayer.getCardsIn(ZoneType.Hand).size(); - final int max = activePlayer.getMaxHandSize(); + final int n = active.getCardsIn(ZoneType.Hand).size(); + final int max = active.getMaxHandSize(); // goes to the next phase - if (activePlayer.isUnlimitedHandSize() || n <= max || n <= 0 || activePlayer != turnOwner) { - activePlayer.getController().passPriority(); + if (active.isUnlimitedHandSize() || n <= max || n <= 0 || active != turnOwner) { + active.getController().passPriority(); return; } ButtonUtil.disableAll(); @@ -70,20 +69,20 @@ public class InputCleanup extends InputBase { final StringBuffer sb = new StringBuffer(); sb.append("Cleanup Phase: You can only have a maximum of ").append(max); sb.append(" cards, you currently have ").append(n).append(" cards in your hand - select a card to discard"); - showMessage(sb.toString()); + CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString()); } /** {@inheritDoc} */ @Override public final void selectCard(final Card card) { Zone zone = game.getZoneOf(card); - if (!zone.is(ZoneType.Hand, turnOwner)) + if (!zone.is(ZoneType.Hand, Singletons.getControl().getPlayer())) return; FThreads.invokeInNewThread(new Runnable() { @Override public void run() { - turnOwner.discard(card, null); + card.getController().discard(card, null); } }, true); } From 69dd8af5848dd54ba7e3be17e7e9eb4b7a263a1b Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 31 Mar 2013 10:49:35 +0000 Subject: [PATCH 012/123] Added another class to indicate auto-pass of priority and keep the passPriority call in right thread (that is non-edt) InputBase has player field and that passPriority method, --- .gitattributes | 1 + src/main/java/forge/FThreads.java | 4 + .../java/forge/card/cost/CostPartMana.java | 2 +- .../java/forge/card/cost/CostTapType.java | 76 +++---------------- .../forge/card/cost/InputPayCostBase.java | 9 +++ .../card/spellability/TargetSelection.java | 1 + .../java/forge/control/input/InputAttack.java | 5 +- .../control/input/InputAutoPassPriority.java | 21 +++++ .../java/forge/control/input/InputBase.java | 21 ++++- .../java/forge/control/input/InputBlock.java | 14 ++-- .../forge/control/input/InputCleanup.java | 11 ++- .../forge/control/input/InputControl.java | 36 +++++---- .../java/forge/control/input/InputLockUI.java | 21 ++++- .../forge/control/input/InputMulligan.java | 37 +++++---- .../control/input/InputPassPriority.java | 35 ++++----- .../forge/control/input/InputPayManaBase.java | 17 ++--- .../input/InputPayManaExecuteCommands.java | 6 +- .../input/InputPayManaOfCostPayment.java | 6 +- .../control/input/InputPayManaSimple.java | 14 ++-- .../forge/control/input/InputPayManaX.java | 5 +- .../control/input/InputSelectManyBase.java | 2 + .../control/input/InputSyncronizedBase.java | 4 +- src/main/java/forge/game/ai/AiInputBlock.java | 10 +-- .../java/forge/game/ai/AiInputCommon.java | 1 + .../java/forge/game/phase/PhaseHandler.java | 1 + .../forge/game/player/PlayerController.java | 4 +- .../forge/game/player/PlayerControllerAi.java | 11 ++- .../game/player/PlayerControllerHuman.java | 14 +++- src/main/java/forge/game/zone/MagicStack.java | 2 +- src/main/java/forge/gui/InputProxy.java | 37 ++++----- 30 files changed, 234 insertions(+), 194 deletions(-) create mode 100644 src/main/java/forge/control/input/InputAutoPassPriority.java diff --git a/.gitattributes b/.gitattributes index abca738a842..dc26207456b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13820,6 +13820,7 @@ src/main/java/forge/control/bazaar/ControlStall.java -text src/main/java/forge/control/bazaar/package-info.java svneol=native#text/plain src/main/java/forge/control/input/Input.java -text src/main/java/forge/control/input/InputAttack.java svneol=native#text/plain +src/main/java/forge/control/input/InputAutoPassPriority.java -text src/main/java/forge/control/input/InputBase.java svneol=native#text/plain src/main/java/forge/control/input/InputBlock.java svneol=native#text/plain src/main/java/forge/control/input/InputCleanup.java svneol=native#text/plain diff --git a/src/main/java/forge/FThreads.java b/src/main/java/forge/FThreads.java index 1c0a0aaa86a..8ca661eb834 100644 --- a/src/main/java/forge/FThreads.java +++ b/src/main/java/forge/FThreads.java @@ -123,5 +123,9 @@ public class FThreads { public static void delay(int milliseconds, Runnable inputUpdater) { getScheduledPool().schedule(inputUpdater, milliseconds, TimeUnit.MILLISECONDS); } + + public static String debugGetCurrThreadId() { + return isEDT() ? "EDT" : Long.toString(Thread.currentThread().getId()); + } } diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index 7385fbe8c57..bf9c65aecb4 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -223,7 +223,7 @@ public class CostPartMana extends CostPart { } if (this.getAmountOfX() > 0) { source.setXManaCostPaid(0); - InputPayment inpPayment = new InputPayManaX(game, ability, this.getAmountOfX(), this.canXbe0()); + InputPayment inpPayment = new InputPayManaX(ability, this.getAmountOfX(), this.canXbe0()); FThreads.setInputAndWait(inpPayment); if(!inpPayment.isPaid()) return false; diff --git a/src/main/java/forge/card/cost/CostTapType.java b/src/main/java/forge/card/cost/CostTapType.java index 26969477c28..29024f70249 100644 --- a/src/main/java/forge/card/cost/CostTapType.java +++ b/src/main/java/forge/card/cost/CostTapType.java @@ -23,83 +23,21 @@ import forge.Card; import forge.CardLists; import forge.CardPredicates.Presets; import forge.FThreads; -import forge.Singletons; import forge.card.ability.AbilityUtils; import forge.card.spellability.SpellAbility; -import forge.control.input.InputPayment; +import forge.control.input.InputSelectCards; +import forge.control.input.InputSelectCardsFromList; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; import forge.game.player.Player; -import forge.game.zone.Zone; import forge.game.zone.ZoneType; -import forge.view.ButtonUtil; /** * The Class CostTapType. */ public class CostTapType extends CostPartWithList { - /** - * TODO: Write javadoc for this type. - * - */ - public static final class InputPayCostTapType extends InputPayCostBase { - private final CostTapType tapType; - private final int nCards; - private final List cardList; - private static final long serialVersionUID = 6438988130447851042L; - private int nTapped = 0; - - /** - * TODO: Write javadoc for Constructor. - * @param sa - * @param tapType - * @param nCards - * @param cardList - * @param payment - */ - public InputPayCostTapType(CostTapType tapType, int nCards, List cardList) { - this.tapType = tapType; - this.nCards = nCards; - this.cardList = cardList; - } - - @Override - public void showMessage() { - - final int left = nCards - this.nTapped; - showMessage("Select a " + tapType.getDescription() + " to tap (" + left + " left)"); - ButtonUtil.enableOnlyCancel(); - if (nCards == 0) { - this.done(); - } - } - - - - @Override - public void selectCard(final Card card) { - Zone zone = Singletons.getModel().getGame().getZoneOf(card); - if (zone.is(ZoneType.Battlefield) && cardList.contains(card) && card.isUntapped()) { - // send in List for Typing - tapType.executePayment(null, card); - cardList.remove(card); - - this.nTapped++; - - if (this.nTapped == nCards) { - this.done(); - } else if (cardList.size() == 0) { - // happen - this.cancel(); - } else { - this.showMessage(); - } - } - } - } - private final boolean canTapSource; /** @@ -214,9 +152,15 @@ public class CostTapType extends CostPartWithList { c = AbilityUtils.calculateAmount(source, amount, ability); } } - InputPayment inp = new InputPayCostTapType(this, c, typeList); + + + InputSelectCards inp = new InputSelectCardsFromList(c, c, typeList); + inp.setMessage("Select a " + getDescription() + " to tap (%d left)"); FThreads.setInputAndWait(inp); - return inp.isPaid(); + if ( inp.hasCancelled() ) + return false; + + return executePayment(ability, inp.getSelected()); } /* (non-Javadoc) diff --git a/src/main/java/forge/card/cost/InputPayCostBase.java b/src/main/java/forge/card/cost/InputPayCostBase.java index 00847b3c488..33c65bff7b3 100644 --- a/src/main/java/forge/card/cost/InputPayCostBase.java +++ b/src/main/java/forge/card/cost/InputPayCostBase.java @@ -1,5 +1,6 @@ package forge.card.cost; +import forge.Singletons; import forge.control.input.InputPayment; import forge.control.input.InputSyncronizedBase; @@ -8,6 +9,14 @@ import forge.control.input.InputSyncronizedBase; * */ abstract class InputPayCostBase extends InputSyncronizedBase implements InputPayment { + /** + * TODO: Write javadoc for Constructor. + * @param player + */ + public InputPayCostBase() { + super(Singletons.getControl().getPlayer()); + } + private static final long serialVersionUID = -2967434867139585579L; boolean bPaid = false; diff --git a/src/main/java/forge/card/spellability/TargetSelection.java b/src/main/java/forge/card/spellability/TargetSelection.java index 4f8185c312e..b1952e2b120 100644 --- a/src/main/java/forge/card/spellability/TargetSelection.java +++ b/src/main/java/forge/card/spellability/TargetSelection.java @@ -71,6 +71,7 @@ public class TargetSelection { * @param mandatory */ public InputSelectTargets(TargetSelection select, List choices, ArrayList alreadyTargeted, boolean targeted, Target tgt, SpellAbility sa, boolean mandatory) { + super(sa.getActivatingPlayer()); this.select = select; this.choices = choices; this.alreadyTargeted = alreadyTargeted; diff --git a/src/main/java/forge/control/input/InputAttack.java b/src/main/java/forge/control/input/InputAttack.java index 5a6d05d1631..ae75e889a74 100644 --- a/src/main/java/forge/control/input/InputAttack.java +++ b/src/main/java/forge/control/input/InputAttack.java @@ -47,8 +47,9 @@ public class InputAttack extends InputBase { private static final long serialVersionUID = 7849903731842214245L; private final GameState game; - public InputAttack(GameState game0) { - game = game0; + public InputAttack(Player human) { + super(human); + game = human.getGame(); } diff --git a/src/main/java/forge/control/input/InputAutoPassPriority.java b/src/main/java/forge/control/input/InputAutoPassPriority.java new file mode 100644 index 00000000000..d3244abc907 --- /dev/null +++ b/src/main/java/forge/control/input/InputAutoPassPriority.java @@ -0,0 +1,21 @@ +package forge.control.input; + +import forge.game.player.Player; + +/** + * TODO: Write javadoc for this type. + * + */ +public class InputAutoPassPriority extends InputBase { + private static final long serialVersionUID = -7520803307255234647L; + + public InputAutoPassPriority(Player player) { + super(player); + } + + @Override + public void showMessage() { + passPriority(); + } + +} diff --git a/src/main/java/forge/control/input/InputBase.java b/src/main/java/forge/control/input/InputBase.java index f835e028ab3..d3da28bee70 100644 --- a/src/main/java/forge/control/input/InputBase.java +++ b/src/main/java/forge/control/input/InputBase.java @@ -18,6 +18,7 @@ package forge.control.input; import forge.Card; +import forge.FThreads; import forge.Singletons; import forge.game.player.Player; import forge.gui.match.CMatchUI; @@ -33,7 +34,11 @@ import forge.gui.match.CMatchUI; public abstract class InputBase implements java.io.Serializable, Input { /** Constant serialVersionUID=-6539552513871194081L. */ private static final long serialVersionUID = -6539552513871194081L; - + protected final Player player; + public InputBase(Player player) { + this.player = player; + } + // showMessage() is always the first method called @Override public abstract void showMessage(); @@ -64,4 +69,18 @@ public abstract class InputBase implements java.io.Serializable, Input { } protected void afterStop() { } + + + + protected void passPriority() { + final Runnable pass = new Runnable() { + @Override public void run() { + player.getController().passPriority(); + } + }; + if( FThreads.isEDT() ) + FThreads.invokeInNewThread(pass, true); + else + pass.run(); + } } \ No newline at end of file diff --git a/src/main/java/forge/control/input/InputBlock.java b/src/main/java/forge/control/input/InputBlock.java index e2f172403bf..29ad874a3c0 100644 --- a/src/main/java/forge/control/input/InputBlock.java +++ b/src/main/java/forge/control/input/InputBlock.java @@ -23,7 +23,6 @@ import java.util.List; import forge.Card; import forge.Singletons; -import forge.control.FControl; import forge.game.GameState; import forge.game.phase.CombatUtil; import forge.game.player.Player; @@ -48,14 +47,13 @@ public class InputBlock extends InputBase { private Card currentAttacker = null; private final HashMap> allBlocking = new HashMap>(); - private final Player defender; /** * TODO: Write javadoc for Constructor. * @param priority */ - public InputBlock(Player priority) { - defender = priority; + public InputBlock(Player human) { + super(human); } /** @@ -98,14 +96,14 @@ public class InputBlock extends InputBase { @Override public final void selectButtonOK() { final GameState game = Singletons.getModel().getGame(); - if (CombatUtil.finishedMandatoryBlocks(game.getCombat(), defender)) { + if (CombatUtil.finishedMandatoryBlocks(game.getCombat(), player)) { // Done blocking ButtonUtil.reset(); CombatUtil.orderMultipleCombatants(game.getCombat()); currentAttacker = null; allBlocking.clear(); - FControl.SINGLETON_INSTANCE.getPlayer().getController().passPriority(); + passPriority(); } } @@ -121,8 +119,8 @@ public class InputBlock extends InputBase { } else { Zone zone = Singletons.getModel().getGame().getZoneOf(card); // Make sure this card is valid to even be a blocker - if (this.currentAttacker != null && card.isCreature() && card.getController().equals(defender) - && zone.is(ZoneType.Battlefield, defender)) { + if (this.currentAttacker != null && card.isCreature() && card.getController().equals(player) + && zone.is(ZoneType.Battlefield, player)) { // Create a new blockedBy list if it doesn't exist if (!this.allBlocking.containsKey(card)) { this.allBlocking.put(card, new ArrayList()); diff --git a/src/main/java/forge/control/input/InputCleanup.java b/src/main/java/forge/control/input/InputCleanup.java index 5e8f98fddbb..0f0f21c4ef5 100644 --- a/src/main/java/forge/control/input/InputCleanup.java +++ b/src/main/java/forge/control/input/InputCleanup.java @@ -24,7 +24,6 @@ import forge.game.GameState; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; -import forge.gui.match.CMatchUI; import forge.view.ButtonUtil; /** @@ -44,9 +43,9 @@ public class InputCleanup extends InputBase { * TODO: Write javadoc for Constructor. * @param game */ - public InputCleanup(GameState game) { - super(); - this.game = game; + public InputCleanup(Player player) { + super(player); + this.game = player.getGame(); } /** {@inheritDoc} */ @@ -59,7 +58,7 @@ public class InputCleanup extends InputBase { final int max = active.getMaxHandSize(); // goes to the next phase if (active.isUnlimitedHandSize() || n <= max || n <= 0 || active != turnOwner) { - active.getController().passPriority(); + passPriority(); return; } ButtonUtil.disableAll(); @@ -69,7 +68,7 @@ public class InputCleanup extends InputBase { final StringBuffer sb = new StringBuffer(); sb.append("Cleanup Phase: You can only have a maximum of ").append(max); sb.append(" cards, you currently have ").append(n).append(" cards in your hand - select a card to discard"); - CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString()); + showMessage(sb.toString()); } /** {@inheritDoc} */ diff --git a/src/main/java/forge/control/input/InputControl.java b/src/main/java/forge/control/input/InputControl.java index a2be674d7e9..cba5c1c743e 100644 --- a/src/main/java/forge/control/input/InputControl.java +++ b/src/main/java/forge/control/input/InputControl.java @@ -19,6 +19,7 @@ package forge.control.input; import java.util.Stack; +import forge.Singletons; import forge.game.GameState; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; @@ -115,38 +116,37 @@ public class InputControl extends MyObservable implements java.io.Serializable { */ public final Input getActualInput(GameState game) { if ( !game.hasMulliganned() ) - return new InputMulligan(); + return new InputMulligan(Singletons.getModel().getMatch(), Singletons.getControl().getPlayer()); final PhaseHandler handler = game.getPhaseHandler(); final PhaseType phase = handler.getPhase(); final Player playerTurn = handler.getPlayerTurn(); final Player priority = handler.getPriorityPlayer(); - final MagicStack stack = game.getStack(); + if (priority == null) + throw new RuntimeException("No player has priority!"); + PlayerController pc = priority.getController(); + + - - // TODO this resolving portion needs more work, but fixes Death Cloud - // issues if (!this.inputStack.isEmpty()) { // incoming input to Control return this.inputStack.peek(); } - // If the Phase we're in doesn't allow for Priority, return null to move - // to next phase + + + // If the Phase we're in doesn't allow for Priority, move to next phase if (!handler.isPlayerPriorityAllowed()) { - return null; + return pc.getAutoPassPriorityInput(); } - if (priority == null) - return null; - PlayerController pc = priority.getController(); - // Special Inputs needed for the following phases: + final MagicStack stack = game.getStack(); switch (phase) { case COMBAT_DECLARE_ATTACKERS: stack.freezeStack(); if (playerTurn.isHuman() && !playerTurn.getController().mayAutoPass(phase)) { game.getCombat().initiatePossibleDefenders(playerTurn.getOpponents()); - return new InputAttack(game); + return new InputAttack(playerTurn); } break; @@ -158,8 +158,7 @@ public class InputControl extends MyObservable implements java.io.Serializable { } // noone attacks you - pc.passPriority(); - return null; + return pc.getAutoPassPriorityInput(); case CLEANUP: // discard @@ -176,10 +175,9 @@ public class InputControl extends MyObservable implements java.io.Serializable { // Special phases handled above, everything else is handled simply by // priority - boolean prioritySkip = pc.mayAutoPass(phase) || pc.isUiSetToSkipPhase(playerTurn, phase); - if (game.getStack().isEmpty() && prioritySkip) { - pc.passPriority(); - return null; + boolean maySkipPriority = pc.mayAutoPass(phase) || pc.isUiSetToSkipPhase(playerTurn, phase); + if (game.getStack().isEmpty() && maySkipPriority) { + return pc.getAutoPassPriorityInput(); } else pc.autoPassCancel(); // probably cancel, since something has happened diff --git a/src/main/java/forge/control/input/InputLockUI.java b/src/main/java/forge/control/input/InputLockUI.java index 2fb4c2eba7e..5beace5ed26 100644 --- a/src/main/java/forge/control/input/InputLockUI.java +++ b/src/main/java/forge/control/input/InputLockUI.java @@ -2,16 +2,18 @@ package forge.control.input; import java.util.concurrent.atomic.AtomicInteger; +import forge.Card; import forge.FThreads; +import forge.Singletons; +import forge.game.player.Player; +import forge.gui.match.CMatchUI; import forge.view.ButtonUtil; /** * TODO: Write javadoc for this type. * */ -public class InputLockUI extends InputBase { - private static final long serialVersionUID = 5777143577098597374L; - +public class InputLockUI implements Input { private final AtomicInteger iCall = new AtomicInteger(); public void showMessage() { @@ -47,5 +49,18 @@ public class InputLockUI extends InputBase { showMessage("Waiting for actions..."); } }; + + protected final boolean isActive() { + return Singletons.getModel().getMatch().getInput().getInput() == this; + } + + protected void showMessage(String message) { + CMatchUI.SINGLETON_INSTANCE.showMessage(message); + } + + @Override public void selectCard(Card c) {} + @Override public void selectPlayer(Player player) {} + @Override public void selectButtonOK() {} + @Override public void selectButtonCancel() {} } diff --git a/src/main/java/forge/control/input/InputMulligan.java b/src/main/java/forge/control/input/InputMulligan.java index ed7b64ffab9..be6961b84b8 100644 --- a/src/main/java/forge/control/input/InputMulligan.java +++ b/src/main/java/forge/control/input/InputMulligan.java @@ -24,12 +24,13 @@ import com.google.common.collect.Iterables; import forge.Card; import forge.CardPredicates; -import forge.Singletons; +import forge.FThreads; import forge.card.ability.AbilityFactory; import forge.card.spellability.SpellAbility; import forge.game.GameAction; import forge.game.GameState; import forge.game.GameType; +import forge.game.MatchController; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; import forge.game.player.Player; @@ -52,22 +53,28 @@ import forge.view.ButtonUtil; public class InputMulligan extends InputBase { /** Constant serialVersionUID=-8112954303001155622L. */ private static final long serialVersionUID = -8112954303001155622L; - + + private final MatchController match; + + public InputMulligan(MatchController match0, Player humanPlayer) { + super(humanPlayer); + match = match0; + } + /** {@inheritDoc} */ @Override public final void showMessage() { ButtonUtil.setButtonText("No", "Yes"); ButtonUtil.enableAllFocusOk(); - GameState game = Singletons.getModel().getGame(); + GameState game = match.getCurrentGame(); Player startingPlayer = game.getPhaseHandler().getPlayerTurn(); - Player localPlayer = Singletons.getControl().getPlayer(); StringBuilder sb = new StringBuilder(); sb.append(startingPlayer.getName()).append(" is going first. "); - if (!startingPlayer.equals(localPlayer)) { - sb.append("You are going ").append(game.getOrdinalPosition(localPlayer, startingPlayer)).append(". "); + if (!startingPlayer.equals(player)) { + sb.append("You are going ").append(game.getOrdinalPosition(player, startingPlayer)).append(". "); } sb.append("Do you want to Mulligan?"); @@ -83,10 +90,9 @@ public class InputMulligan extends InputBase { /** {@inheritDoc} */ @Override public final void selectButtonCancel() { - final Player humanPlayer = Singletons.getControl().getPlayer(); - humanPlayer.doMulligan(); + player.doMulligan(); - if (humanPlayer.getCardsIn(ZoneType.Hand).isEmpty()) { + if (player.getCardsIn(ZoneType.Hand).isEmpty()) { this.end(); } else { ButtonUtil.enableAllFocusOk(); @@ -94,9 +100,9 @@ public class InputMulligan extends InputBase { } final void end() { - GameState game = Singletons.getModel().getGame(); // Computer mulligan + final GameState game = match.getCurrentGame(); for (Player p : game.getPlayers()) { if (!(p instanceof AIPlayer)) { continue; @@ -173,17 +179,22 @@ public class InputMulligan extends InputBase { SDisplayUtil.showTab(nextField); game.setMulliganned(true); - Singletons.getModel().getMatch().getInput().clearInput(); + FThreads.invokeInNewThread( new Runnable() { + @Override + public void run() { + match.getInput().clearInput(); + } + }); } @Override public void selectCard(Card c0) { - Zone z0 = Singletons.getModel().getGame().getZoneOf(c0); + Zone z0 = match.getCurrentGame().getZoneOf(c0); if (c0.getName().equals("Serum Powder") && z0.is(ZoneType.Hand)) { if (GuiDialog.confirm(c0, "Use " + c0.getName() + "'s ability?")) { List hand = new ArrayList(c0.getController().getCardsIn(ZoneType.Hand)); for (Card c : hand) { - Singletons.getModel().getGame().getAction().exile(c); + match.getCurrentGame().getAction().exile(c); } c0.getController().drawCards(hand.size()); } diff --git a/src/main/java/forge/control/input/InputPassPriority.java b/src/main/java/forge/control/input/InputPassPriority.java index 4fe8b4bcf64..114893439b0 100644 --- a/src/main/java/forge/control/input/InputPassPriority.java +++ b/src/main/java/forge/control/input/InputPassPriority.java @@ -19,11 +19,9 @@ package forge.control.input; import forge.Card; import forge.FThreads; -import forge.Singletons; import forge.card.spellability.SpellAbility; -import forge.control.FControl; -import forge.game.phase.PhaseType; -import forge.game.player.Player; +import forge.game.phase.PhaseHandler; +import forge.game.player.HumanPlayer; import forge.gui.GuiDisplayUtil; import forge.gui.framework.SDisplayUtil; import forge.gui.match.CMatchUI; @@ -42,26 +40,28 @@ public class InputPassPriority extends InputBase { /** Constant serialVersionUID=-581477682214137181L. */ private static final long serialVersionUID = -581477682214137181L; + /** + * TODO: Write javadoc for Constructor. + * @param player + */ + public InputPassPriority(HumanPlayer human) { + super(human); + } + /** {@inheritDoc} */ @Override public final void showMessage() { GuiDisplayUtil.updateGUI(); ButtonUtil.enableOnlyOk(); - final PhaseType phase = Singletons.getModel().getGame().getPhaseHandler().getPhase(); - final Player player = Singletons.getModel().getGame().getPhaseHandler().getPriorityPlayer(); - - if (player.isComputer()) { - System.err.println(phase + ": Computer in passpriority"); - } - + final PhaseHandler ph = player.getGame().getPhaseHandler(); final StringBuilder sb = new StringBuilder(); - sb.append("Turn : ").append(Singletons.getModel().getGame().getPhaseHandler().getPlayerTurn()).append("\n"); - sb.append("Phase: ").append(phase.Name).append("\n"); + sb.append("Turn : ").append(ph.getPlayerTurn()).append("\n"); + sb.append("Phase: ").append(ph.getPhase().Name).append("\n"); sb.append("Stack: "); - if (Singletons.getModel().getGame().getStack().size() != 0) { - sb.append(Singletons.getModel().getGame().getStack().size()).append(" to Resolve."); + if (player.getGame().getStack().size() != 0) { + sb.append(player.getGame().getStack().size()).append(" to Resolve."); } else { sb.append("Empty"); } @@ -71,16 +71,17 @@ public class InputPassPriority extends InputBase { CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString()); } + /** {@inheritDoc} */ @Override public final void selectButtonOK() { - FControl.SINGLETON_INSTANCE.getPlayer().getController().passPriority(); + ButtonUtil.disableAll(); + passPriority(); } /** {@inheritDoc} */ @Override public final void selectCard(final Card card) { - final Player player = Singletons.getControl().getPlayer(); final SpellAbility ab = player.getController().getAbilityToPlay(player.getGame().getAbilitesOfCard(card, player)); if ( null != ab) { Runnable execAbility = new Runnable() { diff --git a/src/main/java/forge/control/input/InputPayManaBase.java b/src/main/java/forge/control/input/InputPayManaBase.java index 424667b1318..4438fb45ecd 100644 --- a/src/main/java/forge/control/input/InputPayManaBase.java +++ b/src/main/java/forge/control/input/InputPayManaBase.java @@ -30,16 +30,15 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I protected int phyLifeToLose = 0; - protected final Player whoPays; protected final GameState game; protected ManaCostBeingPaid manaCost; protected final SpellAbility saPaidFor; boolean bPaid = false; - protected InputPayManaBase(final GameState game, SpellAbility saToPayFor) { - this.game = game; - this.whoPays = saToPayFor.getActivatingPlayer(); + protected InputPayManaBase(SpellAbility saToPayFor) { + super(saToPayFor.getActivatingPlayer()); + this.game = player.getGame(); this.saPaidFor = saToPayFor; } @@ -115,7 +114,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I * @return ManaCost the amount of mana remaining to be paid after the mana is activated */ protected void useManaFromPool(String color, ManaCostBeingPaid manaCost) { - ManaPool mp = whoPays.getManaPool(); + ManaPool mp = player.getManaPool(); // Convert Color to short String String manaStr = "1"; @@ -144,7 +143,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I */ protected void activateManaAbility(final Card card, ManaCostBeingPaid manaCost) { // make sure computer's lands aren't selected - if (card.getController() != whoPays) { + if (card.getController() != player) { return; } @@ -168,7 +167,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I // you can't remove unneeded abilities inside a for(am:abilities) loop :( for (SpellAbility ma : card.getManaAbility()) { - ma.setActivatingPlayer(whoPays); + ma.setActivatingPlayer(player); AbilityManaPart m = null; SpellAbility tail = ma; while(m == null && tail != null) @@ -303,11 +302,11 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I public void onManaAbilityPlayed(final SpellAbility saPaymentSrc) { if ( saPaymentSrc != null) // null comes when they've paid from pool - this.manaCost = whoPays.getManaPool().payManaFromAbility(saPaidFor, manaCost, saPaymentSrc); + this.manaCost = player.getManaPool().payManaFromAbility(saPaidFor, manaCost, saPaymentSrc); onManaAbilityPaid(); if ( saPaymentSrc != null ) - whoPays.getZone(ZoneType.Battlefield).updateObservers(); + player.getZone(ZoneType.Battlefield).updateObservers(); } protected final void checkIfAlredyPaid() { diff --git a/src/main/java/forge/control/input/InputPayManaExecuteCommands.java b/src/main/java/forge/control/input/InputPayManaExecuteCommands.java index 0214bc8177a..5d0ee10ac24 100644 --- a/src/main/java/forge/control/input/InputPayManaExecuteCommands.java +++ b/src/main/java/forge/control/input/InputPayManaExecuteCommands.java @@ -62,7 +62,7 @@ public class InputPayManaExecuteCommands extends InputPayManaBase implements Inp * a {@link forge.Command} object. */ public InputPayManaExecuteCommands(final Player p, final String prompt, final ManaCost manaCost2) { - super(p.getGame(), new SpellAbility(null) { + super(new SpellAbility(null) { @Override public void resolve() {} @@ -90,8 +90,8 @@ public class InputPayManaExecuteCommands extends InputPayManaBase implements Inp } @Override - public void selectPlayer(final Player player) { - if (player == whoPays) { + public void selectPlayer(final Player selectedPlayer) { + if (player == selectedPlayer) { if (player.canPayLife(this.phyLifeToLose + 2) && manaCost.payPhyrexian()) { this.phyLifeToLose += 2; } diff --git a/src/main/java/forge/control/input/InputPayManaOfCostPayment.java b/src/main/java/forge/control/input/InputPayManaOfCostPayment.java index b241ebf074a..8812c1f3cf6 100644 --- a/src/main/java/forge/control/input/InputPayManaOfCostPayment.java +++ b/src/main/java/forge/control/input/InputPayManaOfCostPayment.java @@ -12,7 +12,7 @@ import forge.view.ButtonUtil; public class InputPayManaOfCostPayment extends InputPayManaBase { public InputPayManaOfCostPayment(final GameState game, CostPartMana costMana, SpellAbility spellAbility, int toAdd) { - super(game, spellAbility); + super(spellAbility); manaCost = new ManaCostBeingPaid(costMana.getManaToPay()); manaCost.increaseColorlessMana(toAdd); } @@ -21,8 +21,8 @@ public class InputPayManaOfCostPayment extends InputPayManaBase { private int phyLifeToLose = 0; @Override - public void selectPlayer(final Player player) { - if (player == whoPays) { + public void selectPlayer(final Player selectedPlayer) { + if (player == selectedPlayer) { if (player.canPayLife(this.phyLifeToLose + 2) && manaCost.payPhyrexian()) { this.phyLifeToLose += 2; } diff --git a/src/main/java/forge/control/input/InputPayManaSimple.java b/src/main/java/forge/control/input/InputPayManaSimple.java index 712f0ebfe4d..3f502812d7f 100644 --- a/src/main/java/forge/control/input/InputPayManaSimple.java +++ b/src/main/java/forge/control/input/InputPayManaSimple.java @@ -37,7 +37,7 @@ public class InputPayManaSimple extends InputPayManaBase { private final String originalManaCost; public InputPayManaSimple(final GameState game, final SpellAbility sa, final ManaCostBeingPaid manaCostToPay) { - super(game, sa); + super(sa); this.originalManaCost = manaCostToPay.toString(); // Change this.originalCard = sa.getSourceCard(); @@ -68,9 +68,9 @@ public class InputPayManaSimple extends InputPayManaBase { /** {@inheritDoc} */ @Override - public final void selectPlayer(final Player player) { + public final void selectPlayer(final Player selectedPlayer) { - if (player == whoPays) { + if (player == selectedPlayer) { if (player.canPayLife(this.phyLifeToLose + 2) && manaCost.payPhyrexian()) { this.phyLifeToLose += 2; } @@ -88,10 +88,10 @@ public class InputPayManaSimple extends InputPayManaBase { @Override protected void done() { if (this.phyLifeToLose > 0) { - whoPays.payLife(this.phyLifeToLose, this.originalCard); + player.payLife(this.phyLifeToLose, this.originalCard); } if (!this.saPaidFor.getSourceCard().isCopiedSpell()) { - whoPays.getManaPool().clearManaPaid(this.saPaidFor, false); + player.getManaPool().clearManaPaid(this.saPaidFor, false); this.resetManaCost(); if (this.saPaidFor.isSpell()) { @@ -110,8 +110,8 @@ public class InputPayManaSimple extends InputPayManaBase { handleConvokedCards(true); this.resetManaCost(); - whoPays.getManaPool().refundManaPaid(this.saPaidFor, true); - whoPays.getZone(ZoneType.Battlefield).updateObservers(); // DO + player.getManaPool().refundManaPaid(this.saPaidFor, true); + player.getZone(ZoneType.Battlefield).updateObservers(); // DO this.stop(); } diff --git a/src/main/java/forge/control/input/InputPayManaX.java b/src/main/java/forge/control/input/InputPayManaX.java index 75d1a3bc544..fc77d6fd52b 100644 --- a/src/main/java/forge/control/input/InputPayManaX.java +++ b/src/main/java/forge/control/input/InputPayManaX.java @@ -3,7 +3,6 @@ package forge.control.input; import forge.Card; import forge.card.mana.ManaCostBeingPaid; import forge.card.spellability.SpellAbility; -import forge.game.GameState; import forge.view.ButtonUtil; public class InputPayManaX extends InputPayManaBase { @@ -15,9 +14,9 @@ public class InputPayManaX extends InputPayManaBase { private final boolean xCanBe0; - public InputPayManaX(final GameState game, final SpellAbility sa0, final int amountX, final boolean xCanBe0) + public InputPayManaX(final SpellAbility sa0, final int amountX, final boolean xCanBe0) { - super(game, sa0); + super(sa0); xPaid = 0; colorX = saPaidFor.hasParam("XColor") ? saPaidFor.getParam("XColor") : ""; diff --git a/src/main/java/forge/control/input/InputSelectManyBase.java b/src/main/java/forge/control/input/InputSelectManyBase.java index 84c58f000c7..06e04d39727 100644 --- a/src/main/java/forge/control/input/InputSelectManyBase.java +++ b/src/main/java/forge/control/input/InputSelectManyBase.java @@ -5,6 +5,7 @@ import java.util.List; import forge.Card; import forge.GameEntity; +import forge.Singletons; import forge.view.ButtonUtil; public abstract class InputSelectManyBase extends InputSyncronizedBase implements InputSelectMany { @@ -23,6 +24,7 @@ public abstract class InputSelectManyBase extends InputSyn protected InputSelectManyBase(int min, int max) { + super(Singletons.getControl().getPlayer()); selected = new ArrayList(); if (min > max) { throw new IllegalArgumentException("Min must not be greater than Max"); diff --git a/src/main/java/forge/control/input/InputSyncronizedBase.java b/src/main/java/forge/control/input/InputSyncronizedBase.java index 6b975f19ec7..7d3f1928c51 100644 --- a/src/main/java/forge/control/input/InputSyncronizedBase.java +++ b/src/main/java/forge/control/input/InputSyncronizedBase.java @@ -4,13 +4,15 @@ import java.util.concurrent.CountDownLatch; import forge.FThreads; import forge.error.BugReporter; +import forge.game.player.Player; public abstract class InputSyncronizedBase extends InputBase implements InputSynchronized { private static final long serialVersionUID = 8756177361251703052L; private final CountDownLatch cdlDone; - public InputSyncronizedBase() { + public InputSyncronizedBase(Player player) { + super(player); cdlDone = new CountDownLatch(1); } diff --git a/src/main/java/forge/game/ai/AiInputBlock.java b/src/main/java/forge/game/ai/AiInputBlock.java index 341818aa4a8..55873b3bfe7 100644 --- a/src/main/java/forge/game/ai/AiInputBlock.java +++ b/src/main/java/forge/game/ai/AiInputBlock.java @@ -14,21 +14,17 @@ import forge.game.player.Player; * */ public class AiInputBlock extends InputBase { - private final GameState game; /** * TODO: Write javadoc for Constructor. * @param game * @param player */ - public AiInputBlock(GameState game, Player player) { - super(); - this.game = game; - this.player = player; + public AiInputBlock(Player human) { + super(human); + this.game = human.getGame(); } - private final Player player; - private static final long serialVersionUID = -2253562658069995572L; @Override diff --git a/src/main/java/forge/game/ai/AiInputCommon.java b/src/main/java/forge/game/ai/AiInputCommon.java index 277e1f77e90..ca32bbbbf65 100644 --- a/src/main/java/forge/game/ai/AiInputCommon.java +++ b/src/main/java/forge/game/ai/AiInputCommon.java @@ -43,6 +43,7 @@ public class AiInputCommon extends InputBase implements AiInput { * a {@link forge.game.player.Computer} object. */ public AiInputCommon(final AiController iComputer) { + super(iComputer.getPlayer()); this.computer = iComputer; } diff --git a/src/main/java/forge/game/phase/PhaseHandler.java b/src/main/java/forge/game/phase/PhaseHandler.java index e6561528d27..4b2ca17178f 100644 --- a/src/main/java/forge/game/phase/PhaseHandler.java +++ b/src/main/java/forge/game/phase/PhaseHandler.java @@ -709,6 +709,7 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable { *

*/ public final void passPriority() { + FThreads.checkEDT("PhaseHandler.passPriority", false); // stop game if it's outcome is clear if (game.isGameOver()) { return; diff --git a/src/main/java/forge/game/player/PlayerController.java b/src/main/java/forge/game/player/PlayerController.java index 728d000af4a..5c34f535821 100644 --- a/src/main/java/forge/game/player/PlayerController.java +++ b/src/main/java/forge/game/player/PlayerController.java @@ -29,13 +29,15 @@ public abstract class PlayerController { private PhaseType autoPassUntil = null; - public PlayerController(GameState game0) { game = game0; + } + public abstract Input getDefaultInput(); public abstract Input getBlockInput(); public abstract Input getCleanupInput(); + public abstract Input getAutoPassPriorityInput(); /** diff --git a/src/main/java/forge/game/player/PlayerControllerAi.java b/src/main/java/forge/game/player/PlayerControllerAi.java index 242ffed7898..f405302cec1 100644 --- a/src/main/java/forge/game/player/PlayerControllerAi.java +++ b/src/main/java/forge/game/player/PlayerControllerAi.java @@ -13,6 +13,7 @@ import forge.GameEntity; import forge.card.spellability.Spell; import forge.card.spellability.SpellAbility; import forge.control.input.Input; +import forge.control.input.InputAutoPassPriority; import forge.deck.Deck; import forge.game.GameState; import forge.game.GameType; @@ -36,6 +37,7 @@ public class PlayerControllerAi extends PlayerController { private Input defaultInput; private Input blockInput; private Input cleanupInput; + private Input autoPassPriorityInput; private final AiController brains; private final AIPlayer player; @@ -52,9 +54,9 @@ public class PlayerControllerAi extends PlayerController { brains = new AiController(p, game); defaultInput = new AiInputCommon(brains); - blockInput = new AiInputBlock(game, getPlayer()); + blockInput = new AiInputBlock(getPlayer()); cleanupInput = getDefaultInput(); - + autoPassPriorityInput = new InputAutoPassPriority(getPlayer()); } /** @@ -75,6 +77,11 @@ public class PlayerControllerAi extends PlayerController { return blockInput; } + @Override + public Input getAutoPassPriorityInput() { + return autoPassPriorityInput; + } + /** * @return the cleanupInput */ diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index ba777e5b0db..d6a5825d9bb 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -16,6 +16,7 @@ import forge.FThreads; import forge.GameEntity; import forge.card.spellability.SpellAbility; import forge.control.input.Input; +import forge.control.input.InputAutoPassPriority; import forge.control.input.InputBlock; import forge.control.input.InputCleanup; import forge.control.input.InputPassPriority; @@ -45,8 +46,9 @@ public class PlayerControllerHuman extends PlayerController { private final Input defaultInput; private final Input blockInput; private final Input cleanupInput; + private final Input autoPassPriorityInput; private final HumanPlayer player; - + public final Input getDefaultInput() { return defaultInput; } @@ -55,9 +57,10 @@ public class PlayerControllerHuman extends PlayerController { super(game0); player = p; - defaultInput = new InputPassPriority(); + defaultInput = new InputPassPriority(player); blockInput = new InputBlock(getPlayer()); - cleanupInput = new InputCleanup(game); + cleanupInput = new InputCleanup(getPlayer()); + autoPassPriorityInput = new InputAutoPassPriority(getPlayer()); } @Override @@ -83,6 +86,11 @@ public class PlayerControllerHuman extends PlayerController { return blockInput; } + @Override + public Input getAutoPassPriorityInput() { + return autoPassPriorityInput; + } + /** * @return the cleanupInput */ diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index 037c94a5d4c..c22e38500dc 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -391,7 +391,7 @@ public class MagicStack extends MyObservable { final int xCost = sa.getXManaCost(); Player player = sp.getSourceCard().getController(); if (player.isHuman()) { - InputSynchronized inp = new InputPayManaX(game, sa, xCost, true); + InputSynchronized inp = new InputPayManaX(sa, xCost, true); FThreads.setInputAndWait(inp); MagicStack.this.push(sa); } else { diff --git a/src/main/java/forge/gui/InputProxy.java b/src/main/java/forge/gui/InputProxy.java index 17b2e107906..590e31f3c1c 100644 --- a/src/main/java/forge/gui/InputProxy.java +++ b/src/main/java/forge/gui/InputProxy.java @@ -26,7 +26,6 @@ import forge.FThreads; import forge.control.input.Input; import forge.game.GameState; import forge.game.MatchController; -import forge.game.ai.AiInput; import forge.game.phase.PhaseHandler; import forge.game.player.Player; @@ -50,35 +49,37 @@ public class InputProxy implements Observer { @Override public final synchronized void update(final Observable observable, final Object obj) { - this.input.set(null); final GameState game = match.getCurrentGame(); final PhaseHandler ph = game.getPhaseHandler(); + //System.out.printf("%s > InputProxy.update() =>%n", FThreads.debugGetCurrThreadId()); - //System.out.print((FThreads.isEDT() ? "EDT > " : "TRD > ") + ph.debugPrintState()); if ( match.getInput().isEmpty() && ph.hasPhaseEffects()) { - //System.out.println(" handle begin phase"); - FThreads.invokeInNewThread(new Runnable() { - @Override public void run() { + Runnable rPhase = new Runnable() { + @Override + public void run() { + //System.out.printf("\t%s > handle begin phase during %s%n", FThreads.debugGetCurrThreadId(), ph.debugPrintState()); ph.handleBeginPhase(); - update(observable, obj); - } - }, true); + update(null, null); + } + }; + FThreads.invokeInNewThread(rPhase); return; } final Input nextInput = match.getInput().getActualInput(game); - //System.out.printf(" input is %s \t stack = %s%n", nextInput == null ? "null" : nextInput.getClass().getSimpleName(), match.getInput().printInputStack()); - - if (nextInput != null) { - this.input.set(nextInput); - Runnable showMessage = new Runnable() { @Override public void run() { nextInput.showMessage(); } }; + //System.out.printf("\tinput is %s during %s, \tstack = %s%n", nextInput == null ? "null" : nextInput.getClass().getSimpleName(), ph.debugPrintState(), match.getInput().printInputStack()); + + this.input.set(nextInput); + Runnable showMessage = new Runnable() { + @Override public void run() { + //System.out.printf("%s > showMessage @ %s during %s%n", FThreads.debugGetCurrThreadId(), nextInput.getClass().getSimpleName(), ph.debugPrintState()); + nextInput.showMessage(); + } + }; // if( nextInput instanceof AiInput ) // FThreads.invokeInNewThread(showMessage, true); // else - FThreads.invokeInEDT(showMessage); - } else if (!ph.isPlayerPriorityAllowed()) { - ph.getPriorityPlayer().getController().passPriority(); - } + FThreads.invokeInEDT(showMessage); } /** *

From 336084ec33b148671d25570e07618bd11aef946c Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 31 Mar 2013 13:16:05 +0000 Subject: [PATCH 013/123] button will not flash --- src/main/java/forge/control/input/InputPassPriority.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/forge/control/input/InputPassPriority.java b/src/main/java/forge/control/input/InputPassPriority.java index 114893439b0..2523deecadd 100644 --- a/src/main/java/forge/control/input/InputPassPriority.java +++ b/src/main/java/forge/control/input/InputPassPriority.java @@ -75,7 +75,6 @@ public class InputPassPriority extends InputBase { /** {@inheritDoc} */ @Override public final void selectButtonOK() { - ButtonUtil.disableAll(); passPriority(); } From 0c62ffa4cec112af333094eabbe86ce3fd0090b8 Mon Sep 17 00:00:00 2001 From: Sol Date: Sun, 31 Mar 2013 15:28:29 +0000 Subject: [PATCH 014/123] - AI will now accept PeekAndReveal triggers for cards like Wolf-Skull Shaman (since this is important for the Spock quest deck) --- src/main/java/forge/card/ability/ai/PeekAndRevealAi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/forge/card/ability/ai/PeekAndRevealAi.java b/src/main/java/forge/card/ability/ai/PeekAndRevealAi.java index b7c5c273750..d4175d770b5 100644 --- a/src/main/java/forge/card/ability/ai/PeekAndRevealAi.java +++ b/src/main/java/forge/card/ability/ai/PeekAndRevealAi.java @@ -17,7 +17,7 @@ public class PeekAndRevealAi extends SpellAbilityAi { protected boolean canPlayAI(AIPlayer aiPlayer, SpellAbility sa) { // So far this only appears on Triggers, but will expand // once things get converted from Dig + NoMove - return false; + return true; } } From a221a820d0783345abd7df4053a65e75b3c6c9d4 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 31 Mar 2013 18:22:24 +0000 Subject: [PATCH 015/123] Discard will accept both min and max parameters --- .../card/ability/effects/DiscardEffect.java | 8 +++--- src/main/java/forge/game/ai/AiController.java | 27 ++++++++++++------- src/main/java/forge/game/ai/ComputerUtil.java | 8 +++--- .../forge/game/player/PlayerController.java | 2 +- .../forge/game/player/PlayerControllerAi.java | 6 ++--- .../game/player/PlayerControllerHuman.java | 7 +++-- 6 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/main/java/forge/card/ability/effects/DiscardEffect.java b/src/main/java/forge/card/ability/effects/DiscardEffect.java index 2f1e216e781..b9453d61a93 100644 --- a/src/main/java/forge/card/ability/effects/DiscardEffect.java +++ b/src/main/java/forge/card/ability/effects/DiscardEffect.java @@ -201,12 +201,12 @@ public class DiscardEffect extends RevealEffectBase { } if (mode.startsWith("Reveal") && p != chooser) - chooser.getController().reveal("Revealed " + p + " hand", dPHand, ZoneType.Hand, p); + chooser.getGame().getAction().reveal(dPHand, p); - int minDiscardAmount = sa.hasParam("AnyNumber") || sa.hasParam("Optional") ? 0 : numCards; - int max = Math.min(validCards.size(), minDiscardAmount); + int min = sa.hasParam("AnyNumber") || sa.hasParam("Optional") ? 0 : numCards; + int max = Math.min(validCards.size(), numCards); - List toBeDiscarded = validCards.isEmpty() ? CardLists.emptyList : chooser.getController().chooseCardsToDiscardFrom(p, sa, validCards, max); + List toBeDiscarded = validCards.isEmpty() ? CardLists.emptyList : chooser.getController().chooseCardsToDiscardFrom(p, sa, validCards, min, max); if (mode.startsWith("Reveal") ) { p.getController().reveal(chooser + " has chosen", toBeDiscarded, ZoneType.Hand, p); diff --git a/src/main/java/forge/game/ai/AiController.java b/src/main/java/forge/game/ai/AiController.java index 4b36151e2dd..31715f92cde 100644 --- a/src/main/java/forge/game/ai/AiController.java +++ b/src/main/java/forge/game/ai/AiController.java @@ -467,7 +467,14 @@ public class AiController { // This is for playing spells regularly (no Cascade/Ripple etc.) private boolean canPlayAndPayFor(final SpellAbility sa) { - return sa.canPlay() && sa.canPlayAI() && ComputerUtilCost.canPayCost(sa, player); + boolean canPlay = sa.canPlay(); + if (!canPlay) + return false; + //System.out.printf("Ai thinks of %s @ %s >>> ", sa, sa.getActivatingPlayer().getGame().getPhaseHandler().debugPrintState()); + boolean aiWouldPlay = sa.canPlayAI(); + boolean canPay = ComputerUtilCost.canPayCost(sa, player); + //System.out.printf("wouldPlay: %s, canPay: %s%n", aiWouldPlay, canPay); + return aiWouldPlay && canPay; } // not sure "playing biggest spell" matters? @@ -534,12 +541,12 @@ public class AiController { if ((uTypes != null) && (sa != null)) { hand = CardLists.getValidCards(hand, uTypes, sa.getActivatingPlayer(), sa.getSourceCard()); } - return getCardsToDiscard(numDiscard, hand, sa); + return getCardsToDiscard(numDiscard, numDiscard, hand, sa); } - public List getCardsToDiscard(final int numDiscard, final List validCards, final SpellAbility sa) { + public List getCardsToDiscard(final int min, final int max, final List validCards, final SpellAbility sa) { - if (validCards.size() < numDiscard) { + if (validCards.size() < min) { return null; } @@ -551,7 +558,7 @@ public class AiController { } // look for good discards - while (count < numDiscard) { + while (count < min) { Card prefCard = null; if (sa != null && sa.getActivatingPlayer() != null && sa.getActivatingPlayer().isOpponentOf(player)) { for (Card c : validCards) { @@ -574,7 +581,7 @@ public class AiController { } } - final int discardsLeft = numDiscard - count; + final int discardsLeft = min - count; // choose rest for (int i = 0; i < discardsLeft; i++) { @@ -778,9 +785,11 @@ public class AiController { if (!player.isUnlimitedHandSize()) { int max = Math.min(player.getZone(ZoneType.Hand).size(), size - player.getMaxHandSize()); - final List toDiscard = player.getAi().getCardsToDiscard(max, (String[])null, null); - for (int i = 0; i < toDiscard.size(); i++) { - player.discard(toDiscard.get(i), null); + if( max > 0) { + final List toDiscard = player.getAi().getCardsToDiscard(max, (String[])null, null); + for (int i = 0; i < toDiscard.size(); i++) { + player.discard(toDiscard.get(i), null); + } } game.getStack().chooseOrderOfSimultaneousStackEntryAll(); } diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index 5b218e2d817..995e551a74b 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -1217,7 +1217,7 @@ public class ComputerUtil { * @param min * @return */ - public static List getCardsToDiscardFromOpponent(AIPlayer chooser, Player discarder, SpellAbility sa, List validCards, int min) { + public static List getCardsToDiscardFromOpponent(AIPlayer chooser, Player discarder, SpellAbility sa, List validCards, int min, int max) { List goodChoices = CardLists.filter(validCards, new Predicate() { @Override public boolean apply(final Card c) { @@ -1258,11 +1258,11 @@ public class ComputerUtil { * @param min * @return */ - public static List getCardsToDiscardFromFriend(AIPlayer aiChooser, Player p, SpellAbility sa, List validCards, int min) { + public static List getCardsToDiscardFromFriend(AIPlayer aiChooser, Player p, SpellAbility sa, List validCards, int min, int max) { if (p instanceof AIPlayer) { // ask that ai player what he would like to discard - return ((AIPlayer) p).getAi().getCardsToDiscard(min, validCards, sa); + return ((AIPlayer) p).getAi().getCardsToDiscard(min, max, validCards, sa); } // no special options for human or remote friends - return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min); + return getCardsToDiscardFromOpponent(aiChooser, p, sa, validCards, min, max); } } diff --git a/src/main/java/forge/game/player/PlayerController.java b/src/main/java/forge/game/player/PlayerController.java index 5c34f535821..34b605fb24b 100644 --- a/src/main/java/forge/game/player/PlayerController.java +++ b/src/main/java/forge/game/player/PlayerController.java @@ -113,7 +113,7 @@ public abstract class PlayerController { public abstract boolean willPutCardOnTop(Card c); /** p = target player, validCards - possible discards, min cards to discard */ - public abstract List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List validCards, int min); + public abstract List chooseCardsToDiscardFrom(Player playerDiscard, SpellAbility sa, List validCards, int min, int max); public abstract Card chooseCardToDredge(List dredgers); public abstract void playMiracle(SpellAbility miracle, Card card); diff --git a/src/main/java/forge/game/player/PlayerControllerAi.java b/src/main/java/forge/game/player/PlayerControllerAi.java index f405302cec1..dc1c246c42d 100644 --- a/src/main/java/forge/game/player/PlayerControllerAi.java +++ b/src/main/java/forge/game/player/PlayerControllerAi.java @@ -220,12 +220,12 @@ public class PlayerControllerAi extends PlayerController { } @Override - public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List validCards, int min) { + public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List validCards, int min, int max) { boolean isTargetFriendly = !p.isOpponentOf(getPlayer()); return isTargetFriendly - ? ComputerUtil.getCardsToDiscardFromFriend(player, p, sa, validCards, min) - : ComputerUtil.getCardsToDiscardFromOpponent(player, p, sa, validCards, min); + ? ComputerUtil.getCardsToDiscardFromFriend(player, p, sa, validCards, min, max) + : ComputerUtil.getCardsToDiscardFromOpponent(player, p, sa, validCards, min, max); } @Override diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index d6a5825d9bb..32fc4b4da69 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -324,14 +324,13 @@ public class PlayerControllerHuman extends PlayerController { } @Override - public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List valid, int minDiscard) { + public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List valid, int min, int max) { if ( p != getPlayer() ) { - int cntToKeepInHand = minDiscard == 0 ? -1 : valid.size() - minDiscard; + int cntToKeepInHand = min == 0 ? -1 : valid.size() - min; return GuiChoose.order("Choose cards to Discard", "Discarded", cntToKeepInHand, valid, null, null); } - int max = minDiscard == 0 ? Integer.MAX_VALUE : minDiscard; - InputSelectCards inp = new InputSelectCardsFromList(minDiscard, max, valid); + InputSelectCards inp = new InputSelectCardsFromList(min, max, valid); inp.setMessage("Discard %d cards"); FThreads.setInputAndWait(inp); return inp.getSelected(); From db1131143bb6c9160b1a16246baf2b350ef32e5b Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 31 Mar 2013 18:22:54 +0000 Subject: [PATCH 016/123] a more unified code to pay costs during resolve --- src/main/java/forge/game/GameActionUtil.java | 108 ++++++------------- 1 file changed, 34 insertions(+), 74 deletions(-) diff --git a/src/main/java/forge/game/GameActionUtil.java b/src/main/java/forge/game/GameActionUtil.java index 3131c1cde59..0af29330ff6 100644 --- a/src/main/java/forge/game/GameActionUtil.java +++ b/src/main/java/forge/game/GameActionUtil.java @@ -402,6 +402,7 @@ public final class GameActionUtil { * @param sourceAbility TODO */ public static boolean payCostDuringAbilityResolve(final Player p, final SpellAbility ability, final Cost cost, SpellAbility sourceAbility, final GameState game) { + // Only human player pays this way final Card source = ability.getSourceCard(); final List parts = cost.getCostParts(); ArrayList remainingParts = new ArrayList(cost.getCostParts()); @@ -502,98 +503,39 @@ public final class GameActionUtil { } else if (part instanceof CostSacrifice) { - CostSacrifice sacCost = (CostSacrifice) part; - String valid = sacCost.getType(); - int amount = Integer.parseInt(sacCost.getAmount()); - List list = AbilityUtils.filterListByType(p.getCardsIn(ZoneType.Battlefield), valid, ability); - - if (list.size() < amount) { - // unable to pay (not enough cards) - return false; - } - - GuiUtils.clearPanelSelections(); - GuiUtils.setPanelSelection(source); - - List toSac = p.getController().choosePermanentsToSacrifice(list, amount, ability, false, true); - if ( toSac.size() != amount ) - return false; - - CostPartWithList cpl = (CostPartWithList)part; - for(Card c : toSac) { - cpl.executePayment(sourceAbility, c); - } - cpl.reportPaidCardsTo(sourceAbility); + int amount = Integer.parseInt(((CostSacrifice)part).getAmount()); + List list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), p, source); + boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "sacrifice"); + if(!hasPaid) return false; } else if (part instanceof CostReturn) { - List choiceList = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), p, source); + List list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), p, source); int amount = getAmountFromPartX(part, source, sourceAbility); - - InputSelectCards inp = new InputSelectCardsFromList(amount, amount, choiceList); - inp.setMessage("Select %d card(s) to return to hand"); - inp.setCancelAllowed(true); - - FThreads.setInputAndWait(inp); - if( inp.hasCancelled() || inp.getSelected().size() != amount) - return false; - - CostPartWithList cpl = (CostPartWithList)part; - for(Card c : inp.getSelected()) { - cpl.executePayment(sourceAbility, c); - } - cpl.reportPaidCardsTo(sourceAbility); + boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "return to hand"); + if(!hasPaid) return false; } else if (part instanceof CostDiscard) { - List choiceList = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType().split(";"), p, source); + List list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), part.getType().split(";"), p, source); int amount = getAmountFromPartX(part, source, sourceAbility); - - InputSelectCards inp = new InputSelectCardsFromList(amount, amount, choiceList); - inp.setMessage("Select %d card(s) to discard"); - inp.setCancelAllowed(true); - - FThreads.setInputAndWait(inp); - if( inp.hasCancelled() || inp.getSelected().size() != amount) - return false; - - CostPartWithList cpl = (CostPartWithList)part; - for(Card c : inp.getSelected()) { - cpl.executePayment(sourceAbility, c); - } - cpl.reportPaidCardsTo(sourceAbility); + boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "discard"); + if(!hasPaid) return false; } else if (part instanceof CostTapType) { - List choiceList = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), p, source); - choiceList = CardLists.filter(choiceList, Presets.UNTAPPED); + List list = CardLists.getValidCards(p.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), p, source); + list = CardLists.filter(list, Presets.UNTAPPED); int amount = getAmountFromPartX(part, source, sourceAbility); - - if (choiceList.size() < amount) { - // unable to pay (not enough cards) - return false; - } - - InputSelectCards inp = new InputSelectCardsFromList(amount, amount, choiceList); - inp.setMessage("Select %d card(s) to tap"); - inp.setCancelAllowed(true); - - FThreads.setInputAndWait(inp); - if (inp.hasCancelled() || inp.getSelected().size() != amount) - return false; - - CostPartWithList cpl = (CostPartWithList)part; - for (Card c : inp.getSelected()) { - cpl.executePayment(sourceAbility, c); - } - cpl.reportPaidCardsTo(sourceAbility); + boolean hasPaid = payCostPart(sourceAbility, (CostPartWithList)part, amount, list, "tap"); + if(!hasPaid) return false; } else if (part instanceof CostPartMana ) { if (!((CostPartMana) part).getManaToPay().equals("0")) // non-zero costs require input mayRemovePart = false; } else - throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - An unhandled type of cost has ocurred: " + part.getClass()); + throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - An unhandled type of cost was met: " + part.getClass()); if( mayRemovePart ) remainingParts.remove(part); @@ -616,6 +558,24 @@ public final class GameActionUtil { return toSet.isPaid(); } + private static boolean payCostPart(SpellAbility sourceAbility, CostPartWithList cpl, int amount, List list, String actionName) { + if (list.size() < amount) return false; // unable to pay (not enough cards) + + InputSelectCards inp = new InputSelectCardsFromList(amount, amount, list); + inp.setMessage("Select %d " + cpl.getDescriptiveType() + " card(s) to " + actionName); + inp.setCancelAllowed(true); + + FThreads.setInputAndWait(inp); + if( inp.hasCancelled() || inp.getSelected().size() != amount) + return false; + + for(Card c : inp.getSelected()) { + cpl.executePayment(sourceAbility, c); + } + cpl.reportPaidCardsTo(sourceAbility); + return true; + } + // not restricted to combat damage, not restricted to dealing damage to // creatures/players /** From 6696b2c0d19ce838790698e3d5c93eabe61ee0e9 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 31 Mar 2013 18:33:44 +0000 Subject: [PATCH 017/123] Fallow wurm and thundering wurm use unless cost --- res/cardsfolder/f/fallow_wurm.txt | 7 ++----- res/cardsfolder/t/thundering_wurm.txt | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/res/cardsfolder/f/fallow_wurm.txt b/res/cardsfolder/f/fallow_wurm.txt index d483aa87ceb..87b6046c263 100644 --- a/res/cardsfolder/f/fallow_wurm.txt +++ b/res/cardsfolder/f/fallow_wurm.txt @@ -2,11 +2,8 @@ Name:Fallow Wurm ManaCost:2 G Types:Creature Wurm PT:4/4 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield, sacrifice it unless you discard a land card. -SVar:TrigDiscard:AB$ Discard | Cost$ 0 | NumCards$ 1 | DiscardValid$ Land | Mode$ TgtChoose | Optional$ True | RememberDiscarded$ True | SubAbility$ DBSacSelf -SVar:DBSacSelf:DB$ Sacrifice | Cost$ 0 | Defined$ Self | SubAbility$ DBCleanup | ConditionCheckSVar$ X | ConditionSVarCompare$ LT1 | References$ X -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Remembered$Amount +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBSacSelf | TriggerDescription$ When CARDNAME enters the battlefield, sacrifice it unless you discard a land card. +SVar:DBSacSelf:DB$ Sacrifice | Defined$ Self | UnlessCost$ Discard<1/Land> | UnlessPayer$ You SVar:NeedsToPlayVar:Y GE1 SVar:Y:Count$TypeInYourHand.Land SVar:Picture:http://www.wizards.com/global/images/magic/general/fallow_wurm.jpg diff --git a/res/cardsfolder/t/thundering_wurm.txt b/res/cardsfolder/t/thundering_wurm.txt index 1349e4ae6e3..2cb47aad7dd 100644 --- a/res/cardsfolder/t/thundering_wurm.txt +++ b/res/cardsfolder/t/thundering_wurm.txt @@ -2,11 +2,8 @@ Name:Thundering Wurm ManaCost:2 G Types:Creature Wurm PT:4/4 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME enters the battlefield, sacrifice it unless you discard a land card. -SVar:TrigDiscard:AB$ Discard | Cost$ 0 | NumCards$ 1 | DiscardValid$ Land | Mode$ TgtChoose | Optional$ True | RememberDiscarded$ True | SubAbility$ DBSacSelf -SVar:DBSacSelf:DB$ Sacrifice | Cost$ 0 | Defined$ Self | SubAbility$ DBCleanup | ConditionCheckSVar$ X | References$ X | ConditionSVarCompare$ LT1 -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Remembered$Amount +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBSacSelf | TriggerDescription$ When CARDNAME enters the battlefield, sacrifice it unless you discard a land card. +SVar:DBSacSelf:DB$ Sacrifice | Defined$ Self | UnlessCost$ Discard<1/Land> | UnlessPayer$ You SVar:NeedsToPlayVar:Y GE1 SVar:Y:Count$TypeInYourHand.Land SVar:Picture:http://www.wizards.com/global/images/magic/general/thundering_wurm.jpg From 1aaf6c5a08fc6ad5493830becf0e2faa9f26c594 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 31 Mar 2013 18:50:30 +0000 Subject: [PATCH 018/123] Cost*: removed 4 parameters needless from canPlay() method also removed excessive parameters in their callers --- src/main/java/forge/card/ability/AbilityUtils.java | 2 +- src/main/java/forge/card/cost/CostDamage.java | 2 +- src/main/java/forge/card/cost/CostDiscard.java | 5 ++++- src/main/java/forge/card/cost/CostExile.java | 6 +++++- src/main/java/forge/card/cost/CostGainLife.java | 12 ++++++------ src/main/java/forge/card/cost/CostMill.java | 4 +++- src/main/java/forge/card/cost/CostPart.java | 3 +-- src/main/java/forge/card/cost/CostPartMana.java | 3 +-- src/main/java/forge/card/cost/CostPartWithList.java | 2 ++ src/main/java/forge/card/cost/CostPayLife.java | 3 ++- src/main/java/forge/card/cost/CostPayment.java | 4 ++-- src/main/java/forge/card/cost/CostPutCounter.java | 4 +++- src/main/java/forge/card/cost/CostRemoveCounter.java | 4 +++- src/main/java/forge/card/cost/CostReturn.java | 4 +++- src/main/java/forge/card/cost/CostReveal.java | 5 ++++- src/main/java/forge/card/cost/CostSacrifice.java | 5 ++++- src/main/java/forge/card/cost/CostTap.java | 4 ++-- src/main/java/forge/card/cost/CostTapType.java | 7 +++++-- src/main/java/forge/card/cost/CostUnattach.java | 5 ++++- src/main/java/forge/card/cost/CostUntap.java | 4 ++-- src/main/java/forge/card/cost/CostUntapType.java | 6 ++++-- .../forge/card/spellability/AbilityActivated.java | 8 ++++---- src/main/java/forge/card/spellability/Spell.java | 2 +- src/main/java/forge/game/GameAction.java | 4 ++-- src/main/java/forge/game/GameActionUtil.java | 7 ++++--- src/main/java/forge/game/ai/ComputerUtil.java | 2 +- src/main/java/forge/game/ai/ComputerUtilCost.java | 10 +++------- src/main/java/forge/game/ai/ComputerUtilMana.java | 10 +++------- src/main/java/forge/game/phase/CombatUtil.java | 2 +- src/main/java/forge/game/phase/Upkeep.java | 5 +++-- 30 files changed, 84 insertions(+), 60 deletions(-) diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index f00452e5e84..55d532622e1 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -1102,7 +1102,7 @@ public class AbilityUtils { } } else { // if it's paid by the AI already the human can pay, but it won't change anything - paid |= GameActionUtil.payCostDuringAbilityResolve(payer, ability, cost, sa, game); + paid |= GameActionUtil.payCostDuringAbilityResolve(ability, cost, sa, game); } } diff --git a/src/main/java/forge/card/cost/CostDamage.java b/src/main/java/forge/card/cost/CostDamage.java index f26324df966..1a82c960d12 100644 --- a/src/main/java/forge/card/cost/CostDamage.java +++ b/src/main/java/forge/card/cost/CostDamage.java @@ -54,7 +54,7 @@ public class CostDamage extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { return true; } diff --git a/src/main/java/forge/card/cost/CostDiscard.java b/src/main/java/forge/card/cost/CostDiscard.java index 5d07264d3ac..4c800273418 100644 --- a/src/main/java/forge/card/cost/CostDiscard.java +++ b/src/main/java/forge/card/cost/CostDiscard.java @@ -103,7 +103,10 @@ public class CostDiscard extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + List handList = new ArrayList(activator.getCardsIn(ZoneType.Hand)); String type = this.getType(); final Integer amount = this.convertAmount(); diff --git a/src/main/java/forge/card/cost/CostExile.java b/src/main/java/forge/card/cost/CostExile.java index 4ca0c1d47b4..5694002aee9 100644 --- a/src/main/java/forge/card/cost/CostExile.java +++ b/src/main/java/forge/card/cost/CostExile.java @@ -461,7 +461,11 @@ public class CostExile extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + final GameState game = activator.getGame(); + List typeList = new ArrayList(); if (this.getType().equals("All")) { return true; // this will always work diff --git a/src/main/java/forge/card/cost/CostGainLife.java b/src/main/java/forge/card/cost/CostGainLife.java index dd4350e7c75..dbbd9958ac8 100644 --- a/src/main/java/forge/card/cost/CostGainLife.java +++ b/src/main/java/forge/card/cost/CostGainLife.java @@ -57,10 +57,10 @@ public class CostGainLife extends CostPart { return sb.toString(); } - private List getPotentialTargets(final GameState game, final Player payer, final Card source) + private List getPotentialTargets(final Player payer, final Card source) { List res = new ArrayList(); - for(Player p : game.getPlayers()) + for(Player p : payer.getGame().getPlayers()) { if(p.isValid(getType(), payer, source)) res.add(p); @@ -76,12 +76,12 @@ public class CostGainLife extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { final Integer amount = this.convertAmount(); if ( amount == null ) return false; int cntAbleToGainLife = 0; - List possibleTargets = getPotentialTargets(game, activator, source); + List possibleTargets = getPotentialTargets(ability.getActivatingPlayer(), ability.getSourceCard()); for (final Player opp : possibleTargets) { if (opp.canGainLife()) { @@ -101,7 +101,7 @@ public class CostGainLife extends CostPart { @Override public final void payAI(final PaymentDecision decision, final AIPlayer ai, SpellAbility ability, Card source) { int playersLeft = cntPlayers; - for (final Player opp : getPotentialTargets(ai.getGame(), ai, source)) { + for (final Player opp : getPotentialTargets(ai, source)) { if (opp.canGainLife() && playersLeft > 0) { playersLeft--; opp.gainLife(decision.c, null); @@ -135,7 +135,7 @@ public class CostGainLife extends CostPart { } final List oppsThatCanGainLife = new ArrayList(); - for (final Player opp : getPotentialTargets(game, activator, source)) { + for (final Player opp : getPotentialTargets(activator, source)) { if (opp.canGainLife()) { oppsThatCanGainLife.add(opp); } diff --git a/src/main/java/forge/card/cost/CostMill.java b/src/main/java/forge/card/cost/CostMill.java index 68b55dd44f9..40eefd99084 100644 --- a/src/main/java/forge/card/cost/CostMill.java +++ b/src/main/java/forge/card/cost/CostMill.java @@ -62,7 +62,9 @@ public class CostMill extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); final PlayerZone zone = activator.getZone(ZoneType.Library); Integer i = this.convertAmount(); diff --git a/src/main/java/forge/card/cost/CostPart.java b/src/main/java/forge/card/cost/CostPart.java index 9e4bada7f7a..faf216c8231 100644 --- a/src/main/java/forge/card/cost/CostPart.java +++ b/src/main/java/forge/card/cost/CostPart.java @@ -22,7 +22,6 @@ import forge.Card; import forge.card.spellability.SpellAbility; import forge.game.GameState; import forge.game.player.AIPlayer; -import forge.game.player.Player; /** * The Class CostPart. @@ -147,7 +146,7 @@ public abstract class CostPart { * @param game * @return true, if successful */ - public abstract boolean canPay(SpellAbility ability, Card source, Player activator, Cost cost, GameState game); + public abstract boolean canPay(SpellAbility ability); /** * Decide ai payment. diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index bf9c65aecb4..98aef47f04e 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -29,7 +29,6 @@ import forge.control.input.InputPayment; import forge.game.GameState; import forge.game.ai.ComputerUtilMana; import forge.game.player.AIPlayer; -import forge.game.player.Player; /** * The mana component of any spell or ability cost @@ -181,7 +180,7 @@ public class CostPartMana extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { // For now, this will always return true. But this should probably be // checked at some point return true; diff --git a/src/main/java/forge/card/cost/CostPartWithList.java b/src/main/java/forge/card/cost/CostPartWithList.java index 866590345cc..5326433eddd 100644 --- a/src/main/java/forge/card/cost/CostPartWithList.java +++ b/src/main/java/forge/card/cost/CostPartWithList.java @@ -69,6 +69,8 @@ public abstract class CostPartWithList extends CostPart { sa.addCostToHashList(CardUtil.getLKICopy(card), paymentMethod); } } + + // public abstract List getValidCards(); /** * Instantiates a new cost part with list. diff --git a/src/main/java/forge/card/cost/CostPayLife.java b/src/main/java/forge/card/cost/CostPayLife.java index 6f1c38036c7..5afe507d3f0 100644 --- a/src/main/java/forge/card/cost/CostPayLife.java +++ b/src/main/java/forge/card/cost/CostPayLife.java @@ -72,8 +72,9 @@ public class CostPayLife extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { final Integer amount = this.convertAmount(); + Player activator = ability.getActivatingPlayer(); if ((amount != null) && !activator.canPayLife(amount)) { return false; } diff --git a/src/main/java/forge/card/cost/CostPayment.java b/src/main/java/forge/card/cost/CostPayment.java index 1c08e5d61ac..d48047ff435 100644 --- a/src/main/java/forge/card/cost/CostPayment.java +++ b/src/main/java/forge/card/cost/CostPayment.java @@ -77,7 +77,7 @@ public class CostPayment { * a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ - public static boolean canPayAdditionalCosts(final GameState game, final Cost cost, final SpellAbility ability) { + public static boolean canPayAdditionalCosts(final Cost cost, final SpellAbility ability) { if (cost == null) { return true; } @@ -90,7 +90,7 @@ public class CostPayment { } for (final CostPart part : cost.getCostParts()) { - if (!part.canPay(ability, card, activator, cost, game)) { + if (!part.canPay(ability)) { return false; } } diff --git a/src/main/java/forge/card/cost/CostPutCounter.java b/src/main/java/forge/card/cost/CostPutCounter.java index a20dbc02452..3432222a25f 100644 --- a/src/main/java/forge/card/cost/CostPutCounter.java +++ b/src/main/java/forge/card/cost/CostPutCounter.java @@ -189,7 +189,9 @@ public class CostPutCounter extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); if (this.payCostFromSource()) { if (source.hasKeyword("CARDNAME can't have counters placed on it.")) { return false; diff --git a/src/main/java/forge/card/cost/CostRemoveCounter.java b/src/main/java/forge/card/cost/CostRemoveCounter.java index 592e53dd1d9..780796c4969 100644 --- a/src/main/java/forge/card/cost/CostRemoveCounter.java +++ b/src/main/java/forge/card/cost/CostRemoveCounter.java @@ -297,8 +297,10 @@ public class CostRemoveCounter extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { final CounterType cntrs = this.getCounter(); + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); final Integer amount = this.convertAmount(); if (this.payCostFromSource()) { diff --git a/src/main/java/forge/card/cost/CostReturn.java b/src/main/java/forge/card/cost/CostReturn.java index 0aea94af567..701072a7e52 100644 --- a/src/main/java/forge/card/cost/CostReturn.java +++ b/src/main/java/forge/card/cost/CostReturn.java @@ -93,7 +93,9 @@ public class CostReturn extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); if (!this.payCostFromSource()) { boolean needsAnnoucement = ability.hasParam("Announce") && this.getType().contains(ability.getParam("Announce")); diff --git a/src/main/java/forge/card/cost/CostReveal.java b/src/main/java/forge/card/cost/CostReveal.java index 870a6516ea3..47c64509965 100644 --- a/src/main/java/forge/card/cost/CostReveal.java +++ b/src/main/java/forge/card/cost/CostReveal.java @@ -66,7 +66,10 @@ public class CostReveal extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + List handList = new ArrayList(activator.getCardsIn(ZoneType.Hand)); final String type = this.getType(); final Integer amount = this.convertAmount(); diff --git a/src/main/java/forge/card/cost/CostSacrifice.java b/src/main/java/forge/card/cost/CostSacrifice.java index 6994e4c7aca..14f1de620a1 100644 --- a/src/main/java/forge/card/cost/CostSacrifice.java +++ b/src/main/java/forge/card/cost/CostSacrifice.java @@ -150,7 +150,10 @@ public class CostSacrifice extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + // You can always sac all if (!this.payCostFromSource()) { // If the sacrificed type is dependant on an annoucement, can't necesarily rule out the CanPlay call diff --git a/src/main/java/forge/card/cost/CostTap.java b/src/main/java/forge/card/cost/CostTap.java index 9081fba4086..b3d743647e8 100644 --- a/src/main/java/forge/card/cost/CostTap.java +++ b/src/main/java/forge/card/cost/CostTap.java @@ -21,7 +21,6 @@ import forge.Card; import forge.card.spellability.SpellAbility; import forge.game.GameState; import forge.game.player.AIPlayer; -import forge.game.player.Player; /** * The Class CostTap. @@ -70,7 +69,8 @@ public class CostTap extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Card source = ability.getSourceCard(); return source.isUntapped() && (!source.isSick() || source.hasKeyword("CARDNAME may activate abilities as though it has haste.")); } diff --git a/src/main/java/forge/card/cost/CostTapType.java b/src/main/java/forge/card/cost/CostTapType.java index 29024f70249..32e7e8f8253 100644 --- a/src/main/java/forge/card/cost/CostTapType.java +++ b/src/main/java/forge/card/cost/CostTapType.java @@ -110,12 +110,15 @@ public class CostTapType extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + List typeList = new ArrayList(activator.getCardsIn(ZoneType.Battlefield)); typeList = CardLists.getValidCards(typeList, this.getType().split(";"), activator, source); - if (cost.hasTapCost()) { + if (!canTapSource) { typeList.remove(source); } typeList = CardLists.filter(typeList, Presets.UNTAPPED); diff --git a/src/main/java/forge/card/cost/CostUnattach.java b/src/main/java/forge/card/cost/CostUnattach.java index 5e9e6fe0f49..8e2777861db 100644 --- a/src/main/java/forge/card/cost/CostUnattach.java +++ b/src/main/java/forge/card/cost/CostUnattach.java @@ -65,7 +65,10 @@ public class CostUnattach extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + final String type = this.getType(); if (type.equals("CARDNAME")) { if (source.isEquipping()) { diff --git a/src/main/java/forge/card/cost/CostUntap.java b/src/main/java/forge/card/cost/CostUntap.java index 2d8eda4bebb..c37e56f39e5 100644 --- a/src/main/java/forge/card/cost/CostUntap.java +++ b/src/main/java/forge/card/cost/CostUntap.java @@ -21,7 +21,6 @@ import forge.Card; import forge.card.spellability.SpellAbility; import forge.game.GameState; import forge.game.player.AIPlayer; -import forge.game.player.Player; /** * The Class CostUntap. @@ -69,7 +68,8 @@ public class CostUntap extends CostPart { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { + public final boolean canPay(final SpellAbility ability) { + final Card source = ability.getSourceCard(); return source.isTapped() && (!source.isSick() || source.hasKeyword("CARDNAME may activate abilities as though it has haste.")); } diff --git a/src/main/java/forge/card/cost/CostUntapType.java b/src/main/java/forge/card/cost/CostUntapType.java index 550f3282a16..5d0f4bb440d 100644 --- a/src/main/java/forge/card/cost/CostUntapType.java +++ b/src/main/java/forge/card/cost/CostUntapType.java @@ -113,8 +113,10 @@ public class CostUntapType extends CostPartWithList { * forge.Card, forge.Player, forge.card.cost.Cost) */ @Override - public final boolean canPay(final SpellAbility ability, final Card source, final Player activator, final Cost cost, final GameState game) { - List typeList = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); + public final boolean canPay(final SpellAbility ability) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + List typeList = activator.getGame().getCardsIn(ZoneType.Battlefield); typeList = CardLists.getValidCards(typeList, this.getType().split(";"), activator, source); diff --git a/src/main/java/forge/card/spellability/AbilityActivated.java b/src/main/java/forge/card/spellability/AbilityActivated.java index c3ef6e83912..c9c15d21a4a 100644 --- a/src/main/java/forge/card/spellability/AbilityActivated.java +++ b/src/main/java/forge/card/spellability/AbilityActivated.java @@ -82,7 +82,7 @@ public abstract class AbilityActivated extends SpellAbility implements java.io.S /** {@inheritDoc} */ @Override public boolean canPlay() { - final GameState game = Singletons.getModel().getGame(); + final GameState game = getActivatingPlayer().getGame(); if (game.getStack().isSplitSecondOnStack() && !this.isManaAbility()) { return false; } @@ -90,7 +90,7 @@ public abstract class AbilityActivated extends SpellAbility implements java.io.S final Card c = this.getSourceCard(); // CantBeActivated static abilities - for (final Card ca : Singletons.getModel().getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + for (final Card ca : game.getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { final ArrayList staticAbilities = ca.getStaticAbilities(); for (final StaticAbility stAb : staticAbilities) { if (stAb.applyAbility("CantBeActivated", c, this)) { @@ -104,7 +104,7 @@ public abstract class AbilityActivated extends SpellAbility implements java.io.S } if (this.isCycling() - && Singletons.getModel().getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCycling)) { + && game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noCycling)) { return false; } @@ -112,7 +112,7 @@ public abstract class AbilityActivated extends SpellAbility implements java.io.S return false; } - return CostPayment.canPayAdditionalCosts(game, this.getPayCosts(), this); + return CostPayment.canPayAdditionalCosts(this.getPayCosts(), this); } /* (non-Javadoc) diff --git a/src/main/java/forge/card/spellability/Spell.java b/src/main/java/forge/card/spellability/Spell.java index 2a4fbc7a990..f11a88ac418 100644 --- a/src/main/java/forge/card/spellability/Spell.java +++ b/src/main/java/forge/card/spellability/Spell.java @@ -114,7 +114,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable } if (this.getPayCosts() != null) { - if (!CostPayment.canPayAdditionalCosts(game, this.getPayCosts(), this)) { + if (!CostPayment.canPayAdditionalCosts(this.getPayCosts(), this)) { return false; } } diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index 91510ac8824..49371a05d0c 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -508,7 +508,7 @@ public class GameAction { return null; } }; - + abRecover.setActivatingPlayer(recoverable.getController()); final StringBuilder sb = new StringBuilder(); sb.append("Recover ").append(recoverable).append("\n"); @@ -518,7 +518,7 @@ public class GameAction { Player p = recoverable.getController(); if (p.isHuman()) { - if ( GameActionUtil.payCostDuringAbilityResolve(p, abRecover, abRecover.getPayCosts(), null, game) ) + if ( GameActionUtil.payCostDuringAbilityResolve(abRecover, abRecover.getPayCosts(), null, game) ) moveToHand(recoverable); else exile(recoverable); diff --git a/src/main/java/forge/game/GameActionUtil.java b/src/main/java/forge/game/GameActionUtil.java index 0af29330ff6..00916038587 100644 --- a/src/main/java/forge/game/GameActionUtil.java +++ b/src/main/java/forge/game/GameActionUtil.java @@ -70,7 +70,6 @@ import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.GuiDialog; -import forge.gui.GuiUtils; import forge.sound.SoundEffectType; @@ -401,8 +400,10 @@ public final class GameActionUtil { * a {@link forge.Command} object. * @param sourceAbility TODO */ - public static boolean payCostDuringAbilityResolve(final Player p, final SpellAbility ability, final Cost cost, SpellAbility sourceAbility, final GameState game) { + public static boolean payCostDuringAbilityResolve(final SpellAbility ability, final Cost cost, SpellAbility sourceAbility, final GameState game) { + // Only human player pays this way + final Player p = ability.getActivatingPlayer(); final Card source = ability.getSourceCard(); final List parts = cost.getCostParts(); ArrayList remainingParts = new ArrayList(cost.getCostParts()); @@ -464,7 +465,7 @@ public final class GameActionUtil { int amount = getAmountFromPartX(part, source, sourceAbility); String plural = amount > 1 ? "s" : ""; - if (!part.canPay(sourceAbility, source, p, cost, game)) + if (!part.canPay(sourceAbility)) return false; if ( false == GuiDialog.confirm(source, "Do you want to remove " + amount + " " + counterType.getName() + " counter" + plural + " from " + source + "?")) diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index 995e551a74b..b0dd3b6b420 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -254,7 +254,7 @@ public class ComputerUtil { final SpellAbility newSA = sa.copyWithNoManaCost(); newSA.setActivatingPlayer(ai); - if (!ComputerUtilCost.canPayAdditionalCosts(newSA, ai, game)) { + if (!ComputerUtilCost.canPayAdditionalCosts(newSA, ai)) { return; } diff --git a/src/main/java/forge/game/ai/ComputerUtilCost.java b/src/main/java/forge/game/ai/ComputerUtilCost.java index 5e0b6d9cf00..077bdae73ec 100644 --- a/src/main/java/forge/game/ai/ComputerUtilCost.java +++ b/src/main/java/forge/game/ai/ComputerUtilCost.java @@ -8,7 +8,6 @@ import org.apache.commons.lang3.StringUtils; import forge.Card; import forge.CardLists; import forge.CounterType; -import forge.Singletons; import forge.card.ability.AbilityUtils; import forge.card.cost.Cost; import forge.card.cost.CostDamage; @@ -21,7 +20,6 @@ import forge.card.cost.CostRemoveCounter; import forge.card.cost.CostSacrifice; import forge.card.spellability.Spell; import forge.card.spellability.SpellAbility; -import forge.game.GameState; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.util.MyRandom; @@ -338,8 +336,6 @@ public class ComputerUtilCost { * @return a boolean. */ public static boolean canPayCost(final SpellAbility sa, final Player player) { - - final GameState game = Singletons.getModel().getGame(); // Check for stuff like Nether Void int extraManaNeeded = 0; if (sa instanceof Spell) { @@ -361,7 +357,7 @@ public class ComputerUtilCost { return false; } - return ComputerUtilCost.canPayAdditionalCosts(sa, player, game); + return ComputerUtilCost.canPayAdditionalCosts(sa, player); } // canPayCost() /** @@ -375,7 +371,7 @@ public class ComputerUtilCost { * a {@link forge.game.player.Player} object. * @return a boolean. */ - public static boolean canPayAdditionalCosts(final SpellAbility sa, final Player player, final GameState game) { + public static boolean canPayAdditionalCosts(final SpellAbility sa, final Player player) { if (sa.getActivatingPlayer() == null) { final StringBuilder sb = new StringBuilder(); sb.append(sa.getSourceCard()); @@ -383,7 +379,7 @@ public class ComputerUtilCost { System.out.println(sb.toString()); sa.setActivatingPlayer(player); } - return CostPayment.canPayAdditionalCosts(game, sa.getPayCosts(), sa); + return CostPayment.canPayAdditionalCosts(sa.getPayCosts(), sa); } } diff --git a/src/main/java/forge/game/ai/ComputerUtilMana.java b/src/main/java/forge/game/ai/ComputerUtilMana.java index b85b2e725b2..123a7807f15 100644 --- a/src/main/java/forge/game/ai/ComputerUtilMana.java +++ b/src/main/java/forge/game/ai/ComputerUtilMana.java @@ -27,7 +27,6 @@ import forge.card.spellability.AbilityManaPart; import forge.card.spellability.AbilitySub; import forge.card.spellability.SpellAbility; import forge.game.GameActionUtil; -import forge.game.GameState; import forge.game.player.AIPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -58,8 +57,6 @@ public class ComputerUtilMana { */ public static boolean payManaCost(final SpellAbility sa, final Player ai, final boolean test, final int extraMana, boolean checkPlayable) { ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, test, extraMana); - - final GameState game = Singletons.getModel().getGame(); final ManaPool manapool = ai.getManaPool(); cost = manapool.payManaFromPool(sa, cost); @@ -109,7 +106,7 @@ public class ComputerUtilMana { ma.setActivatingPlayer(ai); // if the AI can't pay the additional costs skip the mana ability if (ma.getPayCosts() != null && checkPlayable) { - if (!ComputerUtilCost.canPayAdditionalCosts(ma, ai, game)) { + if (!ComputerUtilCost.canPayAdditionalCosts(ma, ai)) { continue; } } else if (sourceCard.isTapped() && checkPlayable) { @@ -186,7 +183,7 @@ public class ComputerUtilMana { // Pay additional costs if (ma.getPayCosts() != null) { final CostPayment pay = new CostPayment(ma.getPayCosts(), ma); - if (!pay.payComputerCosts((AIPlayer)ai, game)) { + if (!pay.payComputerCosts((AIPlayer)ai, ai.getGame())) { continue; } } else { @@ -458,7 +455,6 @@ public class ComputerUtilMana { //This method is currently used by AI to estimate human's available mana private static List getAvailableMana(final Player ai, final boolean checkPlayable) { - final GameState game = Singletons.getModel().getGame(); final List list = ai.getCardsIn(ZoneType.Battlefield); list.addAll(ai.getCardsIn(ZoneType.Hand)); final List manaSources = CardLists.filter(list, new Predicate() { @@ -523,7 +519,7 @@ public class ComputerUtilMana { // ability m.setActivatingPlayer(ai); if (cost != null) { - if (!ComputerUtilCost.canPayAdditionalCosts(m, ai, game)) { + if (!ComputerUtilCost.canPayAdditionalCosts(m, ai)) { continue; } } diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index d78e99e53df..9d7c0ae95b2 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -1152,7 +1152,7 @@ public class CombatUtil { ability.setActivatingPlayer(c.getController()); if (c.getController().isHuman()) { - if ( GameActionUtil.payCostDuringAbilityResolve(c.getController(), ability, attackCost, null, game) ) { + if ( GameActionUtil.payCostDuringAbilityResolve(ability, attackCost, null, game) ) { if (!crd.hasKeyword("Vigilance")) { crd.tap(); } } else { game.getCombat().removeFromCombat(crd); diff --git a/src/main/java/forge/game/phase/Upkeep.java b/src/main/java/forge/game/phase/Upkeep.java index 4f0bab96f3c..aae2ac69e82 100644 --- a/src/main/java/forge/game/phase/Upkeep.java +++ b/src/main/java/forge/game/phase/Upkeep.java @@ -171,6 +171,7 @@ public class Upkeep extends Phase { final Card c = list.get(i); if (c.hasStartOfKeyword("(Echo unpaid)")) { final Ability blankAbility = Upkeep.BlankAbility(c, c.getEchoCost()); + blankAbility.setActivatingPlayer(c.getController()); final StringBuilder sb = new StringBuilder(); sb.append("Echo for ").append(c).append("\n"); @@ -182,7 +183,7 @@ public class Upkeep extends Phase { Player controller = c.getController(); if (controller.isHuman()) { Cost cost = new Cost(c, c.getEchoCost().trim(), true); - if ( !GameActionUtil.payCostDuringAbilityResolve(controller, blankAbility, cost, null, game) ) + if ( !GameActionUtil.payCostDuringAbilityResolve(blankAbility, cost, null, game) ) game.getAction().sacrifice(c, null);; } else { // computer @@ -324,7 +325,7 @@ public class Upkeep extends Phase { @Override public void resolve() { if (controller.isHuman()) { - if ( !GameActionUtil.payCostDuringAbilityResolve(controller, blankAbility, blankAbility.getPayCosts(), this, game)) + if ( !GameActionUtil.payCostDuringAbilityResolve(blankAbility, blankAbility.getPayCosts(), this, game)) game.getAction().sacrifice(c, null); } else { // computer if (ComputerUtilCost.shouldPayCost(controller, c, upkeepCost) && ComputerUtilCost.canPayCost(blankAbility, controller)) { From 05f3ef372dda52b5c000a48b727b7861340b251a Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 31 Mar 2013 21:31:46 +0000 Subject: [PATCH 019/123] fixed UI for announce. Announced cost for cards with XX like entreat the angels are paid correctly --- .../java/forge/card/cost/CostPartMana.java | 16 +++++++++++----- .../card/spellability/AbilityActivated.java | 1 - .../SpellAbilityRequirements.java | 14 +++++--------- .../card/spellability/TargetSelection.java | 19 +++++++++---------- src/main/java/forge/game/player/Player.java | 2 +- .../forge/game/player/PlayerController.java | 2 +- .../forge/game/player/PlayerControllerAi.java | 4 ++-- .../game/player/PlayerControllerHuman.java | 11 +++++------ 8 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index 98aef47f04e..30314241d91 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -221,11 +221,17 @@ public class CostPartMana extends CostPart { return false; } if (this.getAmountOfX() > 0) { - source.setXManaCostPaid(0); - InputPayment inpPayment = new InputPayManaX(ability, this.getAmountOfX(), this.canXbe0()); - FThreads.setInputAndWait(inpPayment); - if(!inpPayment.isPaid()) - return false; + if( !"X".equals(ability.getParam("Announce")) ) { + source.setXManaCostPaid(0); + InputPayment inpPayment = new InputPayManaX(ability, this.getAmountOfX(), this.canXbe0()); + FThreads.setInputAndWait(inpPayment); + if(!inpPayment.isPaid()) + return false; + } else { + String xVar = ability.getSVar("X"); + String xVal = xVar.split("\\$")[1]; + source.setXManaCostPaid(Integer.parseInt(xVal)); + } } return true; diff --git a/src/main/java/forge/card/spellability/AbilityActivated.java b/src/main/java/forge/card/spellability/AbilityActivated.java index c9c15d21a4a..b196661574d 100644 --- a/src/main/java/forge/card/spellability/AbilityActivated.java +++ b/src/main/java/forge/card/spellability/AbilityActivated.java @@ -20,7 +20,6 @@ package forge.card.spellability; import java.util.ArrayList; import forge.Card; -import forge.Singletons; import forge.card.cost.Cost; import forge.card.cost.CostPayment; import forge.card.staticability.StaticAbility; diff --git a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java index 3513267975a..731d81a1f8a 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java +++ b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java @@ -165,15 +165,11 @@ public class SpellAbilityRequirements { String announce = ability.getParam("Announce"); if (announce != null) { for(String aVar : announce.split(",")) { - String value = ability.getActivatingPlayer().getController().announceRequirements(ability, aVar); - if (value == null || !StringUtils.isNumeric(value)) { - return false; - } else if (ability.getPayCosts().getCostMana() != null && !ability.getPayCosts().getCostMana().canXbe0() - && Integer.parseInt(value) == 0) { - return false; - } - ability.setSVar(aVar, "Number$" + value); - ability.getSourceCard().setSVar(aVar, "Number$" + value); + Integer value = ability.getActivatingPlayer().getController().announceRequirements(ability, aVar, ability.getPayCosts().getCostMana().canXbe0()); + if ( null == value ) + return false; + ability.setSVar(aVar, "Number$" + value); + ability.getSourceCard().setSVar(aVar, "Number$" + value); } } return true; diff --git a/src/main/java/forge/card/spellability/TargetSelection.java b/src/main/java/forge/card/spellability/TargetSelection.java index b1952e2b120..4ef190d8fa2 100644 --- a/src/main/java/forge/card/spellability/TargetSelection.java +++ b/src/main/java/forge/card/spellability/TargetSelection.java @@ -214,9 +214,9 @@ public class TargetSelection { } } - private Target target = null; - private SpellAbility ability = null; - private Card card = null; + private final Target target; + private final SpellAbility ability; + private TargetSelection subSelection = null; /** @@ -249,7 +249,7 @@ public class TargetSelection { * @return a {@link forge.Card} object. */ public final Card getCard() { - return this.card; + return this.ability.getSourceCard(); } private SpellAbilityRequirements req = null; @@ -314,7 +314,6 @@ public class TargetSelection { public TargetSelection(final Target tgt, final SpellAbility sa) { this.target = tgt; this.ability = sa; - this.card = sa.getSourceCard(); } /** @@ -352,14 +351,14 @@ public class TargetSelection { */ public final boolean chooseTargets() { // if not enough targets chosen, reset and cancel Ability - if (this.bCancel || (this.bTargetingDone && !this.target.isMinTargetsChosen(this.card, this.ability))) { + if (this.bCancel || (this.bTargetingDone && !this.target.isMinTargetsChosen(this.getCard(), this.ability))) { this.bCancel = true; return false; } if (!this.doesTarget() - || this.bTargetingDone && this.target.isMinTargetsChosen(this.card, this.ability) - || this.target.isMaxTargetsChosen(this.card, this.ability) + || this.bTargetingDone && this.target.isMinTargetsChosen(this.getCard(), this.ability) + || this.target.isMaxTargetsChosen(this.getCard(), this.ability) || this.target.isDividedAsYouChoose() && this.target.getStillToDivide() == 0) { final AbilitySub abSub = this.ability.getSubAbility(); @@ -375,7 +374,7 @@ public class TargetSelection { } } - if (!this.target.hasCandidates(this.ability, true) && !this.target.isMinTargetsChosen(this.card, this.ability)) { + if (!this.target.hasCandidates(this.ability, true) && !this.target.isMinTargetsChosen(this.getCard(), this.ability)) { // Cancel ability if there aren't any valid Candidates this.bCancel = true; return false; @@ -505,7 +504,7 @@ public class TargetSelection { } // If the cards must have a specific controller if (tgt.getDefinedController() != null) { - List pl = AbilityUtils.getDefinedPlayers(card, tgt.getDefinedController(), this.ability); + List pl = AbilityUtils.getDefinedPlayers(getCard(), tgt.getDefinedController(), this.ability); if (pl != null && !pl.isEmpty()) { Player controller = pl.get(0); choices = CardLists.filterControlledBy(choices, controller); diff --git a/src/main/java/forge/game/player/Player.java b/src/main/java/forge/game/player/Player.java index d1a9de973bb..a382ed932c1 100644 --- a/src/main/java/forge/game/player/Player.java +++ b/src/main/java/forge/game/player/Player.java @@ -1384,7 +1384,7 @@ public abstract class Player extends GameEntity implements Comparable { this.numDrawnThisDrawStep++; // Miracle draws - if (this.numDrawnThisTurn == 1 && game.getPhaseHandler().getTurn() != 0) { + if (this.numDrawnThisTurn == 1 && game.getPhaseHandler().getTurn() != 1) { drawMiracle(c); } diff --git a/src/main/java/forge/game/player/PlayerController.java b/src/main/java/forge/game/player/PlayerController.java index 34b605fb24b..223969c1676 100644 --- a/src/main/java/forge/game/player/PlayerController.java +++ b/src/main/java/forge/game/player/PlayerController.java @@ -94,7 +94,7 @@ public abstract class PlayerController { public abstract Map assignCombatDamage(Card attacker, List blockers, int damageDealt, GameEntity defender); - public abstract String announceRequirements(SpellAbility ability, String announce); + public abstract Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero); public abstract List choosePermanentsToSacrifice(List validTargets, int amount, SpellAbility sa, boolean destroy, boolean isOptional); diff --git a/src/main/java/forge/game/player/PlayerControllerAi.java b/src/main/java/forge/game/player/PlayerControllerAi.java index dc1c246c42d..9b7e0f7c909 100644 --- a/src/main/java/forge/game/player/PlayerControllerAi.java +++ b/src/main/java/forge/game/player/PlayerControllerAi.java @@ -149,9 +149,9 @@ public class PlayerControllerAi extends PlayerController { } @Override - public String announceRequirements(SpellAbility ability, String announce) { + public Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero) { // For now, these "announcements" are made within the AI classes of the appropriate SA effects - return null; + return null; // return incorrect value to indicate that } @Override diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index 32fc4b4da69..b69a70b6790 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -211,12 +211,11 @@ public class PlayerControllerHuman extends PlayerController { * @see forge.game.player.PlayerController#announceRequirements(java.lang.String) */ @Override - public String announceRequirements(SpellAbility ability, String announce) { - StringBuilder sb = new StringBuilder(ability.getSourceCard().getName()); - sb.append(" - How much will you announce for "); - sb.append(announce); - sb.append("?"); - return JOptionPane.showInputDialog(sb.toString()); + public Integer announceRequirements(SpellAbility ability, String announce, boolean canChooseZero) { + List options = new ArrayList(); + for(int i = canChooseZero ? 0 : 1; i < 100; i++) + options.add(Integer.valueOf(i)); + return GuiChoose.oneOrNone(String.format("%s - How much will you announce for %s?", ability.getSourceCard().getName(), announce), options); } /* (non-Javadoc) From 6a317ab62c47773a9b42473db2b6ad1588a4ecb2 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Sun, 31 Mar 2013 21:32:10 +0000 Subject: [PATCH 020/123] the script of ent/angels --- res/cardsfolder/e/entreat_the_angels.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/cardsfolder/e/entreat_the_angels.txt b/res/cardsfolder/e/entreat_the_angels.txt index e2829e377d1..ec651773ad5 100644 --- a/res/cardsfolder/e/entreat_the_angels.txt +++ b/res/cardsfolder/e/entreat_the_angels.txt @@ -1,7 +1,7 @@ Name:Entreat the Angels ManaCost:X X W W W Types:Sorcery -A:SP$ Token | Cost$ X X W W W | TokenAmount$ X | TokenName$ Angel | TokenTypes$ Creature,Angel | TokenOwner$ You | TokenColors$ White | TokenPower$ 4 | TokenToughness$ 4 | TokenKeywords$ Flying | TokenImage$ w 4 4 angel avr | References$ X | SpellDescription$ Put X 4/4 white Angel creature tokens with flying onto the battlefield. +A:SP$ Token | Cost$ X X W W W | Announce$ X | TokenAmount$ X | TokenName$ Angel | TokenTypes$ Creature,Angel | TokenOwner$ You | TokenColors$ White | TokenPower$ 4 | TokenToughness$ 4 | TokenKeywords$ Flying | TokenImage$ w 4 4 angel avr | References$ X | SpellDescription$ Put X 4/4 white Angel creature tokens with flying onto the battlefield. K:Miracle:X W W SVar:X:Count$xPaid SVar:Picture:http://www.wizards.com/global/images/magic/general/entreat_the_angels.jpg From d1cb0b3aa1da40206550cdffb9e83d3acf3e6e5b Mon Sep 17 00:00:00 2001 From: swordshine Date: Mon, 1 Apr 2013 00:27:38 +0000 Subject: [PATCH 021/123] - Currently no card has keyword "CARDNAME can't have -1/-1 counters placed on it.", replaced with canHaveCountersPlacedOnIt --- src/main/java/forge/card/ability/ai/CountersPutAi.java | 3 +-- src/main/java/forge/card/cost/CostPutCounter.java | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/forge/card/ability/ai/CountersPutAi.java b/src/main/java/forge/card/ability/ai/CountersPutAi.java index 18c0e4c67c3..0fcc4087c96 100644 --- a/src/main/java/forge/card/ability/ai/CountersPutAi.java +++ b/src/main/java/forge/card/ability/ai/CountersPutAi.java @@ -49,8 +49,7 @@ public class CountersPutAi extends SpellAbilityAi { list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), new Predicate() { @Override public boolean apply(final Card c) { - return c.canBeTargetedBy(sa) && !c.hasKeyword("CARDNAME can't have counters placed on it.") - && !(c.hasKeyword("CARDNAME can't have -1/-1 counters placed on it.") && type.equals("M1M1")); + return c.canBeTargetedBy(sa) && c.canHaveCountersPlacedOnIt(CounterType.valueOf(type)); } }); diff --git a/src/main/java/forge/card/cost/CostPutCounter.java b/src/main/java/forge/card/cost/CostPutCounter.java index 3432222a25f..252fdfa87a5 100644 --- a/src/main/java/forge/card/cost/CostPutCounter.java +++ b/src/main/java/forge/card/cost/CostPutCounter.java @@ -193,11 +193,7 @@ public class CostPutCounter extends CostPartWithList { final Player activator = ability.getActivatingPlayer(); final Card source = ability.getSourceCard(); if (this.payCostFromSource()) { - if (source.hasKeyword("CARDNAME can't have counters placed on it.")) { - return false; - } - if (source.hasKeyword("CARDNAME can't have -1/-1 counters placed on it.") - && this.counter.equals(CounterType.M1M1)) { + if (!source.canHaveCountersPlacedOnIt(this.counter)) { return false; } } else { From f648f97c729f2a532b83adbe213f23b7980019c0 Mon Sep 17 00:00:00 2001 From: swordshine Date: Mon, 1 Apr 2013 00:30:53 +0000 Subject: [PATCH 022/123] - Simplified scripts of Burning-Tree Bloodscale and Auriok Siege Sled --- res/cardsfolder/a/auriok_siege_sled.txt | 5 ++--- res/cardsfolder/b/burning_tree_bloodscale.txt | 6 +++--- src/main/java/forge/Card.java | 3 +-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/res/cardsfolder/a/auriok_siege_sled.txt b/res/cardsfolder/a/auriok_siege_sled.txt index 9bd63b014e6..cc275b3de89 100644 --- a/res/cardsfolder/a/auriok_siege_sled.txt +++ b/res/cardsfolder/a/auriok_siege_sled.txt @@ -2,9 +2,8 @@ Name:Auriok Siege Sled ManaCost:6 Types:Artifact Creature Juggernaut PT:3/5 -A:AB$ Pump | Cost$ 1 | ValidTgts$ Creature.Artifact | TgtPrompt$ Select target artifact creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBCantblock | StackDescription$ None | SpellDescription$ Target artifact creature can't block CARDNAME this turn. -SVar:DBCantblock:DB$ Effect | Name$ Auriok Siege Sled Effect | RememberObjects$ Targeted | StaticAbilities$ STCantBlock | ImprintCards$ Self -SVar:STCantBlock:Mode$ Continuous | Affected$ Card.IsImprinted | EffectZone$ Command | AddHiddenKeyword$ CantBeBlockedBy Creature.IsRemembered | Description$ Auriok Siege Sled can't be blocked by target artifact creature. +A:AB$ Pump | Cost$ 1 | ValidTgts$ Creature.Artifact | TgtPrompt$ Select target artifact creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target artifact creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/res/cardsfolder/b/burning_tree_bloodscale.txt b/res/cardsfolder/b/burning_tree_bloodscale.txt index 6f215e6eb80..3de8a45ef5c 100644 --- a/res/cardsfolder/b/burning_tree_bloodscale.txt +++ b/res/cardsfolder/b/burning_tree_bloodscale.txt @@ -3,13 +3,13 @@ ManaCost:2 R G Types:Creature Viashino Berserker PT:2/2 K:Bloodthirst 1 -A:AB$ Pump | Cost$ 2 R | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBCantblock | StackDescription$ None | SpellDescription$ Target creature can't block CARDNAME this turn. -SVar:DBCantblock:DB$ Effect | Name$ Burning-Tree Bloodscale Effect | RememberObjects$ Targeted | StaticAbilities$ STCantBlock | ImprintCards$ Self -SVar:STCantBlock:Mode$ Continuous | Affected$ Card.IsImprinted | EffectZone$ Command | AddHiddenKeyword$ CantBeBlockedBy Creature.IsRemembered | Description$ Burning-Tree Bloodscale can't be blocked by target creature. +A:AB$ Pump | Cost$ 2 R | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True A:AB$ MustBlock | Cost$ 2 G | ValidTgts$ Creature | TgtPrompt$ Select target creature that must block this creature this turn | SpellDescription$ Target creature blocks CARDNAME this turn if able. +SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/burning_tree_bloodscale.jpg Oracle:Bloodthirst 1 (If an opponent was dealt damage this turn, this creature enters the battlefield with a +1/+1 counter on it.)\n{2}{R}: Target creature can't block Burning-Tree Bloodscale this turn.\n{2}{G}: Target creature blocks Burning-Tree Bloodscale this turn if able. SetInfo:GPT Common \ No newline at end of file diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index d4c11d29f3d..bfbf60bfa51 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -1258,13 +1258,12 @@ public class Card extends GameEntity implements Comparable { if (this.hasKeyword("CARDNAME can't have counters placed on it.")) { return false; } - if (this.isCreature() && counterName.equals(CounterType.M1M1)) { + if (this.isCreature() && counterName == CounterType.M1M1) { for (final Card c : this.getController().getCreaturesInPlay()) { // look for Melira, Sylvok Outcast if (c.hasKeyword("Creatures you control can't have -1/-1 counters placed on them.")) { return false; } } - } return true; } From 1a6e17f307228ce41f8095b4d2e56dbf1e8067cd Mon Sep 17 00:00:00 2001 From: swordshine Date: Mon, 1 Apr 2013 00:34:03 +0000 Subject: [PATCH 023/123] - Converted other cards with "Target creature can't block CARDNAME this turn" --- res/cardsfolder/d/duct_crawler.txt | 5 ++ res/cardsfolder/s/screeching_griffin.txt | 5 ++ res/cardsfolder/s/shrewd_hatchling.txt | 5 ++ res/cardsfolder/s/spin_engine.txt | 5 ++ .../cardfactory/CardFactoryCreatures.java | 59 ------------------- 5 files changed, 20 insertions(+), 59 deletions(-) diff --git a/res/cardsfolder/d/duct_crawler.txt b/res/cardsfolder/d/duct_crawler.txt index eafb15e634b..dfae84eaf1e 100644 --- a/res/cardsfolder/d/duct_crawler.txt +++ b/res/cardsfolder/d/duct_crawler.txt @@ -2,6 +2,11 @@ Name:Duct Crawler ManaCost:R Types:Creature Insect PT:1/1 +A:AB$ Pump | Cost$ 1 R | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None +T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/duct_crawler.jpg Oracle:{1}{R}: Target creature can't block Duct Crawler this turn. diff --git a/res/cardsfolder/s/screeching_griffin.txt b/res/cardsfolder/s/screeching_griffin.txt index 64fae6fc2ab..9916a1c4843 100644 --- a/res/cardsfolder/s/screeching_griffin.txt +++ b/res/cardsfolder/s/screeching_griffin.txt @@ -3,6 +3,11 @@ ManaCost:3 W Types:Creature Griffin PT:2/2 K:Flying +A:AB$ Pump | Cost$ R | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None +T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/screeching_griffin.jpg Oracle:Flying\n{R}: Target creature can't block Screeching Griffin this turn. diff --git a/res/cardsfolder/s/shrewd_hatchling.txt b/res/cardsfolder/s/shrewd_hatchling.txt index d687f9fe3f6..2de4d013a60 100644 --- a/res/cardsfolder/s/shrewd_hatchling.txt +++ b/res/cardsfolder/s/shrewd_hatchling.txt @@ -3,6 +3,11 @@ ManaCost:3 UR Types:Creature Elemental PT:6/6 K:etbCounter:M1M1:4 +A:AB$ Pump | Cost$ UR | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None +T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True T:Mode$ SpellCast | ValidCard$ Card.Red | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigRemoveCounter | TriggerDescription$ Whenever you cast a red spell, remove a -1/-1 counter from CARDNAME. T:Mode$ SpellCast | ValidCard$ Card.Blue | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigRemoveCounter | TriggerDescription$ Whenever you cast a blue spell, remove a -1/-1 counter from CARDNAME. SVar:TrigRemoveCounter:AB$ RemoveCounter | Cost$ 0 | Defined$ Self | CounterType$ M1M1 | CounterNum$ 1 diff --git a/res/cardsfolder/s/spin_engine.txt b/res/cardsfolder/s/spin_engine.txt index 680775e7c34..2bdbb53bc82 100644 --- a/res/cardsfolder/s/spin_engine.txt +++ b/res/cardsfolder/s/spin_engine.txt @@ -2,6 +2,11 @@ Name:Spin Engine ManaCost:3 Types:Artifact Creature Construct PT:3/1 +A:AB$ Pump | Cost$ R | ValidTgts$ Creature | TgtPrompt$ Select target creature that can't block this creature this turn | IsCurse$ True | RememberObjects$ Targeted | SubAbility$ DBPump | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +SVar:DBPump:DB$ Pump | KW$ HIDDEN CantBeBlockedBy Creature.IsRemembered | StackDescription$ None +T:Mode$ Phase | Phase$ Cleanup | TriggerZones$ Battlefield | Execute$ DBCleanup | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ DBCleanup | Static$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/spin_engine.jpg Oracle:{R}: Target creature can't block Spin Engine this turn. diff --git a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java index d395dda03d4..5e2ca066ba2 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java @@ -608,62 +608,6 @@ public class CardFactoryCreatures { card.addSpellAbility(discard); } - private static void getCard_DuctCrawler(final Card card, final String cardName) { - final String theCost; - if (cardName.equals("Duct Crawler")) { - theCost = "1 R"; - } else if (cardName.equals("Shrewd Hatchling")) { - theCost = "UR"; - } else { // if (cardName.equals("Spin Engine") || - // cardName.equals("Screeching Griffin")) { - theCost = "R"; - } - - class DuctCrawlerAbility extends AbilityActivated { - private static final long serialVersionUID = 7914250202245863157L; - - public DuctCrawlerAbility(final Card ca, final Cost co, Target t) { - super(ca, co, t); - } - - @Override - public AbilityActivated getCopy() { - return new DuctCrawlerAbility(getSourceCard(), - getPayCosts(), new Target(getTarget())); - } - - @Override - public void resolve() { - final StringBuilder keywordBuilder = new StringBuilder("HIDDEN CARDNAME can't block "); - keywordBuilder.append(this.getSourceCard().toString()); - - final StringBuilder abilityBuilder = new StringBuilder("AB$Pump | Cost$ "); - abilityBuilder.append(theCost); - abilityBuilder.append(" | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | KW$ "); - abilityBuilder.append(keywordBuilder.toString()); - abilityBuilder.append(" | SpellDescription$ Target creature can't block CARDNAME this turn."); - final SpellAbility myAb = AbilityFactory.getAbility(abilityBuilder.toString(), card); - - myAb.getTarget().setTargetChoices(this.getChosenTarget().getTargetChoices()); - myAb.resolve(); - } - - @Override - public String getStackDescription() { - return this.getSourceCard().toString() + " - Target creature can't block " - + this.getSourceCard().getName() + " this turn."; - } - - @Override - public String getDescription() { - return theCost + ": Target creature can't block CARDNAME this turn."; - } - } - final SpellAbility finalAb = new DuctCrawlerAbility(card, new Cost(card, theCost, true), new Target(card, - "Select target creature.", "Creature")); - - card.addSpellAbility(finalAb); - } // // This is a hardcoded card template // @@ -688,9 +632,6 @@ public class CardFactoryCreatures { getCard_PhyrexianDreadnought(card, cardName); } else if (cardName.equals("Nebuchadnezzar")) { getCard_Nebuchadnezzar(card, cardName); - } else if (cardName.equals("Duct Crawler") || cardName.equals("Shrewd Hatchling") - || cardName.equals("Spin Engine") || cardName.equals("Screeching Griffin")) { - getCard_DuctCrawler(card, cardName); } // *************************************************** From 9f77f7ce871acc2268a3db41ff6665a7e4043667 Mon Sep 17 00:00:00 2001 From: swordshine Date: Mon, 1 Apr 2013 01:04:36 +0000 Subject: [PATCH 024/123] - Added Warp World --- .gitattributes | 1 + res/cardsfolder/w/warp_world.txt | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 res/cardsfolder/w/warp_world.txt diff --git a/.gitattributes b/.gitattributes index dc26207456b..a9533a40281 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11963,6 +11963,7 @@ res/cardsfolder/w/warmongers_chariot.txt svneol=native#text/plain res/cardsfolder/w/warmth.txt svneol=native#text/plain res/cardsfolder/w/warning.txt svneol=native#text/plain res/cardsfolder/w/warp_artifact.txt svneol=native#text/plain +res/cardsfolder/w/warp_world.txt -text res/cardsfolder/w/warpath.txt svneol=native#text/plain res/cardsfolder/w/warpath_ghoul.txt svneol=native#text/plain res/cardsfolder/w/warped_devotion.txt svneol=native#text/plain diff --git a/res/cardsfolder/w/warp_world.txt b/res/cardsfolder/w/warp_world.txt new file mode 100644 index 00000000000..ee611675c7f --- /dev/null +++ b/res/cardsfolder/w/warp_world.txt @@ -0,0 +1,17 @@ +Name:Warp World +ManaCost:5 R R R +Types:Sorcery +A:SP$ RepeatEach | Cost$ 5 R R R | RepeatPlayers$ Player | RepeatSubAbility$ DBShuffle | SubAbility$ ChangePermanent | StackDescription$ SpellDescription | SpellDescription$ Each player shuffles all permanents he or she owns into his or her library, then reveals that many cards from the top of his or her library. Each player puts all artifact, creature, and land cards revealed this way onto the battlefield, then does the same for enchantment cards, then puts all cards revealed this way that weren't put onto the battlefield on the bottom of his or her library. +SVar:DBShuffle:DB$ ChangeZoneAll | ChangeType$ Permanent.RememberedPlayerCtrl | Imprint$ True | Origin$ Battlefield | Destination$ Library | Shuffle$ True | SubAbility$ DBDig +SVar:DBDig:DB$ Dig | Defined$ Remembered | NoMove$ True | DigNum$ WarpX | References$ WarpX | RememberRevealed$ True | Reveal$ True | SubAbility$ DBCleanImprint +SVar:DBCleanImprint:DB$ Cleanup | ClearImprinted$ True +SVar:WarpX:Imprinted$Amount +SVar:ChangePermanent:DB$ ChangeZoneAll | ChangeType$ Artifact.IsRemembered,Creature.IsRemembered,Land.IsRemembered | Origin$ Library | Destination$ Battlefield | ForgetChanged$ True | SubAbility$ ChangeEnchantment +SVar:ChangeEnchantment:DB$ ChangeZoneAll | ChangeType$ Enchantment.IsRemembered | Origin$ Library | Destination$ Battlefield | ForgetChanged$ True | SubAbility$ GotoBottom +SVar:GotoBottom:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/warp_world.jpg +Oracle:Each player shuffles all permanents he or she owns into his or her library, then reveals that many cards from the top of his or her library. Each player puts all artifact, creature, and land cards revealed this way onto the battlefield, then does the same for enchantment cards, then puts all cards revealed this way that weren't put onto the battlefield on the bottom of his or her library. +SetInfo:RAV Rare +SetInfo:M10 Rare +SetInfo:10E Rare \ No newline at end of file From f10c0e06f157176949207a8b74bb35b2a1e57f46 Mon Sep 17 00:00:00 2001 From: swordshine Date: Mon, 1 Apr 2013 01:22:57 +0000 Subject: [PATCH 025/123] - Added Zur's Weirding and Vexing Shusher --- .gitattributes | 2 ++ res/cardsfolder/v/vexing_shusher.txt | 11 ++++++++++ res/cardsfolder/z/zurs_weirding.txt | 20 +++++++++++++++++++ .../java/forge/card/ability/AbilityUtils.java | 9 +++++++++ 4 files changed, 42 insertions(+) create mode 100644 res/cardsfolder/v/vexing_shusher.txt create mode 100644 res/cardsfolder/z/zurs_weirding.txt diff --git a/.gitattributes b/.gitattributes index a9533a40281..e2f2482de0a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -11647,6 +11647,7 @@ res/cardsfolder/v/vex.txt svneol=native#text/plain res/cardsfolder/v/vexing_arcanix.txt -text res/cardsfolder/v/vexing_beetle.txt svneol=native#text/plain res/cardsfolder/v/vexing_devil.txt -text +res/cardsfolder/v/vexing_shusher.txt -text res/cardsfolder/v/vexing_sphinx.txt -text res/cardsfolder/v/vhati_il_dal.txt -text res/cardsfolder/v/viashino_bladescout.txt svneol=native#text/plain @@ -12456,6 +12457,7 @@ res/cardsfolder/z/zur_the_enchanter.txt svneol=native#text/plain res/cardsfolder/z/zuran_enchanter.txt svneol=native#text/plain res/cardsfolder/z/zuran_orb.txt svneol=native#text/plain res/cardsfolder/z/zuran_spellcaster.txt svneol=native#text/plain +res/cardsfolder/z/zurs_weirding.txt -text res/defaults/editor.preferences svneol=native#text/xml res/defaults/editor.xml svneol=native#text/xml res/defaults/gauntlet/LOCKED_DotP[!!-~]Preconstructed.dat -text diff --git a/res/cardsfolder/v/vexing_shusher.txt b/res/cardsfolder/v/vexing_shusher.txt new file mode 100644 index 00000000000..6d23bec2d3a --- /dev/null +++ b/res/cardsfolder/v/vexing_shusher.txt @@ -0,0 +1,11 @@ +Name:Vexing Shusher +ManaCost:RG RG +Types:Creature Goblin Shaman +PT:2/2 +K:CARDNAME can't be countered. +A:AB$ Pump | Cost$ RG | ValidTgts$ Card.inZoneStack | TgtZone$ Stack,Battlefield | PumpZone$ Stack | KW$ HIDDEN CARDNAME can't be countered. | SpellDescription$ Target spell can't be countered by spells or abilities. +#Should include another zone otherwise the target would not be defined as a card +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/vexing_shusher.jpg +Oracle:Vexing Shusher can't be countered.\n{R/G}: Target spell can't be countered by spells or abilities. +SetInfo:SHM Rare \ No newline at end of file diff --git a/res/cardsfolder/z/zurs_weirding.txt b/res/cardsfolder/z/zurs_weirding.txt new file mode 100644 index 00000000000..7ef0031af01 --- /dev/null +++ b/res/cardsfolder/z/zurs_weirding.txt @@ -0,0 +1,20 @@ +Name:Zur's Weirding +ManaCost:3 U +Types:Enchantment +S:Mode$ Continuous | Affected$ Player | AddKeyword$ Play with your hand revealed. | Description$ Players play with their hands revealed. +R:Event$ Draw | ActiveZones$ Battlefield | ValidPlayer$ Player | ReplaceWith$ RevealTop | Description$ If a player would draw a card, he or she reveals it instead. Then any other player may pay 2 life. If a player does, put that card into its owner's graveyard. Otherwise, that player draws a card. +SVar:RevealTop:AB$ Dig | Cost$ 0 | Defined$ ReplacedPlayer | DigNum$ 1 | NoMove$ True | Reveal$ True | SubAbility$ DBCheck +SVar:DBCheck:DB$ StoreSVar | SVar$ ZurCheck | Type$ Number | Expression$ 1 | UnlessPayer$ NonReplacedPlayer | UnlessCost$ PayLife<2> | SubAbility$ DBMill | StackDescription$ None +SVar:DBMill:DB$ Mill | Defined$ ReplacedPlayer | NumCards$ 1 | SubAbility$ DBDraw | ConditionCheckSVar$ ZurCheck | ConditionSVarCompare$ EQ0 | StackDescription$ None +SVar:DBDraw:DB$ Draw | Defined$ ReplacedPlayer | NumCards$ 1 | SubAbility$ DBReset | ConditionCheckSVar$ ZurCheck | ConditionSVarCompare$ EQ1 | StackDescription$ that player draws a card +SVar:DBReset:DB$ StoreSVar | SVar$ ZurCheck | Type$ Number | Expression$ 0 +SVar:ZurCheck:Number$0 +SVar:RemAIDeck:True +SVar:RemMultiplayer:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/zurs_weirding.jpg +Oracle:Players play with their hands revealed.\nIf a player would draw a card, he or she reveals it instead. Then any other player may pay 2 life. If a player does, put that card into its owner's graveyard. Otherwise, that player draws a card. +SetInfo:ICE Rare +SetInfo:5ED Rare +SetInfo:8ED Rare +SetInfo:6ED Rare +SetInfo:9ED Rare \ No newline at end of file diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index 55d532622e1..bb4071d6cbb 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -219,6 +219,10 @@ public class AbilityUtils { else if (defined.startsWith("Tapped")) { list = sa.getRootAbility().getPaidList("Tapped"); } + + else if (defined.startsWith("Untapped")) { + list = sa.getRootAbility().getPaidList("Untapped"); + } else if (defined.startsWith("Valid ")) { String validDefined = defined.substring("Valid ".length()); @@ -829,6 +833,11 @@ public class AbilityUtils { } } } + } else if (defined.equals("NonReplacedPlayer")) { + final SpellAbility root = sa.getRootAbility(); + Player p = (Player) root.getReplacingObject("Player"); + players.addAll(sa.getActivatingPlayer().getGame().getPlayers()); + players.remove(p); } else if (defined.equals("EnchantedController")) { if (card.getEnchantingCard() == null) { return players; From b4d3ed45c53a787bf1d1fcc4b2792024c8dd81b4 Mon Sep 17 00:00:00 2001 From: swordshine Date: Mon, 1 Apr 2013 01:29:01 +0000 Subject: [PATCH 026/123] - Added Benthic Explorers --- .gitattributes | 1 + res/cardsfolder/b/benthic_explorers.txt | 8 ++++++++ src/main/java/forge/card/cost/CostUntapType.java | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 res/cardsfolder/b/benthic_explorers.txt diff --git a/.gitattributes b/.gitattributes index e2f2482de0a..7f59769cb7c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -902,6 +902,7 @@ res/cardsfolder/b/benevolent_bodyguard.txt svneol=native#text/plain res/cardsfolder/b/benevolent_unicorn.txt svneol=native#text/plain res/cardsfolder/b/benthic_behemoth.txt svneol=native#text/plain res/cardsfolder/b/benthic_djinn.txt svneol=native#text/plain +res/cardsfolder/b/benthic_explorers.txt -text res/cardsfolder/b/benthicore.txt svneol=native#text/plain res/cardsfolder/b/bequeathal.txt svneol=native#text/plain res/cardsfolder/b/bereavement.txt svneol=native#text/plain diff --git a/res/cardsfolder/b/benthic_explorers.txt b/res/cardsfolder/b/benthic_explorers.txt new file mode 100644 index 00000000000..0fb41939ce8 --- /dev/null +++ b/res/cardsfolder/b/benthic_explorers.txt @@ -0,0 +1,8 @@ +Name:Benthic Explorers +ManaCost:3 U +Types:Creature Merfolk Scout +PT:2/4 +A:AB$ ManaReflected | Cost$ T untapYType<1/Land.OppCtrl/land> | ColorOrType$ Type | Valid$ Defined.Untapped | ReflectProperty$ Produce | SpellDescription$ Add one mana of any type that land could produce to your mana pool. +SVar:Picture:http://www.wizards.com/global/images/magic/general/benthic_explorers.jpg +Oracle:{T}, Untap a tapped land an opponent controls: Add one mana of any type that land could produce to your mana pool. +SetInfo:ALL Common x2 \ No newline at end of file diff --git a/src/main/java/forge/card/cost/CostUntapType.java b/src/main/java/forge/card/cost/CostUntapType.java index 5d0f4bb440d..3c2e3c8cbed 100644 --- a/src/main/java/forge/card/cost/CostUntapType.java +++ b/src/main/java/forge/card/cost/CostUntapType.java @@ -83,7 +83,7 @@ public class CostUntapType extends CostPartWithList { sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), " tapped " + desc)); - if (this.getType().contains("YouDontCtrl")) { + if (this.getType().contains("OppCtrl")) { sb.append(" an opponent controls"); } else if (this.getType().contains("YouCtrl")) { sb.append(" you control"); From 9a13f34517ddaeedb66afd830d1e9c4306db3b8b Mon Sep 17 00:00:00 2001 From: swordshine Date: Mon, 1 Apr 2013 02:23:14 +0000 Subject: [PATCH 027/123] - Added Leech Bonder --- .gitattributes | 1 + res/cardsfolder/l/leech_bonder.txt | 11 +++ .../ability/effects/CountersMoveEffect.java | 99 +++++++++++++++++-- 3 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 res/cardsfolder/l/leech_bonder.txt diff --git a/.gitattributes b/.gitattributes index 7f59769cb7c..16003e58249 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5953,6 +5953,7 @@ res/cardsfolder/l/leap_of_flame.txt svneol=native#text/plain res/cardsfolder/l/leaping_lizard.txt svneol=native#text/plain res/cardsfolder/l/leatherback_baloth.txt svneol=native#text/plain res/cardsfolder/l/leave_no_trace.txt svneol=native#text/plain +res/cardsfolder/l/leech_bonder.txt -text res/cardsfolder/l/leeches.txt svneol=native#text/plain res/cardsfolder/l/leeching_bite.txt -text res/cardsfolder/l/leeching_licid.txt -text diff --git a/res/cardsfolder/l/leech_bonder.txt b/res/cardsfolder/l/leech_bonder.txt new file mode 100644 index 00000000000..a7f0e03063e --- /dev/null +++ b/res/cardsfolder/l/leech_bonder.txt @@ -0,0 +1,11 @@ +Name:Leech Bonder +ManaCost:2 U +Types:Creature Merfolk Soldier +PT:3/3 +K:etbCounter:M1M1:2 +A:AB$ Pump | Cost$ U Q | ValidTgts$ Creature | TgtPrompt$ Select target creature to remove counters | SubAbility$ DBMove | StackDescription$ None | SpellDescription$ Move a counter from target creature onto another target creature. +SVar:DBMove:DB$ MoveCounter | Source$ ParentTarget | ValidTgts$ Creature | TgtPrompt$ Select target creature to add counters | TargetUnique$ True | CounterType$ Any | CounterNum$ 1 +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/leech_bonder.jpg +Oracle:Leech Bonder enters the battlefield with two -1/-1 counters on it.\n{U}, {Q}: Move a counter from target creature onto another target creature. ({Q} is the untap symbol.) +SetInfo:SHM Uncommon \ No newline at end of file diff --git a/src/main/java/forge/card/ability/effects/CountersMoveEffect.java b/src/main/java/forge/card/ability/effects/CountersMoveEffect.java index e2cba82325b..dd41e4f22cc 100644 --- a/src/main/java/forge/card/ability/effects/CountersMoveEffect.java +++ b/src/main/java/forge/card/ability/effects/CountersMoveEffect.java @@ -1,6 +1,8 @@ package forge.card.ability.effects; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import forge.Card; import forge.CounterType; @@ -8,6 +10,7 @@ import forge.card.ability.AbilityUtils; import forge.card.ability.SpellAbilityEffect; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.gui.GuiChoose; public class CountersMoveEffect extends SpellAbilityEffect { @@ -28,11 +31,19 @@ public class CountersMoveEffect extends SpellAbilityEffect { source = srcCards.get(0); } final List tgtCards = getTargetCards(sa); - - final CounterType cType = CounterType.valueOf(sa.getParam("CounterType")); + final String countername = sa.getParam("CounterType"); final int amount = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("CounterNum"), sa); - sb.append("Move ").append(amount).append(" ").append(cType.getName()).append(" counter"); + sb.append("Move "); + if ("Any".matches(countername)) { + if (amount == 1) { + sb.append("a counter"); + } else { + sb.append(amount).append(" ").append(" counter"); + } + } else { + sb.append(amount).append(" ").append(countername).append(" counter"); + } if (amount != 1) { sb.append("s"); } @@ -46,12 +57,21 @@ public class CountersMoveEffect extends SpellAbilityEffect { @Override public void resolve(SpellAbility sa) { final Card host = sa.getSourceCard(); - - final CounterType cType = CounterType.valueOf(sa.getParam("CounterType")); + final String counterName = sa.getParam("CounterType"); int amount = 0; if (!sa.getParam("CounterNum").equals("All")) { amount = AbilityUtils.calculateAmount(host, sa.getParam("CounterNum"), sa); } + + CounterType cType = null; + try { + cType = AbilityUtils.getCounterType(counterName, sa); + } catch (Exception e) { + if (!counterName.matches("Any")) { + System.out.println("Counter type doesn't match, nor does an SVar exist with the type name."); + return; + } + } Card source = null; List srcCards; @@ -76,16 +96,75 @@ public class CountersMoveEffect extends SpellAbilityEffect { for (final Card dest : tgtCards) { if ((null != source) && (null != dest)) { - if (source.getCounters(cType) >= amount) { - if (!dest.hasKeyword("CARDNAME can't have counters placed on it.") - && !(dest.hasKeyword("CARDNAME can't have -1/-1 counters placed on it.") && cType - .equals(CounterType.M1M1))) { + if (!"Any".matches(counterName)) { + if (dest.canHaveCountersPlacedOnIt(cType) + && source.getCounters(cType) >= amount) { dest.addCounter(cType, amount, true); source.subtractCounter(cType, amount); } + } else { + if (dest.hasKeyword("CARDNAME can't have counters placed on it.")) { + return; + } + boolean check = false; + for (final Card c : dest.getController().getCreaturesInPlay()) {//Melira, Sylvok Outcast + if (c.hasKeyword("Creatures you control can't have -1/-1 counters placed on them.")) { + check = true; + } + } + while (amount > 0 && source.hasCounters()) { + final Map tgtCounters = source.getCounters(); + CounterType chosenType = null; + int chosenAmount; + if (sa.getActivatingPlayer().isHuman()) { + final ArrayList typeChoices = new ArrayList(); + // get types of counters + for (CounterType key : tgtCounters.keySet()) { + if (tgtCounters.get(key) > 0 && !(key == CounterType.M1M1 && check)) { + typeChoices.add(key); + } + } + if (typeChoices.isEmpty()) { + return; + } + if (typeChoices.size() > 1) { + String prompt = "Select type counters to remove"; + chosenType = GuiChoose.one(prompt, typeChoices); + } else { + chosenType = typeChoices.get(0); + } + chosenAmount = tgtCounters.get(chosenType); + if (chosenAmount > amount) { + chosenAmount = amount; + } + // make list of amount choices + if (chosenAmount > 1) { + final List choices = new ArrayList(); + for (int i = 1; i <= chosenAmount; i++) { + choices.add(Integer.valueOf(i)); + } + String prompt = "Select the number of " + chosenType.getName() + " counters to remove"; + chosenAmount = GuiChoose.one(prompt, choices); + } + } else { + for (Object key : tgtCounters.keySet()) { + if (tgtCounters.get(key) > 0) { + chosenType = (CounterType) key; + break; + } + } + // subtract all of selected type + chosenAmount = tgtCounters.get(chosenType); + if (chosenAmount > amount) { + chosenAmount = amount; + } + } + dest.addCounter(chosenType, chosenAmount, true); + source.subtractCounter(chosenType, chosenAmount); + amount -= chosenAmount; + } } } } } // moveCounterResolve - } From df171b28bda98436911e4a0119889d292768d237 Mon Sep 17 00:00:00 2001 From: swordshine Date: Mon, 1 Apr 2013 02:32:23 +0000 Subject: [PATCH 028/123] - Added Weight of Conscience --- .gitattributes | 1 + res/cardsfolder/w/weight_of_conscience.txt | 10 +++ .../java/forge/card/cost/CostTapType.java | 68 ++++++++++++++++--- 3 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 res/cardsfolder/w/weight_of_conscience.txt diff --git a/.gitattributes b/.gitattributes index 16003e58249..9ab51f67db4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12038,6 +12038,7 @@ res/cardsfolder/w/wei_infantry.txt svneol=native#text/plain res/cardsfolder/w/wei_night_raiders.txt svneol=native#text/plain res/cardsfolder/w/wei_scout.txt svneol=native#text/plain res/cardsfolder/w/wei_strike_force.txt svneol=native#text/plain +res/cardsfolder/w/weight_of_conscience.txt -text res/cardsfolder/w/weight_of_spires.txt -text res/cardsfolder/w/weird_harvest.txt -text res/cardsfolder/w/weirding_shaman.txt svneol=native#text/plain diff --git a/res/cardsfolder/w/weight_of_conscience.txt b/res/cardsfolder/w/weight_of_conscience.txt new file mode 100644 index 00000000000..0d929197ba5 --- /dev/null +++ b/res/cardsfolder/w/weight_of_conscience.txt @@ -0,0 +1,10 @@ +Name:Weight of Conscience +ManaCost:1 W +Types:Enchantment Aura +K:Enchant creature +A:SP$ Attach | Cost$ 1 W | ValidTgts$ Creature | AILogic$ Curse +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddHiddenKeyword$ CARDNAME can't attack. | Description$ Enchanted creature can't attack. +A:AB$ ChangeZone | Cost$ tapXType<2/Creature.sharesCreatureTypeWith/creatures that share a creature type> | Defined$ Enchanted | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile enchanted creature. +SVar:Picture:http://www.wizards.com/global/images/magic/general/weight_of_conscience.jpg +Oracle:Enchant creature\nEnchanted creature can't attack.\nTap two untapped creatures you control that share a creature type: Exile enchanted creature. +SetInfo:MOR Common \ No newline at end of file diff --git a/src/main/java/forge/card/cost/CostTapType.java b/src/main/java/forge/card/cost/CostTapType.java index 32e7e8f8253..7c1eb7e37df 100644 --- a/src/main/java/forge/card/cost/CostTapType.java +++ b/src/main/java/forge/card/cost/CostTapType.java @@ -19,8 +19,12 @@ package forge.card.cost; import java.util.ArrayList; import java.util.List; + +import com.google.common.base.Predicate; + import forge.Card; import forge.CardLists; +import forge.CardPredicates; import forge.CardPredicates.Presets; import forge.FThreads; import forge.card.ability.AbilityUtils; @@ -80,11 +84,14 @@ public class CostTapType extends CostPartWithList { final Integer i = this.convertAmount(); final String desc = this.getDescription(); - - sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "untapped " + desc)); - - sb.append(" you control"); - + final String type = this.getType(); + + if (type.contains("sharesCreatureTypeWith")) { + sb.append("two untapped creatures you control that share a creature type"); + } else { + sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "untapped " + desc)); + sb.append(" you control"); + } return sb.toString(); } @@ -115,13 +122,34 @@ public class CostTapType extends CostPartWithList { final Card source = ability.getSourceCard(); List typeList = new ArrayList(activator.getCardsIn(ZoneType.Battlefield)); - - typeList = CardLists.getValidCards(typeList, this.getType().split(";"), activator, source); + String type = this.getType(); + boolean sameType = false; + + if (type.contains("sharesCreatureTypeWith")) { + sameType = true; + type = type.replace("sharesCreatureTypeWith", ""); + } + + typeList = CardLists.getValidCards(typeList, type.split(";"), activator, source); if (!canTapSource) { typeList.remove(source); } typeList = CardLists.filter(typeList, Presets.UNTAPPED); + + if (sameType) { + for (final Card card : typeList) { + if (CardLists.filter(typeList, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.sharesCreatureTypeWith(card); + } + }).size() > 1) { + return true; + } + } + return false; + } final Integer amount = this.convertAmount(); if ((typeList.size() == 0) || ((amount != null) && (typeList.size() < amount))) { @@ -141,8 +169,29 @@ public class CostTapType extends CostPartWithList { @Override public final boolean payHuman(final SpellAbility ability, final GameState game) { List typeList = new ArrayList(ability.getActivatingPlayer().getCardsIn(ZoneType.Battlefield)); - typeList = CardLists.getValidCards(typeList, this.getType().split(";"), ability.getActivatingPlayer(), ability.getSourceCard()); + String type = this.getType(); + boolean sameType = false; + if (type.contains("sharesCreatureTypeWith")) { + sameType = true; + type = type.replace("sharesCreatureTypeWith", ""); + } + typeList = CardLists.getValidCards(typeList, type.split(";"), ability.getActivatingPlayer(), ability.getSourceCard()); typeList = CardLists.filter(typeList, Presets.UNTAPPED); + if (sameType) { + final List List2 = typeList; + typeList = CardLists.filter(typeList, new Predicate() { + @Override + public boolean apply(final Card c) { + for (Card card : List2) { + if (!card.equals(c) && card.sharesCreatureTypeWith(c)) { + return true; + } + } + return false; + } + }); + } + final String amount = this.getAmount(); final Card source = ability.getSourceCard(); Integer c = this.convertAmount(); @@ -185,6 +234,9 @@ public class CostTapType extends CostPartWithList { c = AbilityUtils.calculateAmount(source, amount, ability); } } + if (this.getType().contains("sharesCreatureTypeWith")) { + return null; + } List totap = ComputerUtil.chooseTapType(ai, this.getType(), source, !canTapSource, c); From f3f2ae9efcb88bd052a9891436ef8f5b2f066f70 Mon Sep 17 00:00:00 2001 From: swordshine Date: Mon, 1 Apr 2013 05:25:25 +0000 Subject: [PATCH 029/123] - Added Multani's Presence and Lullmage Mentor --- .gitattributes | 3 + res/cardsfolder/l/lullmage_mentor.txt | 10 ++ res/cardsfolder/m/multanis_presence.txt | 8 ++ res/lists/token-images.txt | 2 +- .../card/ability/effects/CounterEffect.java | 9 ++ .../forge/card/trigger/TriggerCountered.java | 97 +++++++++++++++++++ .../java/forge/card/trigger/TriggerType.java | 1 + 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 res/cardsfolder/l/lullmage_mentor.txt create mode 100644 res/cardsfolder/m/multanis_presence.txt create mode 100644 src/main/java/forge/card/trigger/TriggerCountered.java diff --git a/.gitattributes b/.gitattributes index 9ab51f67db4..951e74cfb8d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6206,6 +6206,7 @@ res/cardsfolder/l/lu_xun_scholar_general.txt svneol=native#text/plain res/cardsfolder/l/lucent_liminid.txt svneol=native#text/plain res/cardsfolder/l/ludevics_test_subject_ludevics_abomination.txt -text res/cardsfolder/l/lull.txt svneol=native#text/plain +res/cardsfolder/l/lullmage_mentor.txt -text res/cardsfolder/l/lumbering_satyr.txt svneol=native#text/plain res/cardsfolder/l/lumberknot.txt -text res/cardsfolder/l/lumengrid_augur.txt -text @@ -6940,6 +6941,7 @@ res/cardsfolder/m/multani_maro_sorcerer.txt svneol=native#text/plain res/cardsfolder/m/multanis_acolyte.txt svneol=native#text/plain res/cardsfolder/m/multanis_decree.txt svneol=native#text/plain res/cardsfolder/m/multanis_harmony.txt -text svneol=unset#text/plain +res/cardsfolder/m/multanis_presence.txt -text res/cardsfolder/m/mundungu.txt -text res/cardsfolder/m/mungha_wurm.txt svneol=native#text/plain res/cardsfolder/m/muraganda_petroglyphs.txt svneol=native#text/plain @@ -13789,6 +13791,7 @@ src/main/java/forge/card/trigger/TriggerChangesZone.java svneol=native#text/plai src/main/java/forge/card/trigger/TriggerClashed.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerCounterAdded.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerCounterRemoved.java -text +src/main/java/forge/card/trigger/TriggerCountered.java -text src/main/java/forge/card/trigger/TriggerCycled.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerDamageDone.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerDiscarded.java svneol=native#text/plain diff --git a/res/cardsfolder/l/lullmage_mentor.txt b/res/cardsfolder/l/lullmage_mentor.txt new file mode 100644 index 00000000000..8e9e0a00e64 --- /dev/null +++ b/res/cardsfolder/l/lullmage_mentor.txt @@ -0,0 +1,10 @@ +Name:Lullmage Mentor +ManaCost:1 U U +Types:Creature Merfolk Wizard +PT:2/2 +T:Mode$ Countered | ValidCause$ Card.YouCtrl | ValidCard$ Card | OptionalDecider$ You | Execute$ TrigToken | TriggerDescription$ Whenever a spell or ability you control counters a spell, you may put a 1/1 blue Merfolk creature token onto the battlefield. +SVar:TrigToken:AB$ Token | Cost$ 0 | TokenImage$ U 1 1 Merfolk | TokenAmount$ 1 | TokenName$ Merfolk | TokenTypes$ Creature,Merfolk | TokenOwner$ You | TokenColors$ Blue | TokenPower$ 1 | TokenToughness$ 1 +A:AB$ Counter | Cost$ tapXType<7/Merfolk> | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell. +SVar:Picture:http://www.wizards.com/global/images/magic/general/lullmage_mentor.jpg +Oracle:Whenever a spell or ability you control counters a spell, you may put a 1/1 blue Merfolk creature token onto the battlefield.\nTap seven untapped Merfolk you control: Counter target spell. +SetInfo:ZEN Rare \ No newline at end of file diff --git a/res/cardsfolder/m/multanis_presence.txt b/res/cardsfolder/m/multanis_presence.txt new file mode 100644 index 00000000000..b48909644dd --- /dev/null +++ b/res/cardsfolder/m/multanis_presence.txt @@ -0,0 +1,8 @@ +Name:Multani's Presence +ManaCost:G +Types:Enchantment +T:Mode$ Countered | ValidCard$ Card | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ DBDraw | TriggerDescription$ Whenever a spell you've cast is countered, draw a card. +SVar:DBDraw:AB$ Draw | Cost$ 0 | NumCards$ 1 +SVar:Picture:http://www.wizards.com/global/images/magic/general/multanis_presence.jpg +Oracle:Whenever a spell you've cast is countered, draw a card. +SetInfo:ULG Uncommon \ No newline at end of file diff --git a/res/lists/token-images.txt b/res/lists/token-images.txt index 56b2c996242..94c94c43f1c 100644 --- a/res/lists/token-images.txt +++ b/res/lists/token-images.txt @@ -166,6 +166,7 @@ u_1_1_camarid.jpg http://www.cardforge.org/fpics/tokens/u_1 u_1_1_cloud_sprite.jpg http://www.cardforge.org/fpics/tokens/u_1_1_cloud_sprite.jpg u_1_1_faerie.jpg http://www.cardforge.org/fpics/tokens/u_1_1_faerie.jpg u_1_1_illusion.jpg http://www.cardforge.org/fpics/tokens/u_1_1_illusion.jpg +u_1_1_merfolk.jpg http://www.cardforge.org/fpics/tokens/u_1_1_merfolk.jpg u_1_1_merfolk_wizard.jpg http://www.cardforge.org/fpics/tokens/u_1_1_merfolk_wizard.jpg u_1_1_thopter.jpg http://www.cardforge.org/fpics/tokens/u_1_1_thopter.jpg u_1_1_spirit_avr.jpg http://www.cardforge.org/fpics/tokens/u_1_1_spirit_avr.jpg @@ -236,5 +237,4 @@ morph.jpg http://www.cardforge.org/fpics/effects/mo # //These tokens are not currently used by any cards in Forge, but links provided should they be scripted so the correct name is used: # //g_1_1_wolves_of_the_hunt.jpg http://www.cardforge.org/fpics/tokens/g_1_1_wolves_of_the_hunt.jpg [LEG] Master of the Hunt # //rg_1_1_goblin_warrior.jpg http://www.cardforge.org/fpics/tokens/rg_1_1_goblin_warrior.jpg [SHM] Wort, the Raidmother -# //u_1_1_merfolk.jpg http://www.cardforge.org/fpics/tokens/u_1_1_merfolk.jpg [ZEN] Lullmage Mentor # //w_1_1_knight.jpg http://www.cardforge.org/fpics/tokens/w_1_1_knight.jpg [ALL] Errand of Duty diff --git a/src/main/java/forge/card/ability/effects/CounterEffect.java b/src/main/java/forge/card/ability/effects/CounterEffect.java index 3300394624b..7d0324d56ec 100644 --- a/src/main/java/forge/card/ability/effects/CounterEffect.java +++ b/src/main/java/forge/card/ability/effects/CounterEffect.java @@ -1,6 +1,7 @@ package forge.card.ability.effects; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import forge.Card; @@ -10,6 +11,7 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.SpellPermanent; +import forge.card.trigger.TriggerType; public class CounterEffect extends SpellAbilityEffect { @Override @@ -164,6 +166,13 @@ public class CounterEffect extends SpellAbilityEffect { throw new IllegalArgumentException("AbilityFactory_CounterMagic: Invalid Destination argument for card " + srcSA.getSourceCard().getName()); } + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Player", tgtSA.getActivatingPlayer()); + runParams.put("Card", tgtSA.getSourceCard()); + runParams.put("Cause", srcSA.getSourceCard()); + srcSA.getActivatingPlayer().getGame().getTriggerHandler().runTrigger(TriggerType.Countered, runParams, false); + if (!tgtSA.isAbility()) { System.out.println("Send countered spell to " + destination); diff --git a/src/main/java/forge/card/trigger/TriggerCountered.java b/src/main/java/forge/card/trigger/TriggerCountered.java new file mode 100644 index 00000000000..9e8cafc4ca2 --- /dev/null +++ b/src/main/java/forge/card/trigger/TriggerCountered.java @@ -0,0 +1,97 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.card.trigger; + +import forge.Card; +import forge.card.spellability.SpellAbility; + +/** + *

+ * Trigger_Countered class. + *

+ * + * @author Forge + * @version $Id: TriggerCountered.java 17802 2012-10-31 08:05:14Z Max mtg $ + */ +public class TriggerCountered extends Trigger { + + /** + *

+ * Constructor for Trigger_Countered. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerCountered(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.getMapParams().containsKey("ValidCard")) { + if (!matchesValid(runParams2.get("Card"), this.getMapParams().get("ValidCard").split(","), + this.getHostCard())) { + return false; + } + } + + if (this.getMapParams().containsKey("ValidPlayer")) { + if (!matchesValid(runParams2.get("Player"), this.getMapParams().get("ValidPlayer").split(","), + this.getHostCard())) { + return false; + } + } + + if (this.getMapParams().containsKey("ValidCause")) { + if (runParams2.get("Cause") == null) { + return false; + } + if (!matchesValid(runParams2.get("Cause"), this.getMapParams().get("ValidCause").split(","), + this.getHostCard())) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public final Trigger getCopy() { + final Trigger copy = new TriggerCountered(this.getMapParams(), this.getHostCard(), this.isIntrinsic()); + if (this.getOverridingAbility() != null) { + copy.setOverridingAbility(this.getOverridingAbility()); + } + + copyFieldsTo(copy); + return copy; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + sa.setTriggeringObject("Cause", this.getRunParams().get("Cause")); + sa.setTriggeringObject("Player", this.getRunParams().get("Player")); + } +} diff --git a/src/main/java/forge/card/trigger/TriggerType.java b/src/main/java/forge/card/trigger/TriggerType.java index 3de64f9b95c..9af39aaf1d0 100644 --- a/src/main/java/forge/card/trigger/TriggerType.java +++ b/src/main/java/forge/card/trigger/TriggerType.java @@ -20,6 +20,7 @@ public enum TriggerType { ChangesZone(TriggerChangesZone.class), Clashed(TriggerClashed.class), + Countered(TriggerCountered.class), TapsForMana(TriggerTapsForMana.class), CounterAdded(TriggerCounterAdded.class), CounterRemoved(TriggerCounterRemoved.class), From 41f77ba74561b35f052bf3b9c60f15d9eb057b4b Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 05:30:10 +0000 Subject: [PATCH 030/123] Announce will offer a list of 0 or 1 through 20 to choose from and an option to enter a custom number for less frequent cases. --- .../game/player/PlayerControllerHuman.java | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index b69a70b6790..97a735e5b0b 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -212,10 +212,28 @@ public class PlayerControllerHuman extends PlayerController { */ @Override public Integer announceRequirements(SpellAbility ability, String announce, boolean canChooseZero) { - List options = new ArrayList(); - for(int i = canChooseZero ? 0 : 1; i < 100; i++) + List options = new ArrayList(); + for(int i = canChooseZero ? 0 : 1; i <= 20; i++) options.add(Integer.valueOf(i)); - return GuiChoose.oneOrNone(String.format("%s - How much will you announce for %s?", ability.getSourceCard().getName(), announce), options); + options.add("Other amount"); + + + Object chosen = GuiChoose.oneOrNone("Choose " + announce + " for " + ability.getSourceCard().getName(), options); + if (chosen instanceof Integer || chosen == null) + return (Integer)chosen; + + String message = String.format("How much will you announce for %s?%s", announce, canChooseZero ? "" : " (X cannot be 0)"); + while(true){ + String str = JOptionPane.showInputDialog(null, message, ability.getSourceCard().getName(), JOptionPane.QUESTION_MESSAGE); + if (null == str) return null; // that is 'cancel' + + if(StringUtils.isNumeric(str)) { + Integer val = Integer.valueOf(str); + if (val == 0 && canChooseZero || val > 0) + return val; + } + JOptionPane.showMessageDialog(null, "You have to enter a valid number", "Announce value", JOptionPane.WARNING_MESSAGE); + } } /* (non-Javadoc) From 9d4a18f0c29e45eebe6884284e6a94894fa8861f Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 09:56:12 +0000 Subject: [PATCH 031/123] Targeting code fixed to support fireballs and other spell with multiple targets. Some classes had references to ArrayList instead of list, this was also changed --- .gitattributes | 2 +- .../java/forge/card/ability/AbilityUtils.java | 2 +- .../java/forge/card/ability/ai/CounterAi.java | 6 +- .../forge/card/ability/ai/DamageDealAi.java | 9 +- .../java/forge/card/ability/ai/DrawAi.java | 4 +- .../ability/effects/ChangeZoneEffect.java | 8 +- .../effects/ControlExchangeEffect.java | 6 +- .../ability/effects/DestroyAllEffect.java | 2 +- .../cardfactory/CardFactoryCreatures.java | 1 - .../card/cardfactory/CardFactoryUtil.java | 2 +- .../java/forge/card/cost/CostTapType.java | 1 - .../card/replacement/ReplacementHandler.java | 2 +- .../forge/card/spellability/SpellAbility.java | 17 +- .../SpellAbilityRequirements.java | 68 +-- .../java/forge/card/spellability/Target.java | 6 +- .../card/spellability/TargetChoices.java | 13 +- ...argetSelection.java => TargetChooser.java} | 564 ++++++++---------- .../forge/card/trigger/TriggerHandler.java | 2 +- .../forge/control/input/InputMulligan.java | 4 +- src/main/java/forge/game/GameActionPlay.java | 23 +- src/main/java/forge/game/zone/MagicStack.java | 4 +- .../controllers/CEditorConstructed.java | 1 - 22 files changed, 326 insertions(+), 421 deletions(-) rename src/main/java/forge/card/spellability/{TargetSelection.java => TargetChooser.java} (53%) diff --git a/.gitattributes b/.gitattributes index 951e74cfb8d..a350675a54e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13766,7 +13766,7 @@ src/main/java/forge/card/spellability/SpellAbilityVariables.java svneol=native#t src/main/java/forge/card/spellability/SpellPermanent.java svneol=native#text/plain src/main/java/forge/card/spellability/Target.java svneol=native#text/plain src/main/java/forge/card/spellability/TargetChoices.java svneol=native#text/plain -src/main/java/forge/card/spellability/TargetSelection.java svneol=native#text/plain +src/main/java/forge/card/spellability/TargetChooser.java svneol=native#text/plain src/main/java/forge/card/spellability/package-info.java svneol=native#text/plain src/main/java/forge/card/staticability/StaticAbility.java svneol=native#text/plain src/main/java/forge/card/staticability/StaticAbilityCantAttackBlock.java -text diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index bb4071d6cbb..8db20cd141d 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -623,7 +623,7 @@ public class AbilityUtils { } else if (type.startsWith("Targeted")) { source = null; - ArrayList tgts = sa.findTargetedCards(); + List tgts = sa.findTargetedCards(); if (!tgts.isEmpty()) { source = tgts.get(0); } diff --git a/src/main/java/forge/card/ability/ai/CounterAi.java b/src/main/java/forge/card/ability/ai/CounterAi.java index 9e084635dfb..1600901e869 100644 --- a/src/main/java/forge/card/ability/ai/CounterAi.java +++ b/src/main/java/forge/card/ability/ai/CounterAi.java @@ -8,7 +8,7 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; -import forge.card.spellability.TargetSelection; +import forge.card.spellability.TargetChooser; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilMana; @@ -50,7 +50,7 @@ public class CounterAi extends SpellAbilityAi { } tgt.resetTargets(); - if (TargetSelection.matchSpellAbility(sa, topSA, tgt)) { + if (TargetChooser.matchSpellAbility(sa, topSA, tgt)) { tgt.addTarget(topSA); } else { return false; @@ -120,7 +120,7 @@ public class CounterAi extends SpellAbilityAi { } tgt.resetTargets(); - if (TargetSelection.matchSpellAbility(sa, topSA, tgt)) { + if (TargetChooser.matchSpellAbility(sa, topSA, tgt)) { tgt.addTarget(topSA); } else { return false; diff --git a/src/main/java/forge/card/ability/ai/DamageDealAi.java b/src/main/java/forge/card/ability/ai/DamageDealAi.java index f554e23c259..b2f0ba01005 100644 --- a/src/main/java/forge/card/ability/ai/DamageDealAi.java +++ b/src/main/java/forge/card/ability/ai/DamageDealAi.java @@ -15,7 +15,7 @@ import forge.card.cost.Cost; import forge.card.spellability.AbilitySub; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; -import forge.card.spellability.TargetSelection; +import forge.card.spellability.TargetChooser; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; @@ -103,8 +103,7 @@ public class DamageDealAi extends DamageAiBase { if (tgt != null && tgt.getTargetPlayers().isEmpty() && !sa.hasParam("DividedAsYouChoose")) { int actualPay = 0; final boolean noPrevention = sa.hasParam("NoPrevention"); - final ArrayList cards = tgt.getTargetCards(); - for (final Card c : cards) { + for (final Card c : tgt.getTargetCards()) { final int adjDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); if ((adjDamage > actualPay) && (adjDamage <= dmg)) { actualPay = adjDamage; @@ -144,7 +143,7 @@ public class DamageDealAi extends DamageAiBase { final ArrayList objects = tgt.getTargets(); if (saMe.hasParam("TargetUnique")) { - objects.addAll(TargetSelection.getUniqueTargets(saMe)); + objects.addAll(TargetChooser.getUniqueTargets(saMe)); } for (final Object o : objects) { if (o instanceof Card) { @@ -472,7 +471,7 @@ public class DamageDealAi extends DamageAiBase { // If I can kill my target by paying less mana, do it int actualPay = 0; final boolean noPrevention = sa.hasParam("NoPrevention"); - final ArrayList cards = tgt.getTargetCards(); + final List cards = tgt.getTargetCards(); //target is a player if (cards.isEmpty()) { actualPay = dmg; diff --git a/src/main/java/forge/card/ability/ai/DrawAi.java b/src/main/java/forge/card/ability/ai/DrawAi.java index decde1c2179..d7cff15a780 100644 --- a/src/main/java/forge/card/ability/ai/DrawAi.java +++ b/src/main/java/forge/card/ability/ai/DrawAi.java @@ -18,7 +18,7 @@ */ package forge.card.ability.ai; -import java.util.ArrayList; +import java.util.List; import java.util.Random; import forge.Card; @@ -96,7 +96,7 @@ public class DrawAi extends SpellAbilityAi { } if (tgt != null) { - final ArrayList players = tgt.getTargetPlayers(); + final List players = tgt.getTargetPlayers(); if ((players.size() > 0) && players.get(0).isOpponentOf(ai)) { return true; } diff --git a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java index 96996ba07d1..fb0943e0caf 100644 --- a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java +++ b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java @@ -171,7 +171,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final StringBuilder sbTargets = new StringBuilder(); - ArrayList tgts; + List tgts; if (sa.getTarget() != null) { tgts = sa.getTarget().getTargetCards(); } else { @@ -301,8 +301,8 @@ public class ChangeZoneEffect extends SpellAbilityEffect { * a {@link forge.card.spellability.SpellAbility} object. */ private static void changeKnownOriginResolve(final SpellAbility sa) { - ArrayList tgtCards; - ArrayList sas; + List tgtCards; + List sas; final Target tgt = sa.getTarget(); final Player player = sa.getActivatingPlayer(); @@ -552,7 +552,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final Target tgt = sa.getTarget(); if (tgt != null) { - final ArrayList players = tgt.getTargetPlayers(); + final List players = tgt.getTargetPlayers(); player = player != null ? player : players.get(0); if (players.contains(player) && !player.canBeTargetedBy(sa)) { return; diff --git a/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java b/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java index 5f63b87e90b..bca48c00ea9 100644 --- a/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java +++ b/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java @@ -1,6 +1,6 @@ package forge.card.ability.effects; -import java.util.ArrayList; +import java.util.List; import forge.Card; import forge.Singletons; @@ -21,7 +21,7 @@ public class ControlExchangeEffect extends SpellAbilityEffect { Card object1 = null; Card object2 = null; final Target tgt = sa.getTarget(); - ArrayList tgts = tgt.getTargetCards(); + List tgts = tgt.getTargetCards(); if (tgts.size() > 0) { object1 = tgts.get(0); } @@ -42,7 +42,7 @@ public class ControlExchangeEffect extends SpellAbilityEffect { Card object1 = null; Card object2 = null; final Target tgt = sa.getTarget(); - ArrayList tgts = tgt.getTargetCards(); + List tgts = tgt.getTargetCards(); if (tgts.size() > 0) { object1 = tgts.get(0); } diff --git a/src/main/java/forge/card/ability/effects/DestroyAllEffect.java b/src/main/java/forge/card/ability/effects/DestroyAllEffect.java index a184464791d..43297ef91c5 100644 --- a/src/main/java/forge/card/ability/effects/DestroyAllEffect.java +++ b/src/main/java/forge/card/ability/effects/DestroyAllEffect.java @@ -21,7 +21,7 @@ public class DestroyAllEffect extends SpellAbilityEffect { final StringBuilder sb = new StringBuilder(); final boolean noRegen = sa.hasParam("NoRegen"); - ArrayList tgtCards; + List tgtCards; final Target tgt = sa.getTarget(); if (tgt != null) { diff --git a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java index 5e2ca066ba2..6ed71f5c20c 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java @@ -34,7 +34,6 @@ import forge.CardPredicates.Presets; import forge.Command; import forge.CounterType; import forge.Singletons; -import forge.card.ability.AbilityFactory; import forge.card.cost.Cost; import forge.card.mana.ManaCost; import forge.card.spellability.Ability; diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index a5eff8cf730..3ded88f444a 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -2919,7 +2919,7 @@ public class CardFactoryUtil { } if (card.getController().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(card.getController(), origSA, false); + game.getActionPlay().playSpellAbilityNoStack(card.getController(), origSA); } else { ComputerUtil.playNoStack((AIPlayer) card.getController(), origSA, game); } diff --git a/src/main/java/forge/card/cost/CostTapType.java b/src/main/java/forge/card/cost/CostTapType.java index 7c1eb7e37df..b398e4ae07e 100644 --- a/src/main/java/forge/card/cost/CostTapType.java +++ b/src/main/java/forge/card/cost/CostTapType.java @@ -24,7 +24,6 @@ import com.google.common.base.Predicate; import forge.Card; import forge.CardLists; -import forge.CardPredicates; import forge.CardPredicates.Presets; import forge.FThreads; import forge.card.ability.AbilityUtils; diff --git a/src/main/java/forge/card/replacement/ReplacementHandler.java b/src/main/java/forge/card/replacement/ReplacementHandler.java index 3fc979f6080..7da37539dbc 100644 --- a/src/main/java/forge/card/replacement/ReplacementHandler.java +++ b/src/main/java/forge/card/replacement/ReplacementHandler.java @@ -239,7 +239,7 @@ public class ReplacementHandler { Player player = replacementEffect.getHostCard().getController(); //player.getController().playNoStack() if (player.isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(player, effectSA, false); + game.getActionPlay().playSpellAbilityNoStack(player, effectSA); } else { ComputerUtil.playNoStack((AIPlayer) player, effectSA, game); } diff --git a/src/main/java/forge/card/spellability/SpellAbility.java b/src/main/java/forge/card/spellability/SpellAbility.java index 9988f964f09..e3e27b0fe20 100644 --- a/src/main/java/forge/card/spellability/SpellAbility.java +++ b/src/main/java/forge/card/spellability/SpellAbility.java @@ -1053,7 +1053,7 @@ public abstract class SpellAbility implements ISpellAbility { if (this.targetCard == null) { final Target tgt = this.getTarget(); if (tgt != null) { - final ArrayList list = tgt.getTargetCards(); + final List list = tgt.getTargetCards(); if (!list.isEmpty()) { return list.get(0); @@ -1106,7 +1106,7 @@ public abstract class SpellAbility implements ISpellAbility { public Player getTargetPlayer() { final Target tgt = this.getTarget(); if (tgt != null) { - final ArrayList list = tgt.getTargetPlayers(); + final List list = tgt.getTargetPlayers(); if (!list.isEmpty()) { return list.get(0); @@ -1313,7 +1313,7 @@ public abstract class SpellAbility implements ISpellAbility { return false; } if (entity.isValid(this.getTarget().getValidTgts(), this.getActivatingPlayer(), this.getSourceCard()) - && (!this.getTarget().isUniqueTargets() || !TargetSelection.getUniqueTargets(this).contains(entity)) + && (!this.getTarget().isUniqueTargets() || !TargetChooser.getUniqueTargets(this).contains(entity)) && entity.canBeTargetedBy(this)) { return true; } @@ -1494,23 +1494,22 @@ public abstract class SpellAbility implements ISpellAbility { * * @return a {@link forge.card.spellability.SpellAbility} object. */ - public ArrayList findTargetedCards() { - - ArrayList list = new ArrayList(); + public List findTargetedCards() { Target tgt = this.getTarget(); // First search for targeted cards associated with current ability if (tgt != null && tgt.getTargetCards() != null && !tgt.getTargetCards().isEmpty()) { return tgt.getTargetCards(); } + List list = new ArrayList(); // Next search for source cards of targeted SAs associated with current ability - else if (tgt != null && tgt.getTargetSAs() != null && !tgt.getTargetSAs().isEmpty()) { + if (tgt != null && tgt.getTargetSAs() != null && !tgt.getTargetSAs().isEmpty()) { for (final SpellAbility ability : tgt.getTargetSAs()) { list.add(ability.getSourceCard()); } return list; } // Lastly Search parent SAs for target cards - else { + // Check for a parent that targets a card SpellAbility parent = this.getParentTargetingCard(); if (null != parent) { @@ -1523,7 +1522,7 @@ public abstract class SpellAbility implements ISpellAbility { list.add(ability.getSourceCard()); } } - } + return list; } diff --git a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java index 731d81a1f8a..98e88306ea3 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java +++ b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java @@ -38,48 +38,36 @@ import forge.game.zone.Zone; * @version $Id$ */ public class SpellAbilityRequirements { - private SpellAbility ability = null; - private TargetSelection select = null; - private CostPayment payment = null; - private boolean isFree = false; + private final SpellAbility ability; + private final TargetChooser select; + private final CostPayment payment; + private boolean isFree; private boolean skipStack = false; - private boolean bCasting = false; - private Zone fromZone = null; - private Integer zonePosition = null; + private boolean isAlreadyTargeted = false; - - public final void setSkipStack(final boolean bSkip) { - this.skipStack = bSkip; - } - - public final void setFree(final boolean bFree) { - this.isFree = bFree; - } + public void setAlreadyTargeted() { isAlreadyTargeted = true; } + public final void setSkipStack() { this.skipStack = true; } + public void setFree() { this.isFree = true; } - - public SpellAbilityRequirements(final SpellAbility sa, final TargetSelection ts, final CostPayment cp) { + public SpellAbilityRequirements(final SpellAbility sa, final CostPayment cp) { this.ability = sa; - this.select = ts; + this.select = new TargetChooser(sa); this.payment = cp; } + public final void fillRequirements() { - this.fillRequirements(false); - } - - public final void fillRequirements(final boolean skipTargeting) { final GameState game = Singletons.getModel().getGame(); - - if ((this.ability instanceof Spell) && !this.bCasting) { - // remove from hand - this.bCasting = true; - if (!this.ability.getSourceCard().isCopiedSpell()) { - final Card c = this.ability.getSourceCard(); - this.fromZone = game.getZoneOf(c); - this.zonePosition = this.fromZone.getPosition(c); - this.ability.setSourceCard(game.getAction().moveToStack(c)); - } + // used to rollback + Zone fromZone = null; + int zonePosition = 0; + + final Card c = this.ability.getSourceCard(); + if (this.ability instanceof Spell && !c.isCopiedSpell()) { + fromZone = game.getZoneOf(c); + zonePosition = fromZone.getPosition(c); + this.ability.setSourceCard(game.getAction().moveToStack(c)); } // freeze Stack. No abilities should go onto the stack while I'm filling requirements. @@ -88,19 +76,19 @@ public class SpellAbilityRequirements { // Announce things like how many times you want to Multikick or the value of X if (!this.announceRequirements()) { this.select.setCancel(true); - rollbackAbility(); + rollbackAbility(fromZone, zonePosition); return; } // Skip to paying if parent ability doesn't target and has no // subAbilities. // (or trigger case where its already targeted) - if (!skipTargeting && (this.select.doesTarget() || (this.ability.getSubAbility() != null))) { - this.select.setRequirements(this); + boolean acceptsTargets = this.select.doesTarget() || this.ability.getSubAbility() != null; + if (!isAlreadyTargeted && acceptsTargets) { this.select.clearTargets(); this.select.chooseTargets(); if (this.select.isCanceled()) { - rollbackAbility(); + rollbackAbility(fromZone, zonePosition); return; } } @@ -114,7 +102,7 @@ public class SpellAbilityRequirements { } if (!paymentMade) { - rollbackAbility(); + rollbackAbility(fromZone, zonePosition); return; } @@ -134,7 +122,7 @@ public class SpellAbilityRequirements { } } - private void rollbackAbility() { + private void rollbackAbility(Zone fromZone, int zonePosition) { // cancel ability during target choosing final Card c = this.ability.getSourceCard(); @@ -143,9 +131,9 @@ public class SpellAbilityRequirements { c.setState(CardCharacteristicName.Original); } - if (this.bCasting && !c.isCopiedSpell()) { // and not a copy + if (fromZone != null) { // and not a copy // add back to where it came from - Singletons.getModel().getGame().getAction().moveTo(this.fromZone, c, this.zonePosition); + Singletons.getModel().getGame().getAction().moveTo(fromZone, c, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); } if (this.select != null) { diff --git a/src/main/java/forge/card/spellability/Target.java b/src/main/java/forge/card/spellability/Target.java index c0b835643cd..99dc9117bbf 100644 --- a/src/main/java/forge/card/spellability/Target.java +++ b/src/main/java/forge/card/spellability/Target.java @@ -464,7 +464,7 @@ public class Target { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetCards() { + public final List getTargetCards() { if (this.choice == null) { return new ArrayList(); } @@ -479,7 +479,7 @@ public class Target { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetPlayers() { + public final List getTargetPlayers() { if (this.choice == null) { return new ArrayList(); } @@ -494,7 +494,7 @@ public class Target { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetSAs() { + public final List getTargetSAs() { if (this.choice == null) { return new ArrayList(); } diff --git a/src/main/java/forge/card/spellability/TargetChoices.java b/src/main/java/forge/card/spellability/TargetChoices.java index 1167673b280..62e5f7ba9a3 100644 --- a/src/main/java/forge/card/spellability/TargetChoices.java +++ b/src/main/java/forge/card/spellability/TargetChoices.java @@ -18,6 +18,7 @@ package forge.card.spellability; import java.util.ArrayList; +import java.util.List; import forge.Card; import forge.game.player.Player; @@ -45,9 +46,9 @@ public class TargetChoices { } // Card or Player are legal targets. - private final ArrayList targetCards = new ArrayList(); - private final ArrayList targetPlayers = new ArrayList(); - private final ArrayList targetSAs = new ArrayList(); + private final List targetCards = new ArrayList(); + private final List targetPlayers = new ArrayList(); + private final List targetSAs = new ArrayList(); /** *

@@ -199,7 +200,7 @@ public class TargetChoices { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetCards() { + public final List getTargetCards() { return this.targetCards; } @@ -210,7 +211,7 @@ public class TargetChoices { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetPlayers() { + public final List getTargetPlayers() { return this.targetPlayers; } @@ -221,7 +222,7 @@ public class TargetChoices { * * @return a {@link java.util.ArrayList} object. */ - public final ArrayList getTargetSAs() { + public final List getTargetSAs() { return this.targetSAs; } diff --git a/src/main/java/forge/card/spellability/TargetSelection.java b/src/main/java/forge/card/spellability/TargetChooser.java similarity index 53% rename from src/main/java/forge/card/spellability/TargetSelection.java rename to src/main/java/forge/card/spellability/TargetChooser.java index 4ef190d8fa2..9eab1736b11 100644 --- a/src/main/java/forge/card/spellability/TargetSelection.java +++ b/src/main/java/forge/card/spellability/TargetChooser.java @@ -20,20 +20,23 @@ package forge.card.spellability; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + import com.google.common.base.Predicate; import forge.Card; import forge.CardLists; import forge.FThreads; -import forge.Singletons; +import forge.GameEntity; import forge.card.ability.AbilityUtils; import forge.card.ability.ApiType; -import forge.control.input.InputSynchronized; import forge.control.input.InputSyncronizedBase; +import forge.game.GameState; import forge.game.player.Player; +import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; -import forge.gui.match.CMatchUI; import forge.view.ButtonUtil; /** @@ -44,21 +47,24 @@ import forge.view.ButtonUtil; * @author Forge * @version $Id$ */ -public class TargetSelection { +public class TargetChooser { /** * TODO: Write javadoc for this type. * */ - public final class InputSelectTargets extends InputSyncronizedBase { - private final TargetSelection select; + public static final class InputSelectTargets extends InputSyncronizedBase { private final List choices; - private final ArrayList alreadyTargeted; - private final boolean targeted; + // some cards can be targeted several times (eg: distribute damage as you choose) + private final Map targetDepth = new HashMap(); private final Target tgt; private final SpellAbility sa; + private boolean bCancel = false; + private boolean bOk = false; private final boolean mandatory; private static final long serialVersionUID = -1091595663541356356L; + public final boolean hasCancelled() { return bCancel; } + public final boolean hasPressedOk() { return bOk; } /** * TODO: Write javadoc for Constructor. * @param select @@ -70,13 +76,10 @@ public class TargetSelection { * @param sa * @param mandatory */ - public InputSelectTargets(TargetSelection select, List choices, ArrayList alreadyTargeted, boolean targeted, Target tgt, SpellAbility sa, boolean mandatory) { + public InputSelectTargets(List choices, SpellAbility sa, boolean mandatory) { super(sa.getActivatingPlayer()); - this.select = select; this.choices = choices; - this.alreadyTargeted = alreadyTargeted; - this.targeted = targeted; - this.tgt = tgt; + this.tgt = sa.getTarget(); this.sa = sa; this.mandatory = mandatory; } @@ -84,12 +87,14 @@ public class TargetSelection { @Override public void showMessage() { final StringBuilder sb = new StringBuilder(); - sb.append("Targeted: "); - for (final Object o : alreadyTargeted) { - sb.append(o).append(" "); + sb.append("Targeted:\n"); + for (final Entry o : targetDepth.entrySet()) { + sb.append(o.getKey()); + if( o.getValue() > 1 ) + sb.append(" (").append(o.getValue()).append(" times)"); + sb.append("\n"); } - sb.append(tgt.getTargetedString()); - sb.append("\n"); + //sb.append(tgt.getTargetedString()).append("\n"); sb.append(tgt.getVTSelection()); showMessage(sb.toString()); @@ -114,110 +119,147 @@ public class TargetSelection { @Override public void selectButtonCancel() { - select.setCancel(true); + bCancel = true; this.done(); } @Override public void selectButtonOK() { + bOk = true; this.done(); } @Override public void selectCard(final Card card) { - // leave this in temporarily, there some seriously wrong things - // going on here - if (targeted && !card.canBeTargetedBy(sa)) { - CMatchUI.SINGLETON_INSTANCE.showMessage("Cannot target this card (Shroud? Protection? Restrictions?)."); - } else if (choices.contains(card)) { - if (tgt.isDividedAsYouChoose()) { - final int stillToDivide = tgt.getStillToDivide(); - int allocatedPortion = 0; - // allow allocation only if the max targets isn't reached and there are more candidates - if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) - && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { - final Integer[] choices = new Integer[stillToDivide]; - for (int i = 1; i <= stillToDivide; i++) { - choices[i - 1] = i; - } - String apiBasedMessage = "Distribute how much to "; - if (sa.getApi() == ApiType.DealDamage) { - apiBasedMessage = "Select how much damage to deal to "; - } else if (sa.getApi() == ApiType.PreventDamage) { - apiBasedMessage = "Select how much damage to prevent to "; - } else if (sa.getApi() == ApiType.PutCounter) { - apiBasedMessage = "Select how many counters to distribute to "; - } - final StringBuilder sb = new StringBuilder(); - sb.append(apiBasedMessage); - sb.append(card.toString()); - Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); - if (null == chosen) { - return; - } - allocatedPortion = chosen; - } else { // otherwise assign the rest of the damage/protection - allocatedPortion = stillToDivide; - } - tgt.setStillToDivide(stillToDivide - allocatedPortion); - tgt.addDividedAllocation(card, allocatedPortion); - } - tgt.addTarget(card); - this.done(); + if (!tgt.isUniqueTargets() && targetDepth.containsKey(card)) { + return; } + + // leave this in temporarily, there some seriously wrong things going on here + if (!card.canBeTargetedBy(sa)) { + showMessage("Cannot target this card (Shroud? Protection? Restrictions?)."); + return; + } + if (!choices.contains(card)) { + showMessage("This card is not a valid choice for some other reason besides (Shroud? Protection? Restrictions?)."); + return; + } + + if (tgt.isDividedAsYouChoose()) { + final int stillToDivide = tgt.getStillToDivide(); + int allocatedPortion = 0; + // allow allocation only if the max targets isn't reached and there are more candidates + if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) + && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { + final Integer[] choices = new Integer[stillToDivide]; + for (int i = 1; i <= stillToDivide; i++) { + choices[i - 1] = i; + } + String apiBasedMessage = "Distribute how much to "; + if (sa.getApi() == ApiType.DealDamage) { + apiBasedMessage = "Select how much damage to deal to "; + } else if (sa.getApi() == ApiType.PreventDamage) { + apiBasedMessage = "Select how much damage to prevent to "; + } else if (sa.getApi() == ApiType.PutCounter) { + apiBasedMessage = "Select how many counters to distribute to "; + } + final StringBuilder sb = new StringBuilder(); + sb.append(apiBasedMessage); + sb.append(card.toString()); + Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); + if (null == chosen) { + return; + } + allocatedPortion = chosen; + } else { // otherwise assign the rest of the damage/protection + allocatedPortion = stillToDivide; + } + tgt.setStillToDivide(stillToDivide - allocatedPortion); + tgt.addDividedAllocation(card, allocatedPortion); + } + addTarget(card); } // selectCard() @Override public void selectPlayer(final Player player) { - if (alreadyTargeted.contains(player)) { + if (!tgt.isUniqueTargets() && targetDepth.containsKey(player)) { return; } - if (sa.canTarget(player)) { - if (tgt.isDividedAsYouChoose()) { - final int stillToDivide = tgt.getStillToDivide(); - int allocatedPortion = 0; - // allow allocation only if the max targets isn't reached and there are more candidates - if ((alreadyTargeted.size() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) - && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { - final Integer[] choices = new Integer[stillToDivide]; - for (int i = 1; i <= stillToDivide; i++) { - choices[i - 1] = i; - } - String apiBasedMessage = "Distribute how much to "; - if (sa.getApi() == ApiType.DealDamage) { - apiBasedMessage = "Select how much damage to deal to "; - } else if (sa.getApi() == ApiType.PreventDamage) { - apiBasedMessage = "Select how much damage to prevent to "; - } - final StringBuilder sb = new StringBuilder(); - sb.append(apiBasedMessage); - sb.append(player.getName()); - Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); - if (null == chosen) { - return; - } - allocatedPortion = chosen; - } else { // otherwise assign the rest of the damage/protection - allocatedPortion = stillToDivide; - } - tgt.setStillToDivide(stillToDivide - allocatedPortion); - tgt.addDividedAllocation(player, allocatedPortion); - } - tgt.addTarget(player); - this.done(); + if (!sa.canTarget(player)) { + showMessage("Cannot target this player (Hexproof? Protection? Restrictions?)."); + return; } + + if (tgt.isDividedAsYouChoose()) { + final int stillToDivide = tgt.getStillToDivide(); + int allocatedPortion = 0; + // allow allocation only if the max targets isn't reached and there are more candidates + if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { + final Integer[] choices = new Integer[stillToDivide]; + for (int i = 1; i <= stillToDivide; i++) { + choices[i - 1] = i; + } + String apiBasedMessage = "Distribute how much to "; + if (sa.getApi() == ApiType.DealDamage) { + apiBasedMessage = "Select how much damage to deal to "; + } else if (sa.getApi() == ApiType.PreventDamage) { + apiBasedMessage = "Select how much damage to prevent to "; + } + final StringBuilder sb = new StringBuilder(); + sb.append(apiBasedMessage); + sb.append(player.getName()); + Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); + if (null == chosen) { + return; + } + allocatedPortion = chosen; + } else { // otherwise assign the rest of the damage/protection + allocatedPortion = stillToDivide; + } + tgt.setStillToDivide(stillToDivide - allocatedPortion); + tgt.addDividedAllocation(player, allocatedPortion); + } + addTarget(player); } + void addTarget(GameEntity ge) { + tgt.addTarget(ge); + Integer val = targetDepth.get(ge); + targetDepth.put(ge, val == null ? Integer.valueOf(1) : Integer.valueOf(val.intValue() + 1) ); + + if(hasAllTargets()) { + bOk = true; + this.done(); + } + else + this.showMessage(); + } + void done() { this.stop(); } + + boolean hasAllTargets() { + return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa); + } } - private final Target target; private final SpellAbility ability; - private TargetSelection subSelection = null; + /** + *

+ * Constructor for Target_Selection. + *

+ * + * @param tgt + * a {@link forge.card.spellability.Target} object. + * @param sa + * a {@link forge.card.spellability.SpellAbility} object. + */ + public TargetChooser(final SpellAbility sa) { + this.ability = sa; + } /** *

@@ -227,7 +269,7 @@ public class TargetSelection { * @return a {@link forge.card.spellability.Target} object. */ public final Target getTgt() { - return this.target; + return this.ability.getTarget(); } /** @@ -252,20 +294,7 @@ public class TargetSelection { return this.ability.getSourceCard(); } - private SpellAbilityRequirements req = null; - - /** - *

- * setRequirements. - *

- * - * @param reqs - * a {@link forge.card.spellability.SpellAbilityRequirements} - * object. - */ - public final void setRequirements(final SpellAbilityRequirements reqs) { - this.req = reqs; - } + private TargetChooser subSelection = null; private boolean bCancel = false; private boolean bTargetingDone = false; @@ -290,44 +319,12 @@ public class TargetSelection { * @return a boolean. */ public final boolean isCanceled() { - if (this.bCancel) { - return this.bCancel; - } - - if (this.subSelection == null) { - return false; - } - - return this.subSelection.isCanceled(); + return this.bCancel || this.subSelection != null && this.subSelection.isCanceled(); } - /** - *

- * Constructor for Target_Selection. - *

- * - * @param tgt - * a {@link forge.card.spellability.Target} object. - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - */ - public TargetSelection(final Target tgt, final SpellAbility sa) { - this.target = tgt; - this.ability = sa; - } - - /** - *

- * doesTarget. - *

- * - * @return a boolean. - */ public final boolean doesTarget() { - if (this.target == null) { - return false; - } - return this.target.doesTarget(); + Target tg = getTgt(); + return tg != null && tg.doesTarget(); } /** @@ -336,55 +333,65 @@ public class TargetSelection { *

*/ public final void clearTargets() { - if (this.target != null) { - this.target.resetTargets(); - this.target.calculateStillToDivide(this.ability.getParam("DividedAsYouChoose"), this.getCard(), this.ability); + Target tg = getTgt(); + if (tg != null) { + tg.resetTargets(); + tg.calculateStillToDivide(this.ability.getParam("DividedAsYouChoose"), this.getCard(), this.ability); } } - /** - *

- * chooseTargets. - *

- * - * @return a boolean. - */ public final boolean chooseTargets() { + Target tgt = getTgt(); + final boolean canTarget = tgt.doesTarget(); + final int minTargets = canTarget ? tgt.getMinTargets(getCard(), ability) : 0; + final int maxTargets = canTarget ? tgt.getMaxTargets(getCard(), ability) : 0; + final int numTargeted = canTarget ? tgt.getNumTargeted() : 0; + + boolean hasEnoughTargets = minTargets == 0 || numTargeted >= minTargets; + boolean hasAllTargets = numTargeted == maxTargets && maxTargets > 0; + // if not enough targets chosen, reset and cancel Ability - if (this.bCancel || (this.bTargetingDone && !this.target.isMinTargetsChosen(this.getCard(), this.ability))) { - this.bCancel = true; - return false; - } + if (this.bTargetingDone && !hasEnoughTargets) this.bCancel = true; + if (this.bCancel) return false; + - if (!this.doesTarget() - || this.bTargetingDone && this.target.isMinTargetsChosen(this.getCard(), this.ability) - || this.target.isMaxTargetsChosen(this.getCard(), this.ability) - || this.target.isDividedAsYouChoose() && this.target.getStillToDivide() == 0) { + if (!canTarget || this.bTargetingDone && hasEnoughTargets || hasAllTargets || tgt.isDividedAsYouChoose() && tgt.getStillToDivide() == 0) { final AbilitySub abSub = this.ability.getSubAbility(); - - if (abSub == null) { - // if no more SubAbilities finish targeting + if (abSub == null) // if no more SubAbilities finish targeting return true; - } else { - // Has Sub Ability - this.subSelection = new TargetSelection(abSub.getTarget(), abSub); - this.subSelection.setRequirements(this.req); - this.subSelection.clearTargets(); - return this.subSelection.chooseTargets(); - } + + // Has Sub Ability + this.subSelection = new TargetChooser(abSub); + this.subSelection.clearTargets(); + return this.subSelection.chooseTargets(); } - if (!this.target.hasCandidates(this.ability, true) && !this.target.isMinTargetsChosen(this.getCard(), this.ability)) { + if (!tgt.hasCandidates(this.ability, true) && !hasEnoughTargets) { // Cancel ability if there aren't any valid Candidates this.bCancel = true; return false; } - - this.chooseValidInput(); - if ( !bCancel ) - return chooseTargets(); - - return false; + + final List zone = tgt.getZone(); + final boolean mandatory = tgt.getMandatory() && tgt.hasCandidates(this.ability, true); + + if (zone.size() == 1 && zone.get(0) == ZoneType.Stack) { + // If Zone is Stack, the choices are handled slightly differently + this.chooseCardFromStack(mandatory); + } else { + List validTargets = this.chooseValidInput(); + if (zone.size() == 1 && zone.get(0) == ZoneType.Battlefield) { + InputSelectTargets inp = new InputSelectTargets(validTargets, ability, mandatory); + FThreads.setInputAndWait(inp); + bCancel = inp.hasCancelled(); + bTargetingDone = inp.hasPressedOk(); + } else { + this.chooseCardFromList(validTargets, true, mandatory); + } + } + // some inputs choose cards 1-by-1 and need to be called again, + // moreover there are sub-abilities that also need targets + return chooseTargets(); } /** @@ -416,21 +423,15 @@ public class TargetSelection { *

* chooseValidInput. *

+ * @return */ - public final void chooseValidInput() { + public final List chooseValidInput() { final Target tgt = this.getTgt(); + final GameState game = ability.getActivatingPlayer().getGame(); final List zone = tgt.getZone(); - final boolean mandatory = this.target.getMandatory() ? this.target.hasCandidates(this.ability, true) : false; final boolean canTgtStack = zone.contains(ZoneType.Stack); - - if (canTgtStack && (zone.size() == 1)) { - // If Zone is Stack, the choices are handled slightly differently - this.chooseCardFromStack(mandatory); - return; - } - - List choices = CardLists.getTargetableCards(CardLists.getValidCards(Singletons.getModel().getGame().getCardsIn(zone), this.target.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()), this.ability); + List choices = CardLists.getTargetableCards(CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()), this.ability); if (canTgtStack) { // Since getTargetableCards doesn't have additional checks if one of the Zones is stack // Remove the activating card from targeting itself if its on the Stack @@ -450,7 +451,7 @@ public class TargetSelection { } // Remove cards already targeted - final ArrayList targeted = tgt.getTargetCards(); + final List targeted = tgt.getTargetCards(); for (final Card c : targeted) { if (choices.contains(c)) { choices.remove(c); @@ -486,7 +487,7 @@ public class TargetSelection { } // If all cards must have different controllers if (tgt.isDifferentControllers() && !targeted.isEmpty()) { - final List availableControllers = new ArrayList(Singletons.getModel().getGame().getPlayers()); + final List availableControllers = new ArrayList(game.getPlayers()); for (int i = 0; i < targeted.size(); i++) { availableControllers.remove(targeted.get(i).getController()); } @@ -512,20 +513,7 @@ public class TargetSelection { choices.clear(); } } - - if (!tgt.isUniqueTargets()) { - // Previously targeted objects needed to be used for same controller above, but causes problems - // if passed through with certain card functionality to inputTargetSpecific so resetting now - objects = new ArrayList(); - } - - if (zone.contains(ZoneType.Battlefield) && zone.size() == 1) { - InputSynchronized inp = new InputSelectTargets(this, choices, objects, true, this.target, this.ability, mandatory); - FThreads.setInputAndWait(inp); - bTargetingDone = !bCancel; - } else { - this.chooseCardFromList(choices, true, mandatory); - } + return choices; } // input_targetValid /** @@ -540,92 +528,63 @@ public class TargetSelection { * @param mandatory * a boolean. */ - public final void chooseCardFromList(final List choices, final boolean targeted, final boolean mandatory) { + private final void chooseCardFromList(final List choices, final boolean targeted, final boolean mandatory) { // Send in a list of valid cards, and popup a choice box to target - final Card dummy = new Card(); - dummy.setName("[FINISH TARGETING]"); - final SpellAbility sa = this.ability; - final String message = this.target.getVTSelection(); + final GameState game = ability.getActivatingPlayer().getGame(); - final Card divBattlefield = new Card(); - divBattlefield.setName("--CARDS ON BATTLEFIELD:--"); - final Card divExile = new Card(); - divExile.setName("--CARDS IN EXILE:--"); - final Card divGrave = new Card(); - divGrave.setName("--CARDS IN GRAVEYARD:--"); - final Card divLibrary = new Card(); - divLibrary.setName("--CARDS IN LIBRARY:--"); - final Card divStack = new Card(); - divStack.setName("--CARDS IN STACK:--"); - - List choicesZoneUnfiltered = choices; final List crdsBattle = new ArrayList(); final List crdsExile = new ArrayList(); final List crdsGrave = new ArrayList(); final List crdsLibrary = new ArrayList(); final List crdsStack = new ArrayList(); - for (final Card inZone : choicesZoneUnfiltered) { - if (Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield).contains(inZone)) { - crdsBattle.add(inZone); - } else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Exile).contains(inZone)) { - crdsExile.add(inZone); - } else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Graveyard).contains(inZone)) { - crdsGrave.add(inZone); - } else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Library).contains(inZone)) { - crdsLibrary.add(inZone); - } else if (Singletons.getModel().getGame().getCardsIn(ZoneType.Stack).contains(inZone)) { - crdsStack.add(inZone); - } + for (final Card inZone : choices) { + Zone zz = game.getZoneOf(inZone); + if (zz.is(ZoneType.Battlefield)) crdsBattle.add(inZone); + else if (zz.is(ZoneType.Exile)) crdsExile.add(inZone); + else if (zz.is(ZoneType.Graveyard)) crdsGrave.add(inZone); + else if (zz.is(ZoneType.Library)) crdsLibrary.add(inZone); + else if (zz.is(ZoneType.Stack)) crdsStack.add(inZone); } - List choicesFiltered = new ArrayList(); - if (crdsBattle.size() >= 1) { - choicesFiltered.add(divBattlefield); + List choicesFiltered = new ArrayList(); + if (!crdsBattle.isEmpty()) { + choicesFiltered.add("--CARDS ON BATTLEFIELD:--"); choicesFiltered.addAll(crdsBattle); - crdsBattle.clear(); } - if (crdsExile.size() >= 1) { - choicesFiltered.add(divExile); + if (!crdsExile.isEmpty()) { + choicesFiltered.add("--CARDS IN EXILE:--"); choicesFiltered.addAll(crdsExile); - crdsExile.clear(); } - if (crdsGrave.size() >= 1) { - choicesFiltered.add(divGrave); + if (!crdsGrave.isEmpty()) { + choicesFiltered.add("--CARDS IN GRAVEYARD:--"); choicesFiltered.addAll(crdsGrave); - crdsGrave.clear(); } - if (crdsLibrary.size() >= 1) { - choicesFiltered.add(divLibrary); + if (!crdsLibrary.isEmpty()) { + choicesFiltered.add("--CARDS IN LIBRARY:--"); choicesFiltered.addAll(crdsLibrary); - crdsLibrary.clear(); } - if (crdsStack.size() >= 1) { - choicesFiltered.add(divStack); + if (!crdsStack.isEmpty()) { + choicesFiltered.add("--CARDS IN STACK:--"); choicesFiltered.addAll(crdsStack); - crdsStack.clear(); } - final Target tgt = this.getTgt(); - - final List choicesWithDone = choicesFiltered; - if (tgt.isMinTargetsChosen(sa.getSourceCard(), sa)) { + final String msgDone = "[FINISH TARGETING]"; + if (this.getTgt().isMinTargetsChosen(this.ability.getSourceCard(), this.ability)) { // is there a more elegant way of doing this? - choicesWithDone.add(dummy); + choicesFiltered.add(msgDone); } - - final Card check = GuiChoose.oneOrNone(message, choicesWithDone); - if (check != null) { - final Card c = check; - if (!c.equals(divBattlefield) && !c.equals(divExile) && !c.equals(divGrave) - && !c.equals(divLibrary) && !c.equals(divStack)) { - if (c.equals(dummy)) { - bTargetingDone = true; - } else { - tgt.addTarget(c); - } - } - } else { + + final Object chosen = GuiChoose.oneOrNone(getTgt().getVTSelection(), choicesFiltered); + if (chosen == null) { this.setCancel(true); + return; } + if (msgDone.equals(chosen)) { + bTargetingDone = true; + return; + } + + if (chosen instanceof Card ) + this.getTgt().addTarget(chosen); } /** @@ -636,14 +595,13 @@ public class TargetSelection { * @param mandatory * a boolean. */ - public final void chooseCardFromStack(final boolean mandatory) { - final Target tgt = this.target; + private final void chooseCardFromStack(final boolean mandatory) { + final Target tgt = this.getTgt(); final String message = tgt.getVTSelection(); - final TargetSelection select = this; final String doneDummy = "[FINISH TARGETING]"; // Find what's targetable, then allow human to choose - final ArrayList choosables = TargetSelection.getTargetableOnStack(this.ability, select.getTgt()); + final ArrayList choosables = getTargetableOnStack(); final HashMap map = new HashMap(); @@ -657,14 +615,11 @@ public class TargetSelection { map.put(doneDummy, null); } - String[] choices = new String[map.keySet().size()]; - choices = map.keySet().toArray(choices); - if (choices.length == 0) { - select.setCancel(true); + if (map.isEmpty()) { + setCancel(true); } else { - final String madeChoice = GuiChoose.oneOrNone(message, choices); - + final String madeChoice = GuiChoose.oneOrNone(message, map.keySet()); if (madeChoice != null) { if (madeChoice.equals(doneDummy)) { bTargetingDone = true; @@ -672,7 +627,7 @@ public class TargetSelection { tgt.addTarget(map.get(madeChoice)); } } else { - select.setCancel(true); + setCancel(true); } } } @@ -691,15 +646,16 @@ public class TargetSelection { * a {@link forge.card.spellability.Target} object. * @return a {@link java.util.ArrayList} object. */ - public static ArrayList getTargetableOnStack(final SpellAbility sa, final Target tgt) { + private ArrayList getTargetableOnStack() { final ArrayList choosables = new ArrayList(); - for (int i = 0; i < Singletons.getModel().getGame().getStack().size(); i++) { - choosables.add(Singletons.getModel().getGame().getStack().peekAbility(i)); + final GameState game = ability.getActivatingPlayer().getGame(); + for (int i = 0; i < game.getStack().size(); i++) { + choosables.add(game.getStack().peekAbility(i)); } for (int i = 0; i < choosables.size(); i++) { - if (!TargetSelection.matchSpellAbility(sa, choosables.get(i), tgt)) { + if (!TargetChooser.matchSpellAbility(ability, choosables.get(i), getTgt())) { choosables.remove(i); } } @@ -753,7 +709,7 @@ public class TargetSelection { boolean result = false; for (final Object o : matchTgt.getTargets()) { - if (TargetSelection.matchesValid(o, splitTargetRestrictions.split(","), sa)) { + if (TargetChooser.matchesValid(o, splitTargetRestrictions.split(","), sa)) { result = true; break; } @@ -764,26 +720,10 @@ public class TargetSelection { } } - if (!TargetSelection.matchesValidSA(topSA, tgt.getValidTgts(), sa)) { - return false; - } - - return true; + return topSA.getSourceCard().isValid(tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard()); } - /** - *

- * matchesValid. - *

- * - * @param o - * a {@link java.lang.Object} object. - * @param valids - * an array of {@link java.lang.String} objects. - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - * @return a boolean. - */ + private static boolean matchesValid(final Object o, final String[] valids, final SpellAbility sa) { final Card srcCard = sa.getSourceCard(); final Player activatingPlayer = sa.getActivatingPlayer(); @@ -801,24 +741,4 @@ public class TargetSelection { return false; } - - /** - *

- * matchesValidSA. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - * @param valids - * an array of {@link java.lang.String} objects. - * @param source - * a {@link forge.card.spellability.SpellAbility} object. - * @return a boolean. - */ - private static boolean matchesValidSA(final SpellAbility sa, final String[] valids, final SpellAbility source) { - final Card srcCard = source.getSourceCard(); - final Player activatingPlayer = source.getActivatingPlayer(); - final Card c = sa.getSourceCard(); - return c.isValid(valids, activatingPlayer, srcCard); - } } diff --git a/src/main/java/forge/card/trigger/TriggerHandler.java b/src/main/java/forge/card/trigger/TriggerHandler.java index 1fc800f7e29..ed81f1b4009 100644 --- a/src/main/java/forge/card/trigger/TriggerHandler.java +++ b/src/main/java/forge/card/trigger/TriggerHandler.java @@ -419,7 +419,7 @@ public class TriggerHandler { if (regtrig.isStatic()) { if (wrapperAbility.getActivatingPlayer().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility, false); + game.getActionPlay().playSpellAbilityNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility); } else { wrapperAbility.doTrigger(isMandatory, (AIPlayer)wrapperAbility.getActivatingPlayer()); ComputerUtil.playNoStack((AIPlayer)wrapperAbility.getActivatingPlayer(), wrapperAbility, game); diff --git a/src/main/java/forge/control/input/InputMulligan.java b/src/main/java/forge/control/input/InputMulligan.java index be6961b84b8..45be101462c 100644 --- a/src/main/java/forge/control/input/InputMulligan.java +++ b/src/main/java/forge/control/input/InputMulligan.java @@ -78,7 +78,7 @@ public class InputMulligan extends InputBase { } sb.append("Do you want to Mulligan?"); - CMatchUI.SINGLETON_INSTANCE.showMessage(sb.toString()); + showMessage(sb.toString()); } /** {@inheritDoc} */ @@ -130,7 +130,7 @@ public class InputMulligan extends InputBase { if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { // If we ever let the AI memorize cards in the players // hand, this would be a place to do so. - game.getActionPlay().playSpellAbilityNoStack(p, effect, false); + game.getActionPlay().playSpellAbilityNoStack(p, effect); } } } diff --git a/src/main/java/forge/game/GameActionPlay.java b/src/main/java/forge/game/GameActionPlay.java index f44b02dff81..d822d19a906 100644 --- a/src/main/java/forge/game/GameActionPlay.java +++ b/src/main/java/forge/game/GameActionPlay.java @@ -21,7 +21,6 @@ import forge.card.mana.ManaCostShard; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityRequirements; import forge.card.spellability.Target; -import forge.card.spellability.TargetSelection; import forge.card.staticability.StaticAbility; import forge.control.input.InputPayManaSimple; import forge.game.ai.ComputerUtilCard; @@ -73,11 +72,10 @@ public class GameActionPlay { if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { CharmEffect.makeChoices(sa); } - final TargetSelection ts = new TargetSelection(sa.getTarget(), sa); final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); - final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, ts, payment); - req.setFree(true); + final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment); + req.setFree(); req.fillRequirements(); } else { if (sa.isSpell()) { @@ -383,7 +381,6 @@ public class GameActionPlay { // System.out.println("Playing:" + sa.getDescription() + " of " + sa.getSourceCard() + " new = " + newAbility); if (newAbility) { - final TargetSelection ts = new TargetSelection(sa.getTarget(), sa); CostPayment payment = null; if (sa.getPayCosts() == null) { payment = new CostPayment(new Cost(sa.getSourceCard(), "0", sa.isAbility()), sa); @@ -391,7 +388,7 @@ public class GameActionPlay { payment = new CostPayment(sa.getPayCosts(), sa); } - final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, ts, payment); + final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment); req.fillRequirements(); } else { ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); @@ -426,20 +423,24 @@ public class GameActionPlay { * @param skipTargeting * a boolean. */ - public final void playSpellAbilityNoStack(final Player human, final SpellAbility sa, final boolean skipTargeting) { + public final void playSpellAbilityNoStack(final Player human, final SpellAbility sa) { + playSpellAbilityNoStack(human, sa, false); + } + public final void playSpellAbilityNoStack(final Player human, final SpellAbility sa, boolean useOldTargets) { sa.setActivatingPlayer(human); if (sa.getPayCosts() != null) { - final TargetSelection ts = new TargetSelection(sa.getTarget(), sa); final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); if (!sa.isTrigger()) { payment.changeCost(); } - final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, ts, payment); - req.setSkipStack(true); - req.fillRequirements(skipTargeting); + final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment); + if( useOldTargets ) + req.setAlreadyTargeted(); + req.setSkipStack(); + req.fillRequirements(); } else { ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index c22e38500dc..6dbd0b6ba8e 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -42,7 +42,7 @@ import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.Target; import forge.card.spellability.TargetChoices; -import forge.card.spellability.TargetSelection; +import forge.card.spellability.TargetChooser; import forge.card.trigger.Trigger; import forge.card.trigger.TriggerType; import forge.control.input.InputPayManaExecuteCommands; @@ -885,7 +885,7 @@ public class MagicStack extends MyObservable { } else if (o instanceof SpellAbility) { final SpellAbility tgtSA = (SpellAbility) o; - invalidTarget = !(TargetSelection.matchSpellAbility(sa, tgtSA, tgt)); + invalidTarget = !(TargetChooser.matchSpellAbility(sa, tgtSA, tgt)); // TODO Remove target? if (invalidTarget) { choices.removeTarget(tgtSA); diff --git a/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java b/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java index 0e001fade13..aecb26d2dc4 100644 --- a/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java +++ b/src/main/java/forge/gui/deckeditor/controllers/CEditorConstructed.java @@ -205,7 +205,6 @@ public final class CEditorConstructed extends ACEditorBase { /** * Switch between the main deck and the sideboard editor. */ - @SuppressWarnings("incomplete-switch") public void cycleEditorMode() { int curindex = allSections.indexOf(sectionMode); From 96d3f0c3e88122d0afba73b1b520708aed52e966 Mon Sep 17 00:00:00 2001 From: Hellfish Date: Mon, 1 Apr 2013 12:41:16 +0000 Subject: [PATCH 032/123] *Fixed a Soul Seizer crash. --- src/main/java/forge/card/spellability/TargetChooser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/forge/card/spellability/TargetChooser.java b/src/main/java/forge/card/spellability/TargetChooser.java index 9eab1736b11..343ba088d56 100644 --- a/src/main/java/forge/card/spellability/TargetChooser.java +++ b/src/main/java/forge/card/spellability/TargetChooser.java @@ -342,7 +342,7 @@ public class TargetChooser { public final boolean chooseTargets() { Target tgt = getTgt(); - final boolean canTarget = tgt.doesTarget(); + final boolean canTarget = tgt == null ? false : tgt.doesTarget(); final int minTargets = canTarget ? tgt.getMinTargets(getCard(), ability) : 0; final int maxTargets = canTarget ? tgt.getMaxTargets(getCard(), ability) : 0; final int numTargeted = canTarget ? tgt.getNumTargeted() : 0; From 26ca62653ca940180179966264f31dff5f2a8c29 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 1 Apr 2013 12:42:29 +0000 Subject: [PATCH 033/123] - Added new card names to changes.txt. --- CHANGES.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 4b63e80e122..19f8883cd99 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -64,6 +64,14 @@ Auriok Siege Sled Burning-Tree Bloodscale Soulbright Flamekin Koskun Falls +Warp World +Zur's Weirding +Vexing Shusher +Benthic Explorers +Leech Bonder +Weight of Conscience +Multani's Presence +Lullmage Mentor ---------- From 0700036b96bd99375ba693b5f6cec8af2882a6aa Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 14:47:38 +0000 Subject: [PATCH 034/123] InputSelectTargets moved to separate class, SpellAbilityRequirements - all options are passed as parameters to fillRequirements --- .gitattributes | 1 + .../SpellAbilityRequirements.java | 45 ++-- .../card/spellability/TargetChooser.java | 246 +----------------- .../control/input/InputSelectTargets.java | 212 +++++++++++++++ src/main/java/forge/game/GameActionPlay.java | 46 ++-- 5 files changed, 258 insertions(+), 292 deletions(-) create mode 100644 src/main/java/forge/control/input/InputSelectTargets.java diff --git a/.gitattributes b/.gitattributes index a350675a54e..94cea2207e7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13847,6 +13847,7 @@ src/main/java/forge/control/input/InputSelectCards.java -text src/main/java/forge/control/input/InputSelectCardsFromList.java -text src/main/java/forge/control/input/InputSelectMany.java -text src/main/java/forge/control/input/InputSelectManyBase.java -text +src/main/java/forge/control/input/InputSelectTargets.java -text src/main/java/forge/control/input/InputSynchronized.java -text src/main/java/forge/control/input/InputSyncronizedBase.java -text src/main/java/forge/control/input/package-info.java svneol=native#text/plain diff --git a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java index 98e88306ea3..0d18237f63a 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java +++ b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java @@ -39,24 +39,15 @@ import forge.game.zone.Zone; */ public class SpellAbilityRequirements { private final SpellAbility ability; - private final TargetChooser select; private final CostPayment payment; - private boolean isFree; - private boolean skipStack = false; - private boolean isAlreadyTargeted = false; - - public void setAlreadyTargeted() { isAlreadyTargeted = true; } - public final void setSkipStack() { this.skipStack = true; } - public void setFree() { this.isFree = true; } public SpellAbilityRequirements(final SpellAbility sa, final CostPayment cp) { this.ability = sa; - this.select = new TargetChooser(sa); this.payment = cp; } - public final void fillRequirements() { + public final void fillRequirements(boolean isAlreadyTargeted, boolean isFree, boolean skipStack) { final GameState game = Singletons.getModel().getGame(); // used to rollback @@ -75,26 +66,26 @@ public class SpellAbilityRequirements { // Announce things like how many times you want to Multikick or the value of X if (!this.announceRequirements()) { - this.select.setCancel(true); - rollbackAbility(fromZone, zonePosition); + rollbackAbility(fromZone, zonePosition, null); return; } + final TargetChooser select = new TargetChooser(ability); // Skip to paying if parent ability doesn't target and has no // subAbilities. // (or trigger case where its already targeted) - boolean acceptsTargets = this.select.doesTarget() || this.ability.getSubAbility() != null; + boolean acceptsTargets = select.doesTarget() || this.ability.getSubAbility() != null; if (!isAlreadyTargeted && acceptsTargets) { - this.select.clearTargets(); - this.select.chooseTargets(); - if (this.select.isCanceled()) { - rollbackAbility(fromZone, zonePosition); + select.clearTargets(); + select.chooseTargets(); + if (select.isCanceled()) { + rollbackAbility(fromZone, zonePosition, select); return; } } // Payment - boolean paymentMade = this.isFree; + boolean paymentMade = isFree; if (!paymentMade) { this.payment.changeCost(); @@ -102,12 +93,12 @@ public class SpellAbilityRequirements { } if (!paymentMade) { - rollbackAbility(fromZone, zonePosition); + rollbackAbility(fromZone, zonePosition, select); return; } - else if (this.isFree || this.payment.isFullyPaid()) { - if (this.skipStack) { + else if (isFree || this.payment.isFullyPaid()) { + if (skipStack) { AbilityUtils.resolve(this.ability, false); } else { @@ -116,13 +107,13 @@ public class SpellAbilityRequirements { game.getStack().addAndUnfreeze(this.ability); } - // Warning about this - resolution may come in another thread, and it would still need its targets - this.select.clearTargets(); + // no worries here. The same thread must resolve, and by this moment ability will have been resolved already + select.clearTargets(); game.getAction().checkStateEffects(); } } - private void rollbackAbility(Zone fromZone, int zonePosition) { + private void rollbackAbility(Zone fromZone, int zonePosition, TargetChooser select) { // cancel ability during target choosing final Card c = this.ability.getSourceCard(); @@ -136,8 +127,8 @@ public class SpellAbilityRequirements { Singletons.getModel().getGame().getAction().moveTo(fromZone, c, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); } - if (this.select != null) { - this.select.clearTargets(); + if (select != null) { + select.clearTargets(); } this.ability.resetOnceResolved(); @@ -147,7 +138,7 @@ public class SpellAbilityRequirements { } - public boolean announceRequirements() { + private boolean announceRequirements() { // Announcing Requirements like Choosing X or Multikicker // SA Params as comma delimited list String announce = ability.getParam("Announce"); diff --git a/src/main/java/forge/card/spellability/TargetChooser.java b/src/main/java/forge/card/spellability/TargetChooser.java index 343ba088d56..64ecd9b766f 100644 --- a/src/main/java/forge/card/spellability/TargetChooser.java +++ b/src/main/java/forge/card/spellability/TargetChooser.java @@ -20,24 +20,19 @@ package forge.card.spellability; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import com.google.common.base.Predicate; import forge.Card; import forge.CardLists; import forge.FThreads; -import forge.GameEntity; import forge.card.ability.AbilityUtils; -import forge.card.ability.ApiType; -import forge.control.input.InputSyncronizedBase; +import forge.control.input.InputSelectTargets; import forge.game.GameState; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; -import forge.view.ButtonUtil; /** *

@@ -48,249 +43,18 @@ import forge.view.ButtonUtil; * @version $Id$ */ public class TargetChooser { - /** - * TODO: Write javadoc for this type. - * - */ - public static final class InputSelectTargets extends InputSyncronizedBase { - private final List choices; - // some cards can be targeted several times (eg: distribute damage as you choose) - private final Map targetDepth = new HashMap(); - private final Target tgt; - private final SpellAbility sa; - private boolean bCancel = false; - private boolean bOk = false; - private final boolean mandatory; - private static final long serialVersionUID = -1091595663541356356L; - - public final boolean hasCancelled() { return bCancel; } - public final boolean hasPressedOk() { return bOk; } - /** - * TODO: Write javadoc for Constructor. - * @param select - * @param choices - * @param req - * @param alreadyTargeted - * @param targeted - * @param tgt - * @param sa - * @param mandatory - */ - public InputSelectTargets(List choices, SpellAbility sa, boolean mandatory) { - super(sa.getActivatingPlayer()); - this.choices = choices; - this.tgt = sa.getTarget(); - this.sa = sa; - this.mandatory = mandatory; - } - - @Override - public void showMessage() { - final StringBuilder sb = new StringBuilder(); - sb.append("Targeted:\n"); - for (final Entry o : targetDepth.entrySet()) { - sb.append(o.getKey()); - if( o.getValue() > 1 ) - sb.append(" (").append(o.getValue()).append(" times)"); - sb.append("\n"); - } - //sb.append(tgt.getTargetedString()).append("\n"); - sb.append(tgt.getVTSelection()); - - showMessage(sb.toString()); - - // If reached Minimum targets, enable OK button - if (!tgt.isMinTargetsChosen(sa.getSourceCard(), sa) || tgt.isDividedAsYouChoose()) { - if (mandatory && tgt.hasCandidates(sa, true)) { - // Player has to click on a target - ButtonUtil.disableAll(); - } else { - ButtonUtil.enableOnlyCancel(); - } - } else { - if (mandatory && tgt.hasCandidates(sa, true)) { - // Player has to click on a target or ok - ButtonUtil.enableOnlyOk(); - } else { - ButtonUtil.enableAllFocusOk(); - } - } - } - - @Override - public void selectButtonCancel() { - bCancel = true; - this.done(); - } - - @Override - public void selectButtonOK() { - bOk = true; - this.done(); - } - - @Override - public void selectCard(final Card card) { - if (!tgt.isUniqueTargets() && targetDepth.containsKey(card)) { - return; - } - - // leave this in temporarily, there some seriously wrong things going on here - if (!card.canBeTargetedBy(sa)) { - showMessage("Cannot target this card (Shroud? Protection? Restrictions?)."); - return; - } - if (!choices.contains(card)) { - showMessage("This card is not a valid choice for some other reason besides (Shroud? Protection? Restrictions?)."); - return; - } - - if (tgt.isDividedAsYouChoose()) { - final int stillToDivide = tgt.getStillToDivide(); - int allocatedPortion = 0; - // allow allocation only if the max targets isn't reached and there are more candidates - if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) - && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { - final Integer[] choices = new Integer[stillToDivide]; - for (int i = 1; i <= stillToDivide; i++) { - choices[i - 1] = i; - } - String apiBasedMessage = "Distribute how much to "; - if (sa.getApi() == ApiType.DealDamage) { - apiBasedMessage = "Select how much damage to deal to "; - } else if (sa.getApi() == ApiType.PreventDamage) { - apiBasedMessage = "Select how much damage to prevent to "; - } else if (sa.getApi() == ApiType.PutCounter) { - apiBasedMessage = "Select how many counters to distribute to "; - } - final StringBuilder sb = new StringBuilder(); - sb.append(apiBasedMessage); - sb.append(card.toString()); - Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); - if (null == chosen) { - return; - } - allocatedPortion = chosen; - } else { // otherwise assign the rest of the damage/protection - allocatedPortion = stillToDivide; - } - tgt.setStillToDivide(stillToDivide - allocatedPortion); - tgt.addDividedAllocation(card, allocatedPortion); - } - addTarget(card); - } // selectCard() - - @Override - public void selectPlayer(final Player player) { - if (!tgt.isUniqueTargets() && targetDepth.containsKey(player)) { - return; - } - - if (!sa.canTarget(player)) { - showMessage("Cannot target this player (Hexproof? Protection? Restrictions?)."); - return; - } - - if (tgt.isDividedAsYouChoose()) { - final int stillToDivide = tgt.getStillToDivide(); - int allocatedPortion = 0; - // allow allocation only if the max targets isn't reached and there are more candidates - if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { - final Integer[] choices = new Integer[stillToDivide]; - for (int i = 1; i <= stillToDivide; i++) { - choices[i - 1] = i; - } - String apiBasedMessage = "Distribute how much to "; - if (sa.getApi() == ApiType.DealDamage) { - apiBasedMessage = "Select how much damage to deal to "; - } else if (sa.getApi() == ApiType.PreventDamage) { - apiBasedMessage = "Select how much damage to prevent to "; - } - final StringBuilder sb = new StringBuilder(); - sb.append(apiBasedMessage); - sb.append(player.getName()); - Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); - if (null == chosen) { - return; - } - allocatedPortion = chosen; - } else { // otherwise assign the rest of the damage/protection - allocatedPortion = stillToDivide; - } - tgt.setStillToDivide(stillToDivide - allocatedPortion); - tgt.addDividedAllocation(player, allocatedPortion); - } - addTarget(player); - } - - void addTarget(GameEntity ge) { - tgt.addTarget(ge); - Integer val = targetDepth.get(ge); - targetDepth.put(ge, val == null ? Integer.valueOf(1) : Integer.valueOf(val.intValue() + 1) ); - - if(hasAllTargets()) { - bOk = true; - this.done(); - } - else - this.showMessage(); - } - - void done() { - this.stop(); - } - - boolean hasAllTargets() { - return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa); - } - } - private final SpellAbility ability; - /** - *

- * Constructor for Target_Selection. - *

- * - * @param tgt - * a {@link forge.card.spellability.Target} object. - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - */ + public TargetChooser(final SpellAbility sa) { this.ability = sa; } - /** - *

- * getTgt. - *

- * - * @return a {@link forge.card.spellability.Target} object. - */ - public final Target getTgt() { + private final Target getTgt() { return this.ability.getTarget(); } - /** - *

- * Getter for the field ability. - *

- * - * @return a {@link forge.card.spellability.SpellAbility} object. - */ - public final SpellAbility getAbility() { - return this.ability; - } - - /** - *

- * Getter for the field card. - *

- * - * @return a {@link forge.Card} object. - */ - public final Card getCard() { + private final Card getCard() { return this.ability.getSourceCard(); } @@ -342,7 +106,7 @@ public class TargetChooser { public final boolean chooseTargets() { Target tgt = getTgt(); - final boolean canTarget = tgt == null ? false : tgt.doesTarget(); + final boolean canTarget = doesTarget(); final int minTargets = canTarget ? tgt.getMinTargets(getCard(), ability) : 0; final int maxTargets = canTarget ? tgt.getMaxTargets(getCard(), ability) : 0; final int numTargeted = canTarget ? tgt.getNumTargeted() : 0; diff --git a/src/main/java/forge/control/input/InputSelectTargets.java b/src/main/java/forge/control/input/InputSelectTargets.java new file mode 100644 index 00000000000..3d5462a7a0b --- /dev/null +++ b/src/main/java/forge/control/input/InputSelectTargets.java @@ -0,0 +1,212 @@ +package forge.control.input; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import forge.Card; +import forge.GameEntity; +import forge.card.ability.ApiType; +import forge.card.spellability.SpellAbility; +import forge.card.spellability.Target; +import forge.game.player.Player; +import forge.gui.GuiChoose; +import forge.view.ButtonUtil; + +/** + * TODO: Write javadoc for this type. + * + */ +public final class InputSelectTargets extends InputSyncronizedBase { + private final List choices; + // some cards can be targeted several times (eg: distribute damage as you choose) + private final Map targetDepth = new HashMap(); + private final Target tgt; + private final SpellAbility sa; + private boolean bCancel = false; + private boolean bOk = false; + private final boolean mandatory; + private static final long serialVersionUID = -1091595663541356356L; + + public final boolean hasCancelled() { return bCancel; } + public final boolean hasPressedOk() { return bOk; } + /** + * TODO: Write javadoc for Constructor. + * @param select + * @param choices + * @param req + * @param alreadyTargeted + * @param targeted + * @param tgt + * @param sa + * @param mandatory + */ + public InputSelectTargets(List choices, SpellAbility sa, boolean mandatory) { + super(sa.getActivatingPlayer()); + this.choices = choices; + this.tgt = sa.getTarget(); + this.sa = sa; + this.mandatory = mandatory; + } + + @Override + public void showMessage() { + final StringBuilder sb = new StringBuilder(); + sb.append("Targeted:\n"); + for (final Entry o : targetDepth.entrySet()) { + sb.append(o.getKey()); + if( o.getValue() > 1 ) + sb.append(" (").append(o.getValue()).append(" times)"); + sb.append("\n"); + } + //sb.append(tgt.getTargetedString()).append("\n"); + sb.append(tgt.getVTSelection()); + + showMessage(sb.toString()); + + // If reached Minimum targets, enable OK button + if (!tgt.isMinTargetsChosen(sa.getSourceCard(), sa) || tgt.isDividedAsYouChoose()) { + if (mandatory && tgt.hasCandidates(sa, true)) { + // Player has to click on a target + ButtonUtil.disableAll(); + } else { + ButtonUtil.enableOnlyCancel(); + } + } else { + if (mandatory && tgt.hasCandidates(sa, true)) { + // Player has to click on a target or ok + ButtonUtil.enableOnlyOk(); + } else { + ButtonUtil.enableAllFocusOk(); + } + } + } + + @Override + public void selectButtonCancel() { + bCancel = true; + this.done(); + } + + @Override + public void selectButtonOK() { + bOk = true; + this.done(); + } + + @Override + public void selectCard(final Card card) { + if (!tgt.isUniqueTargets() && targetDepth.containsKey(card)) { + return; + } + + // leave this in temporarily, there some seriously wrong things going on here + if (!card.canBeTargetedBy(sa)) { + showMessage("Cannot target this card (Shroud? Protection? Restrictions?)."); + return; + } + if (!choices.contains(card)) { + showMessage("This card is not a valid choice for some other reason besides (Shroud? Protection? Restrictions?)."); + return; + } + + if (tgt.isDividedAsYouChoose()) { + final int stillToDivide = tgt.getStillToDivide(); + int allocatedPortion = 0; + // allow allocation only if the max targets isn't reached and there are more candidates + if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) + && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { + final Integer[] choices = new Integer[stillToDivide]; + for (int i = 1; i <= stillToDivide; i++) { + choices[i - 1] = i; + } + String apiBasedMessage = "Distribute how much to "; + if (sa.getApi() == ApiType.DealDamage) { + apiBasedMessage = "Select how much damage to deal to "; + } else if (sa.getApi() == ApiType.PreventDamage) { + apiBasedMessage = "Select how much damage to prevent to "; + } else if (sa.getApi() == ApiType.PutCounter) { + apiBasedMessage = "Select how many counters to distribute to "; + } + final StringBuilder sb = new StringBuilder(); + sb.append(apiBasedMessage); + sb.append(card.toString()); + Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); + if (null == chosen) { + return; + } + allocatedPortion = chosen; + } else { // otherwise assign the rest of the damage/protection + allocatedPortion = stillToDivide; + } + tgt.setStillToDivide(stillToDivide - allocatedPortion); + tgt.addDividedAllocation(card, allocatedPortion); + } + addTarget(card); + } // selectCard() + + @Override + public void selectPlayer(final Player player) { + if (!tgt.isUniqueTargets() && targetDepth.containsKey(player)) { + return; + } + + if (!sa.canTarget(player)) { + showMessage("Cannot target this player (Hexproof? Protection? Restrictions?)."); + return; + } + + if (tgt.isDividedAsYouChoose()) { + final int stillToDivide = tgt.getStillToDivide(); + int allocatedPortion = 0; + // allow allocation only if the max targets isn't reached and there are more candidates + if ((tgt.getNumTargeted() + 1 < tgt.getMaxTargets(sa.getSourceCard(), sa)) && (tgt.getNumCandidates(sa, true) - 1 > 0) && stillToDivide > 1) { + final Integer[] choices = new Integer[stillToDivide]; + for (int i = 1; i <= stillToDivide; i++) { + choices[i - 1] = i; + } + String apiBasedMessage = "Distribute how much to "; + if (sa.getApi() == ApiType.DealDamage) { + apiBasedMessage = "Select how much damage to deal to "; + } else if (sa.getApi() == ApiType.PreventDamage) { + apiBasedMessage = "Select how much damage to prevent to "; + } + final StringBuilder sb = new StringBuilder(); + sb.append(apiBasedMessage); + sb.append(player.getName()); + Integer chosen = GuiChoose.oneOrNone(sb.toString(), choices); + if (null == chosen) { + return; + } + allocatedPortion = chosen; + } else { // otherwise assign the rest of the damage/protection + allocatedPortion = stillToDivide; + } + tgt.setStillToDivide(stillToDivide - allocatedPortion); + tgt.addDividedAllocation(player, allocatedPortion); + } + addTarget(player); + } + + void addTarget(GameEntity ge) { + tgt.addTarget(ge); + Integer val = targetDepth.get(ge); + targetDepth.put(ge, val == null ? Integer.valueOf(1) : Integer.valueOf(val.intValue() + 1) ); + + if(hasAllTargets()) { + bOk = true; + this.done(); + } + else + this.showMessage(); + } + + void done() { + this.stop(); + } + + boolean hasAllTargets() { + return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa); + } +} \ No newline at end of file diff --git a/src/main/java/forge/game/GameActionPlay.java b/src/main/java/forge/game/GameActionPlay.java index d822d19a906..b5d0950368c 100644 --- a/src/main/java/forge/game/GameActionPlay.java +++ b/src/main/java/forge/game/GameActionPlay.java @@ -66,7 +66,9 @@ public class GameActionPlay { public final void playSpellAbilityWithoutPayingManaCost(final SpellAbility sa) { FThreads.checkEDT("GameActionPlay.playSpellAbilityWithoutPayingManaCost", false); final Card source = sa.getSourceCard(); - setSplitCardState(source, sa); // Split card support + + if( source.isSplitCard()) + setSplitCardState(source, sa); if (sa.getPayCosts() != null) { if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { @@ -75,8 +77,7 @@ public class GameActionPlay { final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment); - req.setFree(); - req.fillRequirements(); + req.fillRequirements(false, true, false); } else { if (sa.isSpell()) { final Card c = sa.getSourceCard(); @@ -356,8 +357,8 @@ public class GameActionPlay { final Card source = sa.getSourceCard(); - // Split card support - setSplitCardState(source, sa); + if(source.isSplitCard()) + setSplitCardState(source, sa); if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { CharmEffect.makeChoices(sa); @@ -389,7 +390,7 @@ public class GameActionPlay { } final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment); - req.fillRequirements(); + req.fillRequirements(false, false, false); } else { ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { @@ -437,10 +438,8 @@ public class GameActionPlay { } final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment); - if( useOldTargets ) - req.setAlreadyTargeted(); - req.setSkipStack(); - req.fillRequirements(); + + req.fillRequirements(useOldTargets, false, true); } else { ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { @@ -492,21 +491,20 @@ public class GameActionPlay { private void setSplitCardState(final Card source, SpellAbility sa) { // Split card support - if (source.isSplitCard()) { - List leftSplitAbilities = source.getState(CardCharacteristicName.LeftSplit).getSpellAbility(); - List rightSplitAbilities = source.getState(CardCharacteristicName.RightSplit).getSpellAbility(); - for (SpellAbility a : leftSplitAbilities) { - if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { - source.setState(CardCharacteristicName.LeftSplit); - break; - } - } - for (SpellAbility a : rightSplitAbilities) { - if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { - source.setState(CardCharacteristicName.RightSplit); - break; - } + List leftSplitAbilities = source.getState(CardCharacteristicName.LeftSplit).getSpellAbility(); + List rightSplitAbilities = source.getState(CardCharacteristicName.RightSplit).getSpellAbility(); + for (SpellAbility a : leftSplitAbilities) { + if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { + source.setState(CardCharacteristicName.LeftSplit); + return; } } + for (SpellAbility a : rightSplitAbilities) { + if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { + source.setState(CardCharacteristicName.RightSplit); + return; + } + } + throw new RuntimeException("Not found which part to choose for ability " + sa + " from card " + source); } } From b532e7147e32f6dd390b184720cbdc0520086bb4 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 15:06:20 +0000 Subject: [PATCH 035/123] static utility methods dealing with ability targets moved from TargetChooser to SpellAbility as instance methods --- .../java/forge/card/ability/ai/CounterAi.java | 5 +- .../forge/card/ability/ai/DamageDealAi.java | 3 +- .../forge/card/spellability/SpellAbility.java | 90 ++++++++++++++++++- .../card/spellability/TargetChooser.java | 90 +------------------ src/main/java/forge/game/zone/MagicStack.java | 3 +- 5 files changed, 96 insertions(+), 95 deletions(-) diff --git a/src/main/java/forge/card/ability/ai/CounterAi.java b/src/main/java/forge/card/ability/ai/CounterAi.java index 1600901e869..2cf860964d1 100644 --- a/src/main/java/forge/card/ability/ai/CounterAi.java +++ b/src/main/java/forge/card/ability/ai/CounterAi.java @@ -8,7 +8,6 @@ import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; -import forge.card.spellability.TargetChooser; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCost; import forge.game.ai.ComputerUtilMana; @@ -50,7 +49,7 @@ public class CounterAi extends SpellAbilityAi { } tgt.resetTargets(); - if (TargetChooser.matchSpellAbility(sa, topSA, tgt)) { + if (sa.canTargetSpellAbility(topSA)) { tgt.addTarget(topSA); } else { return false; @@ -120,7 +119,7 @@ public class CounterAi extends SpellAbilityAi { } tgt.resetTargets(); - if (TargetChooser.matchSpellAbility(sa, topSA, tgt)) { + if (sa.canTargetSpellAbility(topSA)) { tgt.addTarget(topSA); } else { return false; diff --git a/src/main/java/forge/card/ability/ai/DamageDealAi.java b/src/main/java/forge/card/ability/ai/DamageDealAi.java index b2f0ba01005..baac5c59c1d 100644 --- a/src/main/java/forge/card/ability/ai/DamageDealAi.java +++ b/src/main/java/forge/card/ability/ai/DamageDealAi.java @@ -15,7 +15,6 @@ import forge.card.cost.Cost; import forge.card.spellability.AbilitySub; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; -import forge.card.spellability.TargetChooser; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCombat; @@ -143,7 +142,7 @@ public class DamageDealAi extends DamageAiBase { final ArrayList objects = tgt.getTargets(); if (saMe.hasParam("TargetUnique")) { - objects.addAll(TargetChooser.getUniqueTargets(saMe)); + objects.addAll(saMe.getUniqueTargets()); } for (final Object o : objects) { if (o instanceof Card) { diff --git a/src/main/java/forge/card/spellability/SpellAbility.java b/src/main/java/forge/card/spellability/SpellAbility.java index e3e27b0fe20..e8b29b17765 100644 --- a/src/main/java/forge/card/spellability/SpellAbility.java +++ b/src/main/java/forge/card/spellability/SpellAbility.java @@ -1313,7 +1313,7 @@ public abstract class SpellAbility implements ISpellAbility { return false; } if (entity.isValid(this.getTarget().getValidTgts(), this.getActivatingPlayer(), this.getSourceCard()) - && (!this.getTarget().isUniqueTargets() || !TargetChooser.getUniqueTargets(this).contains(entity)) + && (!this.getTarget().isUniqueTargets() || !this.getUniqueTargets().contains(entity)) && entity.canBeTargetedBy(this)) { return true; } @@ -1679,4 +1679,92 @@ public abstract class SpellAbility implements ISpellAbility { public void setCopied(boolean isCopied0) { this.isCopied = isCopied0; } + + /** + * Gets the unique targets. + * + * @param ability + * the ability + * @return the unique targets + */ + public final ArrayList getUniqueTargets() { + final ArrayList targets = new ArrayList(); + SpellAbility child = this; + while (child instanceof AbilitySub) { + child = ((AbilitySub) child).getParent(); + if (child != null && child.getTarget() != null) { + targets.addAll(child.getTarget().getTargets()); + } + } + + return targets; + } + + public boolean canTargetSpellAbility(final SpellAbility topSA) { + final Target tgt = this.getTarget(); + final String saType = tgt.getTargetSpellAbilityType(); + + if (null == saType) { + // just take this to mean no restrictions - carry on. + } else if (topSA instanceof Spell) { + if (!saType.contains("Spell")) { + return false; + } + } else if (topSA.isTrigger()) { + if (!saType.contains("Triggered")) { + return false; + } + } else if (topSA instanceof AbilityActivated) { + if (!saType.contains("Activated")) { + return false; + } + } else { + return false; //Static ability? Whatever. + } + + final String splitTargetRestrictions = tgt.getSAValidTargeting(); + if (splitTargetRestrictions != null) { + // TODO What about spells with SubAbilities with Targets? + + final Target matchTgt = topSA.getTarget(); + + if (matchTgt == null) { + return false; + } + + boolean result = false; + + for (final Object o : matchTgt.getTargets()) { + if (matchesValid(o, splitTargetRestrictions.split(","))) { + result = true; + break; + } + } + + if (!result) { + return false; + } + } + + return topSA.getSourceCard().isValid(tgt.getValidTgts(), this.getActivatingPlayer(), this.getSourceCard()); + } + + + private boolean matchesValid(final Object o, final String[] valids) { + final Card srcCard = this.getSourceCard(); + final Player activatingPlayer = this.getActivatingPlayer(); + if (o instanceof Card) { + final Card c = (Card) o; + return c.isValid(valids, activatingPlayer, srcCard); + } + + if (o instanceof Player) { + Player p = (Player) o; + if (p.isValid(valids, activatingPlayer, srcCard)) { + return true; + } + } + + return false; + } } diff --git a/src/main/java/forge/card/spellability/TargetChooser.java b/src/main/java/forge/card/spellability/TargetChooser.java index 64ecd9b766f..81a84ac61ce 100644 --- a/src/main/java/forge/card/spellability/TargetChooser.java +++ b/src/main/java/forge/card/spellability/TargetChooser.java @@ -158,25 +158,7 @@ public class TargetChooser { return chooseTargets(); } - /** - * Gets the unique targets. - * - * @param ability - * the ability - * @return the unique targets - */ - public static final ArrayList getUniqueTargets(final SpellAbility ability) { - final ArrayList targets = new ArrayList(); - SpellAbility child = ability; - while (child instanceof AbilitySub) { - child = ((AbilitySub) child).getParent(); - if (child != null && child.getTarget() != null) { - targets.addAll(child.getTarget().getTargets()); - } - } - - return targets; - } + // these have been copied over from CardFactoryUtil as they need two extra // parameters for target selection. @@ -204,7 +186,7 @@ public class TargetChooser { choices.remove(tgt.getSourceCard()); } } - ArrayList objects = getUniqueTargets(this.ability); + ArrayList objects = this.ability.getUniqueTargets(); if (tgt.isUniqueTargets()) { for (final Object o : objects) { @@ -419,7 +401,7 @@ public class TargetChooser { } for (int i = 0; i < choosables.size(); i++) { - if (!TargetChooser.matchSpellAbility(ability, choosables.get(i), getTgt())) { + if (!ability.canTargetSpellAbility(choosables.get(i))) { choosables.remove(i); } } @@ -439,70 +421,4 @@ public class TargetChooser { * a {@link forge.card.spellability.Target} object. * @return a boolean. */ - public static boolean matchSpellAbility(final SpellAbility sa, final SpellAbility topSA, final Target tgt) { - final String saType = tgt.getTargetSpellAbilityType(); - - if (null == saType) { - // just take this to mean no restrictions - carry on. - } else if (topSA instanceof Spell) { - if (!saType.contains("Spell")) { - return false; - } - } else if (topSA.isTrigger()) { - if (!saType.contains("Triggered")) { - return false; - } - } else if (topSA instanceof AbilityActivated) { - if (!saType.contains("Activated")) { - return false; - } - } else { - return false; //Static ability? Whatever. - } - - final String splitTargetRestrictions = tgt.getSAValidTargeting(); - if (splitTargetRestrictions != null) { - // TODO What about spells with SubAbilities with Targets? - - final Target matchTgt = topSA.getTarget(); - - if (matchTgt == null) { - return false; - } - - boolean result = false; - - for (final Object o : matchTgt.getTargets()) { - if (TargetChooser.matchesValid(o, splitTargetRestrictions.split(","), sa)) { - result = true; - break; - } - } - - if (!result) { - return false; - } - } - - return topSA.getSourceCard().isValid(tgt.getValidTgts(), sa.getActivatingPlayer(), sa.getSourceCard()); - } - - - private static boolean matchesValid(final Object o, final String[] valids, final SpellAbility sa) { - final Card srcCard = sa.getSourceCard(); - final Player activatingPlayer = sa.getActivatingPlayer(); - if (o instanceof Card) { - final Card c = (Card) o; - return c.isValid(valids, activatingPlayer, srcCard); - } - - if (o instanceof Player) { - Player p = (Player) o; - if (p.isValid(valids, sa.getActivatingPlayer(), sa.getSourceCard())) { - return true; - } - } - - return false; - } } diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index 6dbd0b6ba8e..9253d8a6824 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -42,7 +42,6 @@ import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.Target; import forge.card.spellability.TargetChoices; -import forge.card.spellability.TargetChooser; import forge.card.trigger.Trigger; import forge.card.trigger.TriggerType; import forge.control.input.InputPayManaExecuteCommands; @@ -885,7 +884,7 @@ public class MagicStack extends MyObservable { } else if (o instanceof SpellAbility) { final SpellAbility tgtSA = (SpellAbility) o; - invalidTarget = !(TargetChooser.matchSpellAbility(sa, tgtSA, tgt)); + invalidTarget = !(sa.canTargetSpellAbility(tgtSA)); // TODO Remove target? if (invalidTarget) { choices.removeTarget(tgtSA); From e329fe22c5893a83afa442280e8f9757a7a94f91 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 15:27:02 +0000 Subject: [PATCH 036/123] Renamed TargetChooser.java back into TargetSelection (for Sol) CostSacrifice - removed input, using a common one other list-related costs use getDescriptiveType method from base class InputPayManaExecuteCommands won't reset manaCost because the class is not reusable anyway. --- .gitattributes | 2 +- src/main/java/forge/card/cost/CostPart.java | 8 +- .../java/forge/card/cost/CostSacrifice.java | 78 +++---------------- .../java/forge/card/cost/CostTapType.java | 13 +--- .../java/forge/card/cost/CostUntapType.java | 13 +--- .../SpellAbilityRequirements.java | 4 +- ...argetChooser.java => TargetSelection.java} | 10 +-- .../input/InputPayManaExecuteCommands.java | 13 +--- 8 files changed, 24 insertions(+), 117 deletions(-) rename src/main/java/forge/card/spellability/{TargetChooser.java => TargetSelection.java} (98%) diff --git a/.gitattributes b/.gitattributes index 94cea2207e7..99e09970f8a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13766,7 +13766,7 @@ src/main/java/forge/card/spellability/SpellAbilityVariables.java svneol=native#t src/main/java/forge/card/spellability/SpellPermanent.java svneol=native#text/plain src/main/java/forge/card/spellability/Target.java svneol=native#text/plain src/main/java/forge/card/spellability/TargetChoices.java svneol=native#text/plain -src/main/java/forge/card/spellability/TargetChooser.java svneol=native#text/plain +src/main/java/forge/card/spellability/TargetSelection.java svneol=native#text/plain src/main/java/forge/card/spellability/package-info.java svneol=native#text/plain src/main/java/forge/card/staticability/StaticAbility.java svneol=native#text/plain src/main/java/forge/card/staticability/StaticAbilityCantAttackBlock.java -text diff --git a/src/main/java/forge/card/cost/CostPart.java b/src/main/java/forge/card/cost/CostPart.java index faf216c8231..703c0e0d648 100644 --- a/src/main/java/forge/card/cost/CostPart.java +++ b/src/main/java/forge/card/cost/CostPart.java @@ -91,13 +91,9 @@ public abstract class CostPart { return this.typeDescription; } - /** - * Gets the descriptive type. - * - * @return the descriptive type - */ public final String getDescriptiveType() { - return this.getTypeDescription() == null ? this.getType() : this.getTypeDescription(); + String typeDesc = this.getTypeDescription(); + return typeDesc == null ? this.getType() : typeDesc; } /** diff --git a/src/main/java/forge/card/cost/CostSacrifice.java b/src/main/java/forge/card/cost/CostSacrifice.java index 14f1de620a1..42375798133 100644 --- a/src/main/java/forge/card/cost/CostSacrifice.java +++ b/src/main/java/forge/card/cost/CostSacrifice.java @@ -24,85 +24,20 @@ import forge.CardLists; import forge.FThreads; import forge.card.ability.AbilityUtils; import forge.card.spellability.SpellAbility; -import forge.control.input.InputPayment; +import forge.control.input.InputSelectCards; +import forge.control.input.InputSelectCardsFromList; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiDialog; -import forge.gui.match.CMatchUI; -import forge.view.ButtonUtil; /** * The Class CostSacrifice. */ public class CostSacrifice extends CostPartWithList { - /** - * TODO: Write javadoc for this type. - * - */ - public static final class InputPayCostSacrificeFromList extends InputPayCostBase { - private final CostSacrifice part; - private final SpellAbility sa; - private final int nNeeded; - private final List typeList; - private static final long serialVersionUID = 2685832214519141903L; - private int nSacrifices = 0; - - /** - * TODO: Write javadoc for Constructor. - * @param part - * @param sa - * @param nNeeded - * @param payment - * @param typeList - */ - public InputPayCostSacrificeFromList(CostSacrifice part, SpellAbility sa, int nNeeded, List typeList) { - this.part = part; - this.sa = sa; - this.nNeeded = nNeeded; - this.typeList = typeList; - } - - @Override - public void showMessage() { - if (nNeeded == 0) { - this.done(); - } - - final StringBuilder msg = new StringBuilder("Sacrifice "); - final int nLeft = nNeeded - this.nSacrifices; - msg.append(nLeft).append(" "); - msg.append(part.getDescriptiveType()); - if (nLeft > 1) { - msg.append("s"); - } - - CMatchUI.SINGLETON_INSTANCE.showMessage(msg.toString()); - ButtonUtil.enableOnlyCancel(); - } - - @Override - public void selectCard(final Card card) { - if (typeList.contains(card)) { - this.nSacrifices++; - part.executePayment(sa, card); - typeList.remove(card); - // in case nothing else to sacrifice - if (this.nSacrifices == nNeeded) { - this.done(); - } else if (typeList.isEmpty()) { - // happen - this.cancel(); - } else { - this.showMessage(); - } - } - } - } - /** * Instantiates a new cost sacrifice. * @@ -226,9 +161,14 @@ public class CostSacrifice extends CostPartWithList { if (0 == c.intValue()) { return true; } - InputPayment inp = new InputPayCostSacrificeFromList(this, ability, c, list); + + InputSelectCards inp = new InputSelectCardsFromList(c, c, list); + inp.setMessage("Select a " + this.getDescriptiveType() + " to sacrifice (%d left)"); FThreads.setInputAndWait(inp); - return inp.isPaid(); + if ( inp.hasCancelled() ) + return false; + + return executePayment(ability, inp.getSelected()); } return false; } diff --git a/src/main/java/forge/card/cost/CostTapType.java b/src/main/java/forge/card/cost/CostTapType.java index b398e4ae07e..ac4c6b9c2ac 100644 --- a/src/main/java/forge/card/cost/CostTapType.java +++ b/src/main/java/forge/card/cost/CostTapType.java @@ -62,15 +62,6 @@ public class CostTapType extends CostPartWithList { public boolean isReusable() { return true; } - /** - * Gets the description. - * - * @return the description - */ - public final String getDescription() { - return this.getTypeDescription() == null ? this.getType() : this.getTypeDescription(); - } - /* * (non-Javadoc) * @@ -82,7 +73,7 @@ public class CostTapType extends CostPartWithList { sb.append("Tap "); final Integer i = this.convertAmount(); - final String desc = this.getDescription(); + final String desc = this.getDescriptiveType(); final String type = this.getType(); if (type.contains("sharesCreatureTypeWith")) { @@ -206,7 +197,7 @@ public class CostTapType extends CostPartWithList { InputSelectCards inp = new InputSelectCardsFromList(c, c, typeList); - inp.setMessage("Select a " + getDescription() + " to tap (%d left)"); + inp.setMessage("Select a " + getDescriptiveType() + " to tap (%d left)"); FThreads.setInputAndWait(inp); if ( inp.hasCancelled() ) return false; diff --git a/src/main/java/forge/card/cost/CostUntapType.java b/src/main/java/forge/card/cost/CostUntapType.java index 3c2e3c8cbed..004d5cc5362 100644 --- a/src/main/java/forge/card/cost/CostUntapType.java +++ b/src/main/java/forge/card/cost/CostUntapType.java @@ -59,15 +59,6 @@ public class CostUntapType extends CostPartWithList { public boolean isReusable() { return true; } - /** - * Gets the description. - * - * @return the description - */ - public final String getDescription() { - return this.getTypeDescription() == null ? this.getType() : this.getTypeDescription(); - } - /* * (non-Javadoc) * @@ -79,7 +70,7 @@ public class CostUntapType extends CostPartWithList { sb.append("Untap "); final Integer i = this.convertAmount(); - final String desc = this.getDescription(); + final String desc = this.getDescriptiveType(); sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), " tapped " + desc)); @@ -161,7 +152,7 @@ public class CostUntapType extends CostPartWithList { } } InputSelectCards inp = new InputSelectCardsFromList(c, c, typeList); - inp.setMessage("Select a " + getDescription() + " to untap (%d left)"); + inp.setMessage("Select a " + getDescriptiveType() + " to untap (%d left)"); FThreads.setInputAndWait(inp); if( inp.hasCancelled() || inp.getSelected().size() != c ) return false; diff --git a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java index 0d18237f63a..ab6505b2f3a 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java +++ b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java @@ -70,7 +70,7 @@ public class SpellAbilityRequirements { return; } - final TargetChooser select = new TargetChooser(ability); + final TargetSelection select = new TargetSelection(ability); // Skip to paying if parent ability doesn't target and has no // subAbilities. // (or trigger case where its already targeted) @@ -113,7 +113,7 @@ public class SpellAbilityRequirements { } } - private void rollbackAbility(Zone fromZone, int zonePosition, TargetChooser select) { + private void rollbackAbility(Zone fromZone, int zonePosition, TargetSelection select) { // cancel ability during target choosing final Card c = this.ability.getSourceCard(); diff --git a/src/main/java/forge/card/spellability/TargetChooser.java b/src/main/java/forge/card/spellability/TargetSelection.java similarity index 98% rename from src/main/java/forge/card/spellability/TargetChooser.java rename to src/main/java/forge/card/spellability/TargetSelection.java index 81a84ac61ce..a1f5330ec30 100644 --- a/src/main/java/forge/card/spellability/TargetChooser.java +++ b/src/main/java/forge/card/spellability/TargetSelection.java @@ -42,11 +42,11 @@ import forge.gui.GuiChoose; * @author Forge * @version $Id$ */ -public class TargetChooser { +public class TargetSelection { private final SpellAbility ability; - public TargetChooser(final SpellAbility sa) { + public TargetSelection(final SpellAbility sa) { this.ability = sa; } @@ -58,7 +58,7 @@ public class TargetChooser { return this.ability.getSourceCard(); } - private TargetChooser subSelection = null; + private TargetSelection subSelection = null; private boolean bCancel = false; private boolean bTargetingDone = false; @@ -125,7 +125,7 @@ public class TargetChooser { return true; // Has Sub Ability - this.subSelection = new TargetChooser(abSub); + this.subSelection = new TargetSelection(abSub); this.subSelection.clearTargets(); return this.subSelection.chooseTargets(); } @@ -171,7 +171,7 @@ public class TargetChooser { *

* @return */ - public final List chooseValidInput() { + private final List chooseValidInput() { final Target tgt = this.getTgt(); final GameState game = ability.getActivatingPlayer().getGame(); final List zone = tgt.getZone(); diff --git a/src/main/java/forge/control/input/InputPayManaExecuteCommands.java b/src/main/java/forge/control/input/InputPayManaExecuteCommands.java index 5d0ee10ac24..4f51d8133fe 100644 --- a/src/main/java/forge/control/input/InputPayManaExecuteCommands.java +++ b/src/main/java/forge/control/input/InputPayManaExecuteCommands.java @@ -34,7 +34,7 @@ import forge.view.ButtonUtil; * @author Forge * @version $Id$ */ -public class InputPayManaExecuteCommands extends InputPayManaBase implements InputPayment { +public class InputPayManaExecuteCommands extends InputPayManaBase { /** * Constant serialVersionUID=3836655722696348713L. */ @@ -80,14 +80,6 @@ public class InputPayManaExecuteCommands extends InputPayManaBase implements Inp } - /** - *

- * resetManaCost. - *

- */ - public final void resetManaCost() { - this.manaCost = new ManaCostBeingPaid(this.originalManaCost); - } @Override public void selectPlayer(final Player selectedPlayer) { @@ -109,7 +101,6 @@ public class InputPayManaExecuteCommands extends InputPayManaBase implements Inp if (this.phyLifeToLose > 0) { Singletons.getControl().getPlayer().payLife(this.phyLifeToLose, null); } - this.resetManaCost(); Singletons.getControl().getPlayer().getManaPool().clearManaPaid(this.saPaidFor, false); bPaid = true; this.stop(); @@ -118,8 +109,6 @@ public class InputPayManaExecuteCommands extends InputPayManaBase implements Inp /** {@inheritDoc} */ @Override public final void selectButtonCancel() { - - this.resetManaCost(); Singletons.getControl().getPlayer().getManaPool().refundManaPaid(this.saPaidFor, true); bPaid = false; this.stop(); From 680821cd95a78ab7a0da98bb3fc9a5bb1effbb88 Mon Sep 17 00:00:00 2001 From: Hellfish Date: Mon, 1 Apr 2013 17:02:08 +0000 Subject: [PATCH 037/123] *Reinstated Soul Seizer fix --- src/main/java/forge/card/spellability/TargetSelection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/forge/card/spellability/TargetSelection.java b/src/main/java/forge/card/spellability/TargetSelection.java index a1f5330ec30..29595cff2f2 100644 --- a/src/main/java/forge/card/spellability/TargetSelection.java +++ b/src/main/java/forge/card/spellability/TargetSelection.java @@ -106,7 +106,7 @@ public class TargetSelection { public final boolean chooseTargets() { Target tgt = getTgt(); - final boolean canTarget = doesTarget(); + final boolean canTarget = tgt == null ? false : doesTarget(); final int minTargets = canTarget ? tgt.getMinTargets(getCard(), ability) : 0; final int maxTargets = canTarget ? tgt.getMaxTargets(getCard(), ability) : 0; final int numTargeted = canTarget ? tgt.getNumTargeted() : 0; From 7811880919abe0dbcf18fa2eedbd92912cae92c6 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 18:02:48 +0000 Subject: [PATCH 038/123] Simplified and partially inlined chooseCardFromStack changed return type of zone.getPosition to unboxed int --- .../card/spellability/TargetSelection.java | 73 +++++-------------- src/main/java/forge/game/zone/IZone.java | 10 --- src/main/java/forge/game/zone/Zone.java | 14 +--- 3 files changed, 21 insertions(+), 76 deletions(-) diff --git a/src/main/java/forge/card/spellability/TargetSelection.java b/src/main/java/forge/card/spellability/TargetSelection.java index 29595cff2f2..36414ec9e29 100644 --- a/src/main/java/forge/card/spellability/TargetSelection.java +++ b/src/main/java/forge/card/spellability/TargetSelection.java @@ -344,68 +344,33 @@ public class TargetSelection { private final void chooseCardFromStack(final boolean mandatory) { final Target tgt = this.getTgt(); final String message = tgt.getVTSelection(); - final String doneDummy = "[FINISH TARGETING]"; - // Find what's targetable, then allow human to choose - final ArrayList choosables = getTargetableOnStack(); - - final HashMap map = new HashMap(); - - for (final SpellAbility sa : choosables) { - if (!tgt.getTargetSAs().contains(sa)) { - map.put(choosables.indexOf(sa) + ". " + sa.getStackDescription(), sa); - } - } - - if (tgt.isMinTargetsChosen(this.ability.getSourceCard(), this.ability)) { - map.put(doneDummy, null); - } - - - if (map.isEmpty()) { - setCancel(true); - } else { - final String madeChoice = GuiChoose.oneOrNone(message, map.keySet()); - if (madeChoice != null) { - if (madeChoice.equals(doneDummy)) { - bTargetingDone = true; - } else { - tgt.addTarget(map.get(madeChoice)); - } - } else { - setCancel(true); - } - } - } - - // TODO The following three functions are Utility functions for - // TargetOnStack, probably should be moved - // The following should be select.getTargetableOnStack() - /** - *

- * getTargetableOnStack. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - * @param tgt - * a {@link forge.card.spellability.Target} object. - * @return a {@link java.util.ArrayList} object. - */ - private ArrayList getTargetableOnStack() { - final ArrayList choosables = new ArrayList(); + final List selectOptions = new ArrayList(); final GameState game = ability.getActivatingPlayer().getGame(); for (int i = 0; i < game.getStack().size(); i++) { - choosables.add(game.getStack().peekAbility(i)); + SpellAbility stackItem = game.getStack().peekAbility(i); + if( ability.canTargetSpellAbility(stackItem)) + selectOptions.add(stackItem); + } + + if (tgt.isMinTargetsChosen(this.ability.getSourceCard(), this.ability)) { + selectOptions.add("[FINISH TARGETING]"); } - for (int i = 0; i < choosables.size(); i++) { - if (!ability.canTargetSpellAbility(choosables.get(i))) { - choosables.remove(i); + if (selectOptions.isEmpty()) { + setCancel(true); + } else { + final Object madeChoice = GuiChoose.oneOrNone(message, selectOptions); + if (madeChoice == null) { + setCancel(true); + return; } + if (madeChoice instanceof SpellAbility) { + tgt.addTarget(madeChoice); + } else // only 'FINISH TARGETING' remains + bTargetingDone = true; } - return choosables; } /** diff --git a/src/main/java/forge/game/zone/IZone.java b/src/main/java/forge/game/zone/IZone.java index 4f5252416fd..c537e03fa56 100644 --- a/src/main/java/forge/game/zone/IZone.java +++ b/src/main/java/forge/game/zone/IZone.java @@ -121,16 +121,6 @@ interface IZone { * @return true, if successful */ boolean contains(Card c); - - /** - * gets the positon of a specific card in this zone, null if it doesn't exist. - * - * @param c - * the card - * @return position - */ - Integer getPosition(Card c); - /** * isEmpty returns true if given zone contains no cards. * diff --git a/src/main/java/forge/game/zone/Zone.java b/src/main/java/forge/game/zone/Zone.java index 9e84ec4e44d..8f8d88b0735 100644 --- a/src/main/java/forge/game/zone/Zone.java +++ b/src/main/java/forge/game/zone/Zone.java @@ -171,18 +171,8 @@ public class Zone extends MyObservable implements IZone, Observer, java.io.Seria return this.cardList.contains(c); } - /* - * (non-Javadoc) - * - * @see forge.IPlayerZone#getPosition(forge.Card) - */ - @Override - public final Integer getPosition(final Card c) { - int index = this.cardList.indexOf(c); - if (index == -1) { - return null; - } - return index; + public final int getPosition(final Card c) { + return this.cardList.indexOf(c); } /** From 91fb2a8f8cf0ca1296bd8e9b8db8881db7245e4a Mon Sep 17 00:00:00 2001 From: Hellfish Date: Mon, 1 Apr 2013 18:13:42 +0000 Subject: [PATCH 039/123] *Removed ColorIdentity field from ICardCharacteristics,CardRules and scripts. *Added experimental Oracle Color Identity parser. --- res/cardsfolder/b/bosh_iron_golem.txt | 1 - res/cardsfolder/m/memnarch.txt | 1 - res/cardsfolder/r/rhys_the_exiled.txt | 1 - res/cardsfolder/t/thelon_of_havenwood.txt | 1 - src/main/java/forge/card/CardFace.java | 12 +--- src/main/java/forge/card/CardRules.java | 56 ++++++++++++++++--- src/main/java/forge/card/CardRulesReader.java | 6 -- .../java/forge/card/ICardCharacteristics.java | 1 - 8 files changed, 48 insertions(+), 31 deletions(-) diff --git a/res/cardsfolder/b/bosh_iron_golem.txt b/res/cardsfolder/b/bosh_iron_golem.txt index 6128f26923a..635bebc6bef 100644 --- a/res/cardsfolder/b/bosh_iron_golem.txt +++ b/res/cardsfolder/b/bosh_iron_golem.txt @@ -1,6 +1,5 @@ Name:Bosh, Iron Golem ManaCost:8 -ColorIdentity:Red Types:Legendary Artifact Creature Golem PT:6/7 K:Trample diff --git a/res/cardsfolder/m/memnarch.txt b/res/cardsfolder/m/memnarch.txt index f1048bd1d7d..5e8efff8ee3 100644 --- a/res/cardsfolder/m/memnarch.txt +++ b/res/cardsfolder/m/memnarch.txt @@ -1,6 +1,5 @@ Name:Memnarch ManaCost:7 -ColorIdentity:Blue Types:Legendary Artifact Creature Wizard PT:4/5 A:AB$ Animate | Cost$ 1 U U | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | Types$ Artifact | Permanent$ True | SpellDescription$ Target permanent becomes an artifact in addition to its other types. (This effect lasts indefinitely.) diff --git a/res/cardsfolder/r/rhys_the_exiled.txt b/res/cardsfolder/r/rhys_the_exiled.txt index b46c8d02484..a6201bfb7c7 100644 --- a/res/cardsfolder/r/rhys_the_exiled.txt +++ b/res/cardsfolder/r/rhys_the_exiled.txt @@ -1,6 +1,5 @@ Name:Rhys the Exiled ManaCost:2 G -ColorIdentity:Green,Black Types:Legendary Creature Elf Warrior PT:3/2 T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigGainLife | TriggerDescription$ Whenever CARDNAME attacks, you gain 1 life for each Elf you control. diff --git a/res/cardsfolder/t/thelon_of_havenwood.txt b/res/cardsfolder/t/thelon_of_havenwood.txt index 9f9c3ac8c43..026df543744 100644 --- a/res/cardsfolder/t/thelon_of_havenwood.txt +++ b/res/cardsfolder/t/thelon_of_havenwood.txt @@ -1,6 +1,5 @@ Name:Thelon of Havenwood ManaCost:G G -ColorIdentity:Green,Black Types:Legendary Creature Elf Druid PT:2/2 S:Mode$ Continuous | Affected$ Creature.Fungus | AffectedZone$ Battlefield | AddPower$ AffectedX | AddToughness$ AffectedX | Description$ Each Fungus creature gets +1/+1 for each spore counter on it. diff --git a/src/main/java/forge/card/CardFace.java b/src/main/java/forge/card/CardFace.java index 9158c28d22b..dd2d75df76b 100644 --- a/src/main/java/forge/card/CardFace.java +++ b/src/main/java/forge/card/CardFace.java @@ -29,7 +29,6 @@ final class CardFace implements ICardFace { private CardType type = null; private ManaCost manaCost = ManaCost.NO_COST; private ColorSet color = null; - private ColorSet colorIdentity = null; private String oracleText = null; private int iPower = -1; @@ -59,15 +58,7 @@ final class CardFace implements ICardFace { @Override public CardType getType() { return this.type; } @Override public ManaCost getManaCost() { return this.manaCost; } @Override public ColorSet getColor() { return this.color; } - - @Override - public ColorSet getColorIdentity() { - if(this.colorIdentity != null) - return this.colorIdentity; - - return this.color; - } - + // these are raw and unparsed used for Card creation @Override public Iterable getKeywords() { return keywords; } @Override public Iterable getAbilities() { return abilities; } @@ -86,7 +77,6 @@ final class CardFace implements ICardFace { public void setType(CardType type0) { this.type = type0; } public void setManaCost(ManaCost manaCost0) { this.manaCost = manaCost0; } public void setColor(ColorSet color0) { this.color = color0; } - public void setColorIdentity(ColorSet color0) { this.colorIdentity = color0; } public void setOracleText(String text) { this.oracleText = text; } public void setInitialLoyalty(int value) { this.initialLoyalty = value; } diff --git a/src/main/java/forge/card/CardRules.java b/src/main/java/forge/card/CardRules.java index b7302e01037..2eb63c92df0 100644 --- a/src/main/java/forge/card/CardRules.java +++ b/src/main/java/forge/card/CardRules.java @@ -23,6 +23,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; +import forge.CardColor; import forge.card.mana.ManaCost; /** @@ -39,6 +40,8 @@ public final class CardRules implements ICardCharacteristics { private final Map setsPrinted = new TreeMap(String.CASE_INSENSITIVE_ORDER); private CardAiHints aiHints; + + private ColorSet colorIdentity = null; public CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah, Map sets) { splitType = altMode; @@ -57,6 +60,49 @@ public final class CardRules implements ICardCharacteristics { System.err.println(getName() + " was not assigned any set."); setsPrinted.put(CardEdition.UNKNOWN.getCode(), new CardInSet(CardRarity.Common, 1) ); } + + //Calculate Color Identity + byte colMask = calculateColorIdentity(mainPart); + + if(otherPart != null) + { + colMask |= calculateColorIdentity(otherPart); + } + colorIdentity = ColorSet.fromMask(colMask); + } + + private byte calculateColorIdentity(ICardFace face) + { + byte res = face.getManaCost() == null ? 0 : face.getManaCost().getColorProfile(); + boolean isReminder = false; + boolean isSymbol = false; + for(char c : face.getOracleText().toCharArray()) { + switch(c) + { + case('('): isReminder = true; break; + case(')'): isReminder = false; break; + case('{'): isSymbol = true; break; + case('}'): isSymbol = false; break; + default: + if(isSymbol && !isReminder) { + switch(c) + { + case('W'): res |= MagicColor.WHITE; break; + case('U'): res |= MagicColor.BLUE; break; + case('B'): res |= MagicColor.BLACK; break; + case('R'): res |= MagicColor.RED; break; + case('G'): res |= MagicColor.GREEN; break; + } + } + else + { + continue; + } + break; + } + } + + return res; } public boolean isTraditional() { @@ -178,16 +224,8 @@ public final class CardRules implements ICardCharacteristics { return null; } - @Override public ColorSet getColorIdentity() { - if(this.otherPart != null) - { - return ColorSet.fromMask(mainPart.getColorIdentity().getColor() | otherPart.getColorIdentity().getColor()); - } - else - { - return mainPart.getColorIdentity(); - } + return colorIdentity; } } diff --git a/src/main/java/forge/card/CardRulesReader.java b/src/main/java/forge/card/CardRulesReader.java index 9b57188c3fb..fb1ff50e9c2 100644 --- a/src/main/java/forge/card/CardRulesReader.java +++ b/src/main/java/forge/card/CardRulesReader.java @@ -138,12 +138,6 @@ public class CardRulesReader { ColorSet newCol = ColorSet.fromNames(value.split(",")); this.faces[this.curFace].setColor(newCol); } - else if ("ColorIdentity".equals(key)) { - // This is forge.card.CardColor not forge.CardColor. - // Why do we have two classes with the same name? - ColorSet newCol = ColorSet.fromNames(value.split(",")); - this.faces[this.curFace].setColorIdentity(newCol); - } break; case 'D': diff --git a/src/main/java/forge/card/ICardCharacteristics.java b/src/main/java/forge/card/ICardCharacteristics.java index b38923225be..3b97955f77d 100644 --- a/src/main/java/forge/card/ICardCharacteristics.java +++ b/src/main/java/forge/card/ICardCharacteristics.java @@ -7,7 +7,6 @@ public interface ICardCharacteristics { CardType getType(); ManaCost getManaCost(); ColorSet getColor(); - ColorSet getColorIdentity(); int getIntPower(); int getIntToughness(); From dec594f7d0abfc58c042bfea1be92adc95ed166f Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 18:35:45 +0000 Subject: [PATCH 040/123] Won't crash if precon deck didn't have the 'set' value --- src/main/java/forge/item/PreconDeck.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/forge/item/PreconDeck.java b/src/main/java/forge/item/PreconDeck.java index f5966a3054d..c35f0b9c602 100644 --- a/src/main/java/forge/item/PreconDeck.java +++ b/src/main/java/forge/item/PreconDeck.java @@ -77,17 +77,14 @@ public class PreconDeck implements InventoryItemFromSet { final Map> sections = FileSection.parseSections(deckLines); this.deck = Deck.fromSections(sections); - String setProxy = "n/a"; + FileSection kv = FileSection.parse(sections.get("metadata"), "="); imageFilename = kv.get("Image"); description = kv.get("Description"); - if (Singletons.getModel().getEditions().get(kv.get("set").toUpperCase()) != null) { - setProxy = kv.get("set"); - } - - this.set = setProxy; + String deckEdition = kv.get("set"); + this.set = deckEdition == null || Singletons.getModel().getEditions().get(deckEdition.toUpperCase()) == null ? "n/a" : deckEdition; this.recommendedDeals = new SellRules(sections.get("shop")); } From 5f19620d82b7d8258ccf2b9e612628714c55d387 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 19:57:58 +0000 Subject: [PATCH 041/123] iteration of all subabilities to select targets is made in spellability/SpellAbilityRequirements.java input/InputSelectTargets.java doesn't ask for more targets if all damage (or other thing) has been already 'divided as you choose' TargetSelection does not use cancel, it returns false instead --- src/main/java/forge/card/CardRules.java | 37 +++--- .../forge/card/spellability/SpellAbility.java | 13 +- .../SpellAbilityRequirements.java | 45 ++++--- .../card/spellability/TargetSelection.java | 121 ++++++------------ .../control/input/InputSelectTargets.java | 2 +- 5 files changed, 89 insertions(+), 129 deletions(-) diff --git a/src/main/java/forge/card/CardRules.java b/src/main/java/forge/card/CardRules.java index 2eb63c92df0..093581f542c 100644 --- a/src/main/java/forge/card/CardRules.java +++ b/src/main/java/forge/card/CardRules.java @@ -23,7 +23,6 @@ import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; -import forge.CardColor; import forge.card.mana.ManaCost; /** @@ -76,32 +75,28 @@ public final class CardRules implements ICardCharacteristics { byte res = face.getManaCost() == null ? 0 : face.getManaCost().getColorProfile(); boolean isReminder = false; boolean isSymbol = false; - for(char c : face.getOracleText().toCharArray()) { - switch(c) - { - case('('): isReminder = true; break; - case(')'): isReminder = false; break; - case('{'): isSymbol = true; break; - case('}'): isSymbol = false; break; - default: + String oracleText = face.getOracleText(); + int len = oracleText.length(); + for(int i = 0; i < len; i++) { + char c = oracleText.charAt(i); // This is to avoid needless allocations performed by toCharArray() + switch(c) { + case('('): isReminder = true; break; + case(')'): isReminder = false; break; + case('{'): isSymbol = true; break; + case('}'): isSymbol = false; break; + default: if(isSymbol && !isReminder) { - switch(c) - { - case('W'): res |= MagicColor.WHITE; break; - case('U'): res |= MagicColor.BLUE; break; - case('B'): res |= MagicColor.BLACK; break; - case('R'): res |= MagicColor.RED; break; - case('G'): res |= MagicColor.GREEN; break; + switch(c) { + case('W'): res |= MagicColor.WHITE; break; + case('U'): res |= MagicColor.BLUE; break; + case('B'): res |= MagicColor.BLACK; break; + case('R'): res |= MagicColor.RED; break; + case('G'): res |= MagicColor.GREEN; break; } } - else - { - continue; - } break; } } - return res; } diff --git a/src/main/java/forge/card/spellability/SpellAbility.java b/src/main/java/forge/card/spellability/SpellAbility.java index e8b29b17765..872e0de87ea 100644 --- a/src/main/java/forge/card/spellability/SpellAbility.java +++ b/src/main/java/forge/card/spellability/SpellAbility.java @@ -1687,16 +1687,15 @@ public abstract class SpellAbility implements ISpellAbility { * the ability * @return the unique targets */ - public final ArrayList getUniqueTargets() { - final ArrayList targets = new ArrayList(); - SpellAbility child = this; - while (child instanceof AbilitySub) { - child = ((AbilitySub) child).getParent(); - if (child != null && child.getTarget() != null) { + public final List getUniqueTargets() { + final List targets = new ArrayList(); + SpellAbility child = this.getParent(); + while (child != null) { + if (child.getTarget() != null) { targets.addAll(child.getTarget().getTargets()); } + child = child.getParent(); } - return targets; } diff --git a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java index ab6505b2f3a..114297c078e 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java +++ b/src/main/java/forge/card/spellability/SpellAbilityRequirements.java @@ -66,22 +66,28 @@ public class SpellAbilityRequirements { // Announce things like how many times you want to Multikick or the value of X if (!this.announceRequirements()) { - rollbackAbility(fromZone, zonePosition, null); + rollbackAbility(fromZone, zonePosition); return; } - final TargetSelection select = new TargetSelection(ability); // Skip to paying if parent ability doesn't target and has no // subAbilities. // (or trigger case where its already targeted) - boolean acceptsTargets = select.doesTarget() || this.ability.getSubAbility() != null; - if (!isAlreadyTargeted && acceptsTargets) { - select.clearTargets(); - select.chooseTargets(); - if (select.isCanceled()) { - rollbackAbility(fromZone, zonePosition, select); - return; - } + if (!isAlreadyTargeted) { + + SpellAbility beingTargeted = ability; + do { + Target tgt = beingTargeted.getTarget(); + if( tgt != null && tgt.doesTarget()) { + clearTargets(beingTargeted); + final TargetSelection select = new TargetSelection(beingTargeted); + if (!select.chooseTargets() ) { + rollbackAbility(fromZone, zonePosition); + return; + } + } + beingTargeted = beingTargeted.getSubAbility(); + } while (beingTargeted != null); } // Payment @@ -93,7 +99,7 @@ public class SpellAbilityRequirements { } if (!paymentMade) { - rollbackAbility(fromZone, zonePosition, select); + rollbackAbility(fromZone, zonePosition); return; } @@ -101,19 +107,26 @@ public class SpellAbilityRequirements { if (skipStack) { AbilityUtils.resolve(this.ability, false); } else { - this.enusureAbilityHasDescription(this.ability); this.ability.getActivatingPlayer().getManaPool().clearManaPaid(this.ability, false); game.getStack().addAndUnfreeze(this.ability); } // no worries here. The same thread must resolve, and by this moment ability will have been resolved already - select.clearTargets(); + clearTargets(ability); game.getAction().checkStateEffects(); } } - private void rollbackAbility(Zone fromZone, int zonePosition, TargetSelection select) { + public final void clearTargets(SpellAbility ability) { + Target tg = ability.getTarget(); + if (tg != null) { + tg.resetTargets(); + tg.calculateStillToDivide(ability.getParam("DividedAsYouChoose"), ability.getSourceCard(), ability); + } + } + + private void rollbackAbility(Zone fromZone, int zonePosition) { // cancel ability during target choosing final Card c = this.ability.getSourceCard(); @@ -127,9 +140,7 @@ public class SpellAbilityRequirements { Singletons.getModel().getGame().getAction().moveTo(fromZone, c, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); } - if (select != null) { - select.clearTargets(); - } + clearTargets(ability); this.ability.resetOnceResolved(); this.payment.refundPayment(); diff --git a/src/main/java/forge/card/spellability/TargetSelection.java b/src/main/java/forge/card/spellability/TargetSelection.java index 36414ec9e29..d9363abf0c4 100644 --- a/src/main/java/forge/card/spellability/TargetSelection.java +++ b/src/main/java/forge/card/spellability/TargetSelection.java @@ -18,7 +18,6 @@ package forge.card.spellability; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import com.google.common.base.Predicate; @@ -54,108 +53,63 @@ public class TargetSelection { return this.ability.getTarget(); } - private final Card getCard() { - return this.ability.getSourceCard(); - } - - private TargetSelection subSelection = null; - - private boolean bCancel = false; private boolean bTargetingDone = false; - /** - *

- * setCancel. - *

- * - * @param done - * a boolean. - */ - public final void setCancel(final boolean done) { - this.bCancel = done; - } - - /** - *

- * isCanceled. - *

- * - * @return a boolean. - */ - public final boolean isCanceled() { - return this.bCancel || this.subSelection != null && this.subSelection.isCanceled(); - } - - public final boolean doesTarget() { - Target tg = getTgt(); - return tg != null && tg.doesTarget(); - } /** *

* resetTargets. *

*/ - public final void clearTargets() { - Target tg = getTgt(); - if (tg != null) { - tg.resetTargets(); - tg.calculateStillToDivide(this.ability.getParam("DividedAsYouChoose"), this.getCard(), this.ability); - } - } public final boolean chooseTargets() { Target tgt = getTgt(); - final boolean canTarget = tgt == null ? false : doesTarget(); - final int minTargets = canTarget ? tgt.getMinTargets(getCard(), ability) : 0; - final int maxTargets = canTarget ? tgt.getMaxTargets(getCard(), ability) : 0; - final int numTargeted = canTarget ? tgt.getNumTargeted() : 0; + final boolean canTarget = tgt != null && tgt.doesTarget(); + if( !canTarget ) + throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability); + + final int minTargets = tgt.getMinTargets(ability.getSourceCard(), ability); + final int maxTargets = tgt.getMaxTargets(ability.getSourceCard(), ability); + final int numTargeted = tgt.getNumTargeted(); boolean hasEnoughTargets = minTargets == 0 || numTargeted >= minTargets; boolean hasAllTargets = numTargeted == maxTargets && maxTargets > 0; - // if not enough targets chosen, reset and cancel Ability - if (this.bTargetingDone && !hasEnoughTargets) this.bCancel = true; - if (this.bCancel) return false; + // if not enough targets chosen, cancel Ability + if (this.bTargetingDone && !hasEnoughTargets) + return false; - if (!canTarget || this.bTargetingDone && hasEnoughTargets || hasAllTargets || tgt.isDividedAsYouChoose() && tgt.getStillToDivide() == 0) { - final AbilitySub abSub = this.ability.getSubAbility(); - if (abSub == null) // if no more SubAbilities finish targeting - return true; - - // Has Sub Ability - this.subSelection = new TargetSelection(abSub); - this.subSelection.clearTargets(); - return this.subSelection.chooseTargets(); + if (this.bTargetingDone && hasEnoughTargets || hasAllTargets || tgt.isDividedAsYouChoose() && tgt.getStillToDivide() == 0) { + return true; } if (!tgt.hasCandidates(this.ability, true) && !hasEnoughTargets) { // Cancel ability if there aren't any valid Candidates - this.bCancel = true; return false; } final List zone = tgt.getZone(); final boolean mandatory = tgt.getMandatory() && tgt.hasCandidates(this.ability, true); + final boolean choiceResult; if (zone.size() == 1 && zone.get(0) == ZoneType.Stack) { // If Zone is Stack, the choices are handled slightly differently - this.chooseCardFromStack(mandatory); + choiceResult = this.chooseCardFromStack(mandatory); } else { - List validTargets = this.chooseValidInput(); + List validTargets = this.getValidCardsToTarget(); if (zone.size() == 1 && zone.get(0) == ZoneType.Battlefield) { InputSelectTargets inp = new InputSelectTargets(validTargets, ability, mandatory); FThreads.setInputAndWait(inp); - bCancel = inp.hasCancelled(); + choiceResult = !inp.hasCancelled(); bTargetingDone = inp.hasPressedOk(); } else { - this.chooseCardFromList(validTargets, true, mandatory); + // for every other case an all-purpose GuiChoose + choiceResult = this.chooseCardFromList(validTargets, true, mandatory); } } - // some inputs choose cards 1-by-1 and need to be called again, - // moreover there are sub-abilities that also need targets - return chooseTargets(); + // some inputs choose cards one-by-one and need to be called again + return choiceResult && chooseTargets(); } @@ -171,13 +125,14 @@ public class TargetSelection { *

* @return */ - private final List chooseValidInput() { + private final List getValidCardsToTarget() { final Target tgt = this.getTgt(); final GameState game = ability.getActivatingPlayer().getGame(); final List zone = tgt.getZone(); final boolean canTgtStack = zone.contains(ZoneType.Stack); - List choices = CardLists.getTargetableCards(CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()), this.ability); + List validCards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getSourceCard()); + List choices = CardLists.getTargetableCards(validCards, this.ability); if (canTgtStack) { // Since getTargetableCards doesn't have additional checks if one of the Zones is stack // Remove the activating card from targeting itself if its on the Stack @@ -186,11 +141,11 @@ public class TargetSelection { choices.remove(tgt.getSourceCard()); } } - ArrayList objects = this.ability.getUniqueTargets(); + List targetedObjects = this.ability.getUniqueTargets(); if (tgt.isUniqueTargets()) { - for (final Object o : objects) { - if ((o instanceof Card) && objects.contains(o)) { + for (final Object o : targetedObjects) { + if ((o instanceof Card) && targetedObjects.contains(o)) { choices.remove(o); } } @@ -205,9 +160,9 @@ public class TargetSelection { } // If all cards (including subability targets) must have the same controller - if (tgt.isSameController() && !objects.isEmpty()) { + if (tgt.isSameController() && !targetedObjects.isEmpty()) { final List list = new ArrayList(); - for (final Object o : objects) { + for (final Object o : targetedObjects) { if (o instanceof Card) { list.add((Card) o); } @@ -251,7 +206,7 @@ public class TargetSelection { } // If the cards must have a specific controller if (tgt.getDefinedController() != null) { - List pl = AbilityUtils.getDefinedPlayers(getCard(), tgt.getDefinedController(), this.ability); + List pl = AbilityUtils.getDefinedPlayers(ability.getSourceCard(), tgt.getDefinedController(), this.ability); if (pl != null && !pl.isEmpty()) { Player controller = pl.get(0); choices = CardLists.filterControlledBy(choices, controller); @@ -260,7 +215,7 @@ public class TargetSelection { } } return choices; - } // input_targetValid + } /** *

@@ -274,7 +229,7 @@ public class TargetSelection { * @param mandatory * a boolean. */ - private final void chooseCardFromList(final List choices, final boolean targeted, final boolean mandatory) { + private final boolean chooseCardFromList(final List choices, final boolean targeted, final boolean mandatory) { // Send in a list of valid cards, and popup a choice box to target final GameState game = ability.getActivatingPlayer().getGame(); @@ -321,16 +276,16 @@ public class TargetSelection { final Object chosen = GuiChoose.oneOrNone(getTgt().getVTSelection(), choicesFiltered); if (chosen == null) { - this.setCancel(true); - return; + return false; } if (msgDone.equals(chosen)) { bTargetingDone = true; - return; + return true; } if (chosen instanceof Card ) this.getTgt().addTarget(chosen); + return true; } /** @@ -341,7 +296,7 @@ public class TargetSelection { * @param mandatory * a boolean. */ - private final void chooseCardFromStack(final boolean mandatory) { + private final boolean chooseCardFromStack(final boolean mandatory) { final Target tgt = this.getTgt(); final String message = tgt.getVTSelection(); // Find what's targetable, then allow human to choose @@ -359,18 +314,18 @@ public class TargetSelection { } if (selectOptions.isEmpty()) { - setCancel(true); + return false; } else { final Object madeChoice = GuiChoose.oneOrNone(message, selectOptions); if (madeChoice == null) { - setCancel(true); - return; + return false; } if (madeChoice instanceof SpellAbility) { tgt.addTarget(madeChoice); } else // only 'FINISH TARGETING' remains bTargetingDone = true; } + return true; } /** diff --git a/src/main/java/forge/control/input/InputSelectTargets.java b/src/main/java/forge/control/input/InputSelectTargets.java index 3d5462a7a0b..91e16e00d23 100644 --- a/src/main/java/forge/control/input/InputSelectTargets.java +++ b/src/main/java/forge/control/input/InputSelectTargets.java @@ -207,6 +207,6 @@ public final class InputSelectTargets extends InputSyncronizedBase { } boolean hasAllTargets() { - return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa); + return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa) || tgt.getStillToDivide() == 0; } } \ No newline at end of file From 7009df7ea08ea1de13016cab670dad7a403195cc Mon Sep 17 00:00:00 2001 From: Hellfish Date: Mon, 1 Apr 2013 20:01:47 +0000 Subject: [PATCH 042/123] *Fixed Color Identity for cards like Transguild Courier and DFC's. --- src/main/java/forge/card/CardRules.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/forge/card/CardRules.java b/src/main/java/forge/card/CardRules.java index 093581f542c..6d4ae58380a 100644 --- a/src/main/java/forge/card/CardRules.java +++ b/src/main/java/forge/card/CardRules.java @@ -72,7 +72,7 @@ public final class CardRules implements ICardCharacteristics { private byte calculateColorIdentity(ICardFace face) { - byte res = face.getManaCost() == null ? 0 : face.getManaCost().getColorProfile(); + byte res = face.getColor().getColor(); boolean isReminder = false; boolean isSymbol = false; String oracleText = face.getOracleText(); From e1d6825341a40af9dcaafb20e1450bd7fbceec0a Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 20:35:14 +0000 Subject: [PATCH 043/123] fixed script for castigate --- res/cardsfolder/c/castigate.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/cardsfolder/c/castigate.txt b/res/cardsfolder/c/castigate.txt index b8c90e3bbab..19cb2ae1d12 100644 --- a/res/cardsfolder/c/castigate.txt +++ b/res/cardsfolder/c/castigate.txt @@ -1,7 +1,7 @@ Name:Castigate ManaCost:W B Types:Sorcery -A:SP$ ChangeZone | Cost$ W B | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a nonland card from it and exile that card. +A:SP$ ChangeZone | Cost$ W B | Origin$ Hand | Destination$ Exile | DefinedPlayer$ Targeted | ValidTgts$ Opponent | Chooser$ You | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a nonland card from it and exile that card. SVar:Picture:http://www.wizards.com/global/images/magic/general/castigate.jpg Oracle:Target opponent reveals his or her hand. You choose a nonland card from it and exile that card. SetInfo:GPT Common \ No newline at end of file From 7666f67067fda462121a6dac26ed4d644abc1696 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 20:56:37 +0000 Subject: [PATCH 044/123] InputSelectTargets - added targeted cards' highlighting, corrected target selection for divide as you choose --- .../ability/effects/ChangeZoneEffect.java | 5 ++-- .../control/input/InputSelectTargets.java | 23 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java index fb0943e0caf..c5372498ff5 100644 --- a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java +++ b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java @@ -22,6 +22,7 @@ import forge.card.spellability.Target; import forge.card.trigger.TriggerType; import forge.game.ai.ComputerUtilCard; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; @@ -527,7 +528,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (decider instanceof AIPlayer) { ChangeZoneAi.hiddenOriginResolveAI(decider, sa, player); } else { - changeHiddenOriginResolveHuman(sa, player); + changeHiddenOriginResolveHuman((HumanPlayer)decider, sa, player); } } } @@ -544,7 +545,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { * @param player * a {@link forge.game.player.Player} object. */ - private static void changeHiddenOriginResolveHuman(final SpellAbility sa, Player player) { + private static void changeHiddenOriginResolveHuman(final HumanPlayer decider, final SpellAbility sa, Player player) { final Card card = sa.getSourceCard(); final List movedCards = new ArrayList(); final boolean defined = sa.hasParam("Defined"); diff --git a/src/main/java/forge/control/input/InputSelectTargets.java b/src/main/java/forge/control/input/InputSelectTargets.java index 91e16e00d23..70bfe4d5710 100644 --- a/src/main/java/forge/control/input/InputSelectTargets.java +++ b/src/main/java/forge/control/input/InputSelectTargets.java @@ -189,8 +189,11 @@ public final class InputSelectTargets extends InputSyncronizedBase { addTarget(player); } - void addTarget(GameEntity ge) { + private void addTarget(GameEntity ge) { tgt.addTarget(ge); + if(ge instanceof Card) { + ((Card) ge).setUsedToPay(true); + } Integer val = targetDepth.get(ge); targetDepth.put(ge, val == null ? Integer.valueOf(1) : Integer.valueOf(val.intValue() + 1) ); @@ -202,11 +205,23 @@ public final class InputSelectTargets extends InputSyncronizedBase { this.showMessage(); } - void done() { + /* (non-Javadoc) + * @see forge.control.input.InputSyncronizedBase#afterStop() + */ + @Override + protected void afterStop() { + for(GameEntity c : targetDepth.keySet()) + if( c instanceof Card) + ((Card)c).setUsedToPay(false); + super.afterStop(); + + } + + private void done() { this.stop(); } - boolean hasAllTargets() { - return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa) || tgt.getStillToDivide() == 0; + private boolean hasAllTargets() { + return tgt.isMaxTargetsChosen(sa.getSourceCard(), sa) || ( tgt.getStillToDivide() == 0 && tgt.isDividedAsYouChoose()); } } \ No newline at end of file From e797ce337ac081392b39cdbb61fa0f74d68ccdb1 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 21:17:31 +0000 Subject: [PATCH 045/123] removed couple of methods, whose results can be derived from other members --- .../cardfactory/CardFactoryCreatures.java | 2 +- .../card/cardfactory/CardFactoryUtil.java | 2 -- .../forge/card/spellability/SpellAbility.java | 31 ++----------------- .../forge/card/trigger/WrappedAbility.java | 5 --- 4 files changed, 4 insertions(+), 36 deletions(-) diff --git a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java index 6ed71f5c20c..eef6eaafcfe 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java @@ -316,7 +316,7 @@ public class CardFactoryCreatures { c.addCounter(CounterType.P1P1, xCounters, true); } }; - spell.setIsXCost(true); + spell.setXManaCost(1); // Do not remove SpellAbilities created by AbilityFactory or // Keywords. card.clearFirstSpell(); diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index 3ded88f444a..687f167189a 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -2394,7 +2394,6 @@ public class CardFactoryUtil { final String[] k = parse.split("kicker "); final SpellAbility sa = card.getSpellAbility()[0]; - sa.setIsMultiKicker(true); sa.setMultiKickerManaCost(new ManaCost(new ManaCostParser(k[1]))); } } @@ -2557,7 +2556,6 @@ public class CardFactoryUtil { int xCount = card.getManaCost().getShardCount(ManaCostShard.X); if (xCount > 0) { final SpellAbility sa = card.getSpellAbility()[0]; - sa.setIsXCost(true); sa.setXManaCost(xCount); } // X diff --git a/src/main/java/forge/card/spellability/SpellAbility.java b/src/main/java/forge/card/spellability/SpellAbility.java index 872e0de87ea..8fcd9d76429 100644 --- a/src/main/java/forge/card/spellability/SpellAbility.java +++ b/src/main/java/forge/card/spellability/SpellAbility.java @@ -74,9 +74,7 @@ public abstract class SpellAbility implements ISpellAbility { private boolean temporarilySuppressed = false; private boolean flashBackAbility = false; - private boolean multiKicker = false; private boolean replicate = false; - private boolean xCost = false; private boolean cycling = false; private boolean delve = false; @@ -387,19 +385,7 @@ public abstract class SpellAbility implements ISpellAbility { return false; } - /** - *

- * setIsMultiKicker. - *

- * - * @param b - * a boolean. - */ - public final void setIsMultiKicker(final boolean b) { - this.multiKicker = b; - } - - /** + /** *

* isMultiKicker. *

@@ -407,7 +393,7 @@ public abstract class SpellAbility implements ISpellAbility { * @return a boolean. */ public boolean isMultiKicker() { - return this.multiKicker; + return this.multiKickerManaCost != null; } /** @@ -433,17 +419,6 @@ public abstract class SpellAbility implements ISpellAbility { return this.replicate; } - /** - *

- * setIsXCost. - *

- * - * @param b - * a boolean. - */ - public void setIsXCost(final boolean b) { - this.xCost = b; - } /** *

@@ -453,7 +428,7 @@ public abstract class SpellAbility implements ISpellAbility { * @return a boolean. */ public boolean isXCost() { - return this.xCost; + return getXManaCost() > 0; } /** diff --git a/src/main/java/forge/card/trigger/WrappedAbility.java b/src/main/java/forge/card/trigger/WrappedAbility.java index f679f1cdf3f..e98286e78a7 100644 --- a/src/main/java/forge/card/trigger/WrappedAbility.java +++ b/src/main/java/forge/card/trigger/WrappedAbility.java @@ -284,11 +284,6 @@ public class WrappedAbility extends Ability implements ISpellAbility { sa.setFlashBackAbility(flashBackAbility); } - @Override - public void setIsXCost(final boolean b) { - sa.setIsXCost(b); - } - @Override public void setMultiKickerManaCost(final ManaCost cost) { sa.setMultiKickerManaCost(cost); From 998c8dae8b0fc5558a927cc608a1c97965c657e7 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 21:18:33 +0000 Subject: [PATCH 046/123] minor - write a logical sentence in a single line --- src/main/java/forge/game/GameAction.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index 49371a05d0c..23c08a68205 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -127,12 +127,7 @@ public class GameAction { return c; } - boolean suppress; - if (c.isToken()) { - suppress = false; - } else { - suppress = zoneFrom.equals(zoneTo); - } + boolean suppress = !c.isToken() && zoneFrom.equals(zoneTo); Card copied = null; Card lastKnownInfo = null; From 49f288197dad818853aa90c3ce065375f893da08 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Mon, 1 Apr 2013 21:49:12 +0000 Subject: [PATCH 047/123] Added player's loss condition for 21 damage from generals --- src/main/java/forge/Card.java | 11 +++++++- src/main/java/forge/game/GameLossReason.java | 2 ++ src/main/java/forge/game/player/Player.java | 25 ++++++++----------- .../java/forge/game/player/PlayerOutcome.java | 1 + 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index bfbf60bfa51..ad0eeb19c50 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -9137,6 +9137,15 @@ public class Card extends GameEntity implements Comparable { } public void setRules(CardRules r) { cardRules = r; - } + } + + /** + * TODO: Write javadoc for this method. + * @return + */ + public boolean isEdhGeneral() { + // TODO - have a field + return false; + } } // end Card class diff --git a/src/main/java/forge/game/GameLossReason.java b/src/main/java/forge/game/GameLossReason.java index 652ccc90b70..05700eb0f1f 100644 --- a/src/main/java/forge/game/GameLossReason.java +++ b/src/main/java/forge/game/GameLossReason.java @@ -33,6 +33,8 @@ public enum GameLossReason { // 104.3e and others /** The Spell effect. */ SpellEffect, + + EdhGeneralDamage, OpponentWon diff --git a/src/main/java/forge/game/player/Player.java b/src/main/java/forge/game/player/Player.java index a382ed932c1..4f9538a2fde 100644 --- a/src/main/java/forge/game/player/Player.java +++ b/src/main/java/forge/game/player/Player.java @@ -88,6 +88,8 @@ public abstract class Player extends GameEntity implements Comparable { /** The poison counters. */ private int poisonCounters = 0; + private int edhGeneralDamage = 0; + /** The life. */ private int life = 20; @@ -641,7 +643,10 @@ public abstract class Player extends GameEntity implements Comparable { } } - this.addAssignedDamage(damageToDo, source); + this.assignedDamage.put(source, damageToDo); + if(source.isEdhGeneral()) + this.edhGeneralDamage+= damageToDo; + GameActionUtil.executeDamageDealingEffects(source, damageToDo); GameActionUtil.executeDamageToPlayerEffects(this, source, damageToDo); @@ -916,20 +921,6 @@ public abstract class Player extends GameEntity implements Comparable { this.assignedDamage.clear(); } - /** - *

- * addAssignedDamage. - *

- * - * @param n - * a int. - * @param source - * a {@link forge.Card} object. - */ - public final void addAssignedDamage(final int n, final Card source) { - this.assignedDamage.put(source, n); - } - /** *

* Getter for the field assignedDamage. @@ -2162,6 +2153,10 @@ public abstract class Player extends GameEntity implements Comparable { if (hasNoLife && !this.cantLoseForZeroOrLessLife()) { return this.loseConditionMet(GameLossReason.LifeReachedZero, null); } + + if(this.edhGeneralDamage >= 21) { + return this.loseConditionMet(GameLossReason.EdhGeneralDamage, null); + } return false; } diff --git a/src/main/java/forge/game/player/PlayerOutcome.java b/src/main/java/forge/game/player/PlayerOutcome.java index 05ce59e09f0..73a6926c2b1 100644 --- a/src/main/java/forge/game/player/PlayerOutcome.java +++ b/src/main/java/forge/game/player/PlayerOutcome.java @@ -72,6 +72,7 @@ public class PlayerOutcome { case Poisoned: return "lost because of obtaining 10 poison counters"; case OpponentWon: return "lost because an opponent has won by spell '" + loseConditionSpell + "'"; case SpellEffect: return "lost due to effect of spell '" + loseConditionSpell + "'"; + case EdhGeneralDamage: return "lost due to accumulation of 21 damage from generals"; } return "lost for unknown reason (this is a bug)"; } From 3714ccc14a91cd1ae41931eb498b153adbdd00c7 Mon Sep 17 00:00:00 2001 From: swordshine Date: Tue, 2 Apr 2013 00:48:12 +0000 Subject: [PATCH 048/123] - CounterEffect: Renamed "RememberTargets" (we have another "RememberTargets" in AbilityUtils) - Added Fold into AEther, Hinder, and Swift Silence --- .gitattributes | 3 +++ res/cardsfolder/f/fold_into_aether.txt | 9 +++++++++ res/cardsfolder/h/hinder.txt | 7 +++++++ res/cardsfolder/s/spelljack.txt | 2 +- res/cardsfolder/s/swift_silence.txt | 11 +++++++++++ res/cardsfolder/t/trickbind.txt | 2 +- .../card/ability/effects/CounterEffect.java | 16 +++++++++++++--- 7 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 res/cardsfolder/f/fold_into_aether.txt create mode 100644 res/cardsfolder/h/hinder.txt create mode 100644 res/cardsfolder/s/swift_silence.txt diff --git a/.gitattributes b/.gitattributes index 99e09970f8a..10ced0e1922 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3790,6 +3790,7 @@ res/cardsfolder/f/fog_bank.txt svneol=native#text/plain res/cardsfolder/f/fog_elemental.txt svneol=native#text/plain res/cardsfolder/f/fog_of_gnats.txt svneol=native#text/plain res/cardsfolder/f/foil.txt -text +res/cardsfolder/f/fold_into_aether.txt -text res/cardsfolder/f/folk_medicine.txt svneol=native#text/plain res/cardsfolder/f/folk_of_an_havva.txt svneol=native#text/plain res/cardsfolder/f/folk_of_the_pines.txt svneol=native#text/plain @@ -4902,6 +4903,7 @@ res/cardsfolder/h/higure_the_still_wind_avatar.txt -text res/cardsfolder/h/hikari_twilight_guardian.txt -text res/cardsfolder/h/hill_giant.txt svneol=native#text/plain res/cardsfolder/h/hillcomber_giant.txt svneol=native#text/plain +res/cardsfolder/h/hinder.txt -text res/cardsfolder/h/hindering_light.txt svneol=native#text/plain res/cardsfolder/h/hindering_touch.txt svneol=native#text/plain res/cardsfolder/h/hindervines.txt -text @@ -10567,6 +10569,7 @@ res/cardsfolder/s/swell_of_courage.txt svneol=native#text/plain res/cardsfolder/s/swelter.txt svneol=native#text/plain res/cardsfolder/s/swift_justice.txt -text res/cardsfolder/s/swift_maneuver.txt svneol=native#text/plain +res/cardsfolder/s/swift_silence.txt -text res/cardsfolder/s/swiftfoot_boots.txt svneol=native#text/plain res/cardsfolder/s/swirling_sandstorm.txt svneol=native#text/plain res/cardsfolder/s/swirling_spriggan.txt -text diff --git a/res/cardsfolder/f/fold_into_aether.txt b/res/cardsfolder/f/fold_into_aether.txt new file mode 100644 index 00000000000..7f337309a76 --- /dev/null +++ b/res/cardsfolder/f/fold_into_aether.txt @@ -0,0 +1,9 @@ +Name:Fold into AEther +ManaCost:2 U U +Types:Instant +A:SP$ Counter | Cost$ 2 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | SubAbility$ DBChangeZone | SpellDescription$ Counter target spell. If that spell is countered this way, its controller may put a creature card from his or her hand onto the battlefield. +SVar:DBChangeZone:DB$ ChangeZone | ChangeType$ Creature | Origin$ Hand | Destination$ Battlefield | ChangeNum$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/fold_into_aether.jpg +Oracle:Counter target spell. If that spell is countered this way, its controller may put a creature card from his or her hand onto the battlefield. +SetInfo:5DN Uncommon \ No newline at end of file diff --git a/res/cardsfolder/h/hinder.txt b/res/cardsfolder/h/hinder.txt new file mode 100644 index 00000000000..807dbbd58a0 --- /dev/null +++ b/res/cardsfolder/h/hinder.txt @@ -0,0 +1,7 @@ +Name:Hinder +ManaCost:1 U U +Types:Instant +A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | DestinationChoice$ BottomOfLibrary,TopOfLibrary | SpellDescription$ Counter target spell. If that spell is countered this way, put that card on the top or bottom of its owner's library instead of into that player's graveyard. +SVar:Picture:http://www.wizards.com/global/images/magic/general/hinder.jpg +Oracle:Counter target spell. If that spell is countered this way, put that card on the top or bottom of its owner's library instead of into that player's graveyard. +SetInfo:CHK Uncommon \ No newline at end of file diff --git a/res/cardsfolder/s/spelljack.txt b/res/cardsfolder/s/spelljack.txt index 1aedf658b6e..d9f36a757d0 100644 --- a/res/cardsfolder/s/spelljack.txt +++ b/res/cardsfolder/s/spelljack.txt @@ -1,7 +1,7 @@ Name:Spelljack ManaCost:3 U U U Types:Instant -A:SP$ Counter | Cost$ 3 U U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberTargets$ True | ForgetOtherTargets$ True | Destination$ Exile | SubAbility$ DBEffect | SpellDescription$ Counter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. You may play it without paying its mana cost for as long as it remains exiled. (If it has X in its mana cost, X is 0.) +A:SP$ Counter | Cost$ 3 U U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | ForgetOtherTargets$ True | Destination$ Exile | SubAbility$ DBEffect | SpellDescription$ Counter target spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyard. You may play it without paying its mana cost for as long as it remains exiled. (If it has X in its mana cost, X is 0.) SVar:DBEffect:DB$ Effect | Name$ Spelljack Effect | RememberObjects$ Remembered | StaticAbilities$ PlayOpp,PlayYou | Duration$ Permanent | Triggers$ TrigCleanup | SVars$ DBCleanup | References$ PlayOpp,PlayYou,TrigCleanup,DBCleanup SVar:PlayOpp:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+OppOwn | AffectedZone$ Exile | AddHiddenKeyword$ May be played by your opponent without paying its mana cost | Description$ You may play cards exiled with Spelljack. SVar:PlayYou:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+YouOwn | AffectedZone$ Exile | AddHiddenKeyword$ May be played without paying its mana cost diff --git a/res/cardsfolder/s/swift_silence.txt b/res/cardsfolder/s/swift_silence.txt new file mode 100644 index 00000000000..890e5e1a7c0 --- /dev/null +++ b/res/cardsfolder/s/swift_silence.txt @@ -0,0 +1,11 @@ +Name:Swift Silence +ManaCost:2 W U U +Types:Instant +A:SP$ Counter | Cost$ 2 W U U | AllType$ Spell | AllValid$ Card.Other | RememberCountered$ True | SubAbility$ DBDraw | SpellDescription$ Counter all other spells. Draw a card for each spell countered this way. +SVar:DBDraw:DB$ Draw | NumCards$ X | References$ X | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Remembered$Amount +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/swift_silence.jpg +Oracle:Counter all other spells. Draw a card for each spell countered this way. +SetInfo:DIS Rare \ No newline at end of file diff --git a/res/cardsfolder/t/trickbind.txt b/res/cardsfolder/t/trickbind.txt index bde9d2c6b57..d7e1d044ade 100644 --- a/res/cardsfolder/t/trickbind.txt +++ b/res/cardsfolder/t/trickbind.txt @@ -2,7 +2,7 @@ Name:Trickbind ManaCost:1 U Types:Instant K:Split second -A:SP$ Counter | Cost$ 1 U | TargetType$ Activated,Triggered | TgtPrompt$ Select target Activated or Triggered Ability | RememberTargets$ True | ValidTgts$ Card | SubAbility$ DBEffect | SpellDescription$ Counter target activated or triggered ability. If a permanent's ability is countered this way, activated abilities of that permanent can't be activated this turn. (Mana abilities can't be targeted.) +A:SP$ Counter | Cost$ 1 U | TargetType$ Activated,Triggered | TgtPrompt$ Select target Activated or Triggered Ability | RememberCountered$ True | ValidTgts$ Card | SubAbility$ DBEffect | SpellDescription$ Counter target activated or triggered ability. If a permanent's ability is countered this way, activated abilities of that permanent can't be activated this turn. (Mana abilities can't be targeted.) SVar:DBEffect:DB$ Effect | Name$ Trickbind Effect | StaticAbilities$ STCantBeActivated | RememberObjects$ Remembered | SubAbility$ DBCleanup | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ1 SVar:STCantBeActivated:Mode$ CantBeActivated | EffectZone$ Command | ValidCard$ Permanent.IsRemembered SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/src/main/java/forge/card/ability/effects/CounterEffect.java b/src/main/java/forge/card/ability/effects/CounterEffect.java index 7d0324d56ec..c72603a9cd7 100644 --- a/src/main/java/forge/card/ability/effects/CounterEffect.java +++ b/src/main/java/forge/card/ability/effects/CounterEffect.java @@ -8,10 +8,12 @@ import forge.Card; import forge.Singletons; import forge.card.ability.SpellAbilityEffect; import forge.card.cardfactory.CardFactoryUtil; +import forge.game.player.AIPlayer; import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellAbilityStackInstance; import forge.card.spellability.SpellPermanent; import forge.card.trigger.TriggerType; +import forge.gui.GuiChoose; public class CounterEffect extends SpellAbilityEffect { @Override @@ -107,8 +109,8 @@ public class CounterEffect extends SpellAbilityEffect { Singletons.getModel().getGame().getAction().destroy(tgtSACard); } - if (sa.hasParam("RememberTargets")) { - if (sa.getParam("RememberTargets").equals("True")) { + if (sa.hasParam("RememberCountered")) { + if (sa.getParam("RememberCountered").equals("True")) { sa.getSourceCard().addRemembered(tgtSACard); } } @@ -133,7 +135,15 @@ public class CounterEffect extends SpellAbilityEffect { Singletons.getModel().getGame().getStack().remove(si); String destination = srcSA.hasParam("Destination") ? srcSA.getParam("Destination") : "Graveyard"; - + if (srcSA.hasParam("DestinationChoice")) {//Hinder + final String[] pos = srcSA.getParam("DestinationChoice").split(","); + if (srcSA.getActivatingPlayer() instanceof AIPlayer) { + destination = pos[0]; + } else { + final String prompt = "Select a destination to remove"; + destination = GuiChoose.one(prompt, pos); + } + } if (tgtSA.isAbility()) { // For Ability-targeted counterspells - do not move it anywhere, // even if Destination$ is specified. From 0567c7a3a54f5c370a154cdafe351fe88f18d474 Mon Sep 17 00:00:00 2001 From: swordshine Date: Tue, 2 Apr 2013 02:06:44 +0000 Subject: [PATCH 049/123] - Improved AI for Cleansing --- res/cardsfolder/c/cleansing.txt | 2 +- src/main/java/forge/card/ability/AbilityUtils.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/res/cardsfolder/c/cleansing.txt b/res/cardsfolder/c/cleansing.txt index d24016ad157..b5f31778bac 100644 --- a/res/cardsfolder/c/cleansing.txt +++ b/res/cardsfolder/c/cleansing.txt @@ -2,7 +2,7 @@ Name:Cleansing ManaCost:W W W Types:Sorcery A:SP$ RepeatEach | Cost$ W W W | RepeatSubAbility$ DBSac | RepeatCards$ Land | SpellDescription$ For each land, destroy that land unless any player pays 1 life. -SVar:DBSac:DB$ Destroy | UnlessCost$ PayLife<1> | UnlessPayer$ Player | Defined$ Remembered +SVar:DBSac:DB$ Destroy | UnlessCost$ PayLife<1> | UnlessPayer$ Player | UnlessAI$ DefinedRememberedController | Defined$ Remembered SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/cleansing.jpg Oracle:For each land, destroy that land unless any player pays 1 life. diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index 8db20cd141d..c6c8c5cea1d 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -1181,11 +1181,19 @@ public class AbilityUtils { private static boolean willAIPayForAbility(SpellAbility sa, Player payer, SpellAbility ability, boolean paid, List payers) { Card source = sa.getSourceCard(); boolean payForOwnOnly = "OnlyOwn".equals(sa.getParam("UnlessAI")); + boolean payOwner = sa.getParam("UnlessAI").startsWith("Defined"); boolean payNever = "Never".equals(sa.getParam("UnlessAI")); boolean isMine = sa.getActivatingPlayer().equals(payer); if (payNever) { return false; } if (payForOwnOnly && !isMine) { return false; } + if (payOwner) { + final String defined = sa.getParam("UnlessAI").substring(7); + final Player player = AbilityUtils.getDefinedPlayers(source, defined, sa).get(0); + if (!payer.equals(player)) { + return false; + } + } // AI will only pay when it's not already payed and only opponents abilities if (paid || (payers.size() > 1 && (isMine && !payForOwnOnly))) { From aa46a488882b4cd4677446cf5f401c42d289ec11 Mon Sep 17 00:00:00 2001 From: swordshine Date: Tue, 2 Apr 2013 05:21:53 +0000 Subject: [PATCH 050/123] - Converted "DifferentCardNames", ready for new DGM card Maze's End --- res/cardsfolder/s/signal_the_clans.txt | 2 +- .../forge/card/cardfactory/CardFactoryUtil.java | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/res/cardsfolder/s/signal_the_clans.txt b/res/cardsfolder/s/signal_the_clans.txt index c951eaf2365..75b7b8bf9d9 100644 --- a/res/cardsfolder/s/signal_the_clans.txt +++ b/res/cardsfolder/s/signal_the_clans.txt @@ -6,7 +6,7 @@ SVar:DBChoose:DB$ ChooseCard | Defined$ You | Amount$ 1 | AtRandom$ True | Choic SVar:DBChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | Defined$ ChosenCard | StackDescription$ None | SubAbility$ DBShuffle | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ3 SVar:DBShuffle:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | Shuffle$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$DifferentCardNamesRemembered +SVar:X:Count$DifferentCardNames_Creature.IsRemembered SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/signal_the_clans.jpg Oracle:Search your library for three creature cards and reveal them. If you reveal three cards with different names, choose one of them at random and put that card into your hand. Shuffle the rest into your library. diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index 687f167189a..e3dffb594cd 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -1247,22 +1247,18 @@ public class CardFactoryUtil { return highest; } - if (l[0].startsWith("DifferentCardNamesRemembered")) { - final List list = new ArrayList(); + if (l[0].startsWith("DifferentCardNames_")) { final List crdname = new ArrayList(); - if (c.getRemembered().size() > 0) { - for (final Object o : c.getRemembered()) { - if (o instanceof Card) { - list.add(Singletons.getModel().getGame().getCardState((Card) o)); - } - } - } + final String restriction = l[0].substring(19); + final String[] rest = restriction.split(","); + List list = cardController.getGame().getCardsInGame(); + list = CardLists.getValidCards(list, rest, cardController, c); for (final Card card : list) { if (!crdname.contains(card.getName())) { crdname.add(card.getName()); } } - return crdname.size(); + return CardFactoryUtil.doXMath(crdname.size(), m, c); } if (l[0].startsWith("RememberedSize")) { From d83b6b7e661a5dd2e068d1e76d29cf429ade30db Mon Sep 17 00:00:00 2001 From: swordshine Date: Tue, 2 Apr 2013 05:29:40 +0000 Subject: [PATCH 051/123] - Added Cut the Tethers --- .gitattributes | 1 + res/cardsfolder/c/cut_the_tethers.txt | 8 ++++++++ src/main/java/forge/game/GameActionUtil.java | 14 +++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 res/cardsfolder/c/cut_the_tethers.txt diff --git a/.gitattributes b/.gitattributes index 10ced0e1922..32d4dec9fe8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2243,6 +2243,7 @@ res/cardsfolder/c/cursed_totem.txt svneol=native#text/plain res/cardsfolder/c/custody_battle.txt -text svneol=unset#text/plain res/cardsfolder/c/customs_depot.txt svneol=native#text/plain res/cardsfolder/c/cut_the_earthly_bond.txt svneol=native#text/plain +res/cardsfolder/c/cut_the_tethers.txt -text res/cardsfolder/c/cutthroat_il_dal.txt svneol=native#text/plain res/cardsfolder/c/cyclical_evolution.txt svneol=native#text/plain res/cardsfolder/c/cyclonic_rift.txt -text diff --git a/res/cardsfolder/c/cut_the_tethers.txt b/res/cardsfolder/c/cut_the_tethers.txt new file mode 100644 index 00000000000..2b9898afcae --- /dev/null +++ b/res/cardsfolder/c/cut_the_tethers.txt @@ -0,0 +1,8 @@ +Name:Cut the Tethers +ManaCost:2 U U +Types:Sorcery +A:SP$ RepeatEach | Cost$ 2 U U | RepeatSubAbility$ DBReturn | RepeatCards$ Creature.Spirit | SpellDescription$ For each Spirit, return it to its owner's hand unless that player pays 3. +SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Battlefield | Destination$ Hand | UnlessCost$ 3 | UnlessPayer$ RememberedController +SVar:Picture:http://www.wizards.com.sixxs.org/global/images/magic/general/cut_the_tethers.jpg +Oracle:For each Spirit, return it to its owner's hand unless that player pays {3}. +SetInfo:CHK Uncommon \ No newline at end of file diff --git a/src/main/java/forge/game/GameActionUtil.java b/src/main/java/forge/game/GameActionUtil.java index 00916038587..6e01632b5f3 100644 --- a/src/main/java/forge/game/GameActionUtil.java +++ b/src/main/java/forge/game/GameActionUtil.java @@ -405,6 +405,16 @@ public final class GameActionUtil { // Only human player pays this way final Player p = ability.getActivatingPlayer(); final Card source = ability.getSourceCard(); + Card current = null; // Used in spells with RepeatEach effect to distinguish cards, Cut the Tethers + if (!source.getRemembered().isEmpty() && source.isSpell()) { + if (source.getRemembered().get(0) instanceof Card) { + current = (Card) source.getRemembered().get(0); + } + } + if (!source.getImprinted().isEmpty() && source.isSpell()) { + current = source.getImprinted().get(0); + } + final List parts = cost.getCostParts(); ArrayList remainingParts = new ArrayList(cost.getCostParts()); CostPart costPart = null; @@ -554,7 +564,9 @@ public final class GameActionUtil { if (!(costPart instanceof CostPartMana )) throw new RuntimeException("GameActionUtil.payCostDuringAbilityResolve - The remaining payment type is not Mana."); - InputPayment toSet = new InputPayManaExecuteCommands(p, source + "\r\n", ability.getManaCost()); + InputPayment toSet = current == null + ? new InputPayManaExecuteCommands(p, source + "\r\n", ability.getManaCost()) + : new InputPayManaExecuteCommands(p, source + "\r\n" + "Current Card: " + current + "\r\n" , ability.getManaCost()); FThreads.setInputAndWait(toSet); return toSet.isPaid(); } From 67817aaecd14ecd6abe975b28f1646dc5823580e Mon Sep 17 00:00:00 2001 From: swordshine Date: Tue, 2 Apr 2013 05:51:56 +0000 Subject: [PATCH 052/123] - Added Delay --- .gitattributes | 1 + res/cardsfolder/d/delay.txt | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 res/cardsfolder/d/delay.txt diff --git a/.gitattributes b/.gitattributes index 32d4dec9fe8..70dfe75c578 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2505,6 +2505,7 @@ res/cardsfolder/d/deglamer.txt svneol=native#text/plain res/cardsfolder/d/dehydration.txt svneol=native#text/plain res/cardsfolder/d/deity_of_scars.txt svneol=native#text/plain res/cardsfolder/d/deja_vu.txt svneol=native#text/plain +res/cardsfolder/d/delay.txt -text res/cardsfolder/d/delaying_shield.txt -text res/cardsfolder/d/delifs_cone.txt -text res/cardsfolder/d/delifs_cube.txt -text diff --git a/res/cardsfolder/d/delay.txt b/res/cardsfolder/d/delay.txt new file mode 100644 index 00000000000..27e2db89cac --- /dev/null +++ b/res/cardsfolder/d/delay.txt @@ -0,0 +1,10 @@ +Name:Delay +ManaCost:1 U +Types:Instant +A:SP$ Counter | Cost$ 1 U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | Destination$ Exile | SubAbility$ DBPutCounter | SpellDescription$ Counter target spell. If the spell is countered this way, exile it with three time counters on it instead of putting it into its owner's graveyard. If it doesn't have suspend, it gains suspend. (At the beginning of its owner's upkeep, remove a counter from that card. When the last is removed, the player plays it without paying its mana cost. If it's a creature, it has haste.) +SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterNum$ 3 | CounterType$ TIME | SubAbility$ DBPump +SVar:DBPump:DB$ PumpAll | ValidCards$ Card.IsRemembered+withoutSuspend | PumpZone$ Exile | KW$ Suspend | Permanent$ True | SubAbility$ DBCleanup | StackDescription$ If it doesn't have suspend, it gains suspend. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/delay.jpg +Oracle:Counter target spell. If the spell is countered this way, exile it with three time counters on it instead of putting it into its owner's graveyard. If it doesn't have suspend, it gains suspend. (At the beginning of its owner's upkeep, remove a counter from that card. When the last is removed, the player plays it without paying its mana cost. If it's a creature, it has haste.) +SetInfo:FUT Uncommon \ No newline at end of file From 3d57a98d3cfae7e594043a7a2eaeb0d0a3ebdc92 Mon Sep 17 00:00:00 2001 From: Hellfish Date: Tue, 2 Apr 2013 06:15:58 +0000 Subject: [PATCH 053/123] *Some Commander infrastructure added --- src/main/java/forge/Card.java | 17 +++++++++++------ src/main/java/forge/deck/CardCollections.java | 13 +++++++++++-- src/main/java/forge/game/GameType.java | 3 ++- src/main/java/forge/game/MatchStartHelper.java | 10 ++++++++++ src/main/java/forge/game/player/Player.java | 2 +- .../java/forge/properties/NewConstants.java | 1 + 6 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index ad0eeb19c50..e3b599575be 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -133,6 +133,7 @@ public class Card extends GameEntity implements Comparable { private List blockedThisTurn = null; private List blockedByThisTurn = null; + private boolean isCommander = false; private boolean startsGameInPlay = false; private boolean drawnThisTurn = false; private boolean tapped = false; @@ -7320,6 +7321,10 @@ public class Card extends GameEntity implements Comparable { if (!this.isType(source.getChosenType())) { return false; } + } else if (property.equals("IsCommander")) { + if(!this.isCommander) { + return false; + } } else { if (!this.isType(property)) { return false; @@ -9139,13 +9144,13 @@ public class Card extends GameEntity implements Comparable { cardRules = r; } - /** - * TODO: Write javadoc for this method. - * @return - */ - public boolean isEdhGeneral() { + public boolean isCommander() { // TODO - have a field - return false; + return this.isCommander; + } + + public void setCommander(boolean b) { + this.isCommander = b; } } // end Card class diff --git a/src/main/java/forge/deck/CardCollections.java b/src/main/java/forge/deck/CardCollections.java index ae30b624c1b..e456b8f6831 100644 --- a/src/main/java/forge/deck/CardCollections.java +++ b/src/main/java/forge/deck/CardCollections.java @@ -40,6 +40,7 @@ public class CardCollections { private final IStorage cube; private final IStorage scheme; private final IStorage plane; + private final IStorage commander; /** * TODO: Write javadoc for Constructor. @@ -56,9 +57,10 @@ public class CardCollections { this.cube = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_CUBE_DIR))); this.scheme = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_SCHEME_DIR))); this.plane = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_PLANE_DIR))); - + this.commander = new StorageImmediatelySerialized(new DeckSerializer(new File(NewConstants.DECK_COMMANDER_DIR))); + sw.stop(); - System.out.printf("Read decks (%d ms): %d constructed, %d sealed, %d draft, %d cubes, %d scheme, %d planar.%n", sw.getTime(), constructed.size(), sealed.size(), draft.size(), cube.size(), scheme.size(), plane.size()); + System.out.printf("Read decks (%d ms): %d constructed, %d sealed, %d draft, %d cubes, %d scheme, %d planar, %d commander.%n", sw.getTime(), constructed.size(), sealed.size(), draft.size(), cube.size(), scheme.size(), plane.size(),commander.size()); // int sum = constructed.size() + sealed.size() + draft.size() + cube.size() + scheme.size() + plane.size(); // FSkin.setProgessBarMessage(String.format("Loaded %d decks in %f sec", sum, sw.getTime() / 1000f )); // remove this after most people have been switched to new layout @@ -116,5 +118,12 @@ public class CardCollections { public IStorage getPlane() { return plane; } + + /** + * @return the plane + */ + public IStorage getCommander() { + return commander; + } } diff --git a/src/main/java/forge/game/GameType.java b/src/main/java/forge/game/GameType.java index c6cbf5230e1..3342002eb00 100644 --- a/src/main/java/forge/game/GameType.java +++ b/src/main/java/forge/game/GameType.java @@ -16,7 +16,8 @@ public enum GameType { Constructed ( DeckFormat.Constructed, false, true ), Archenemy ( DeckFormat.Archenemy, false, false ), Planechase ( DeckFormat.Planechase, false, false ), - Vanguard ( DeckFormat.Vanguard, true, true ); + Vanguard ( DeckFormat.Vanguard, true, true ), + Commander ( DeckFormat.Commander, false, false); private final DeckFormat decksFormat; private final boolean bCardpoolLimited; diff --git a/src/main/java/forge/game/MatchStartHelper.java b/src/main/java/forge/game/MatchStartHelper.java index add9e4f39e8..e9b75c6d4ca 100644 --- a/src/main/java/forge/game/MatchStartHelper.java +++ b/src/main/java/forge/game/MatchStartHelper.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.Map; import forge.deck.Deck; +import forge.deck.DeckSection; import forge.game.player.LobbyPlayer; import forge.item.CardPrinted; @@ -33,6 +34,15 @@ public class MatchStartHelper { players.put(player, start); } + + public void addCommanderPlayer(final LobbyPlayer player, final Deck deck) + { + PlayerStartConditions start = new PlayerStartConditions(deck); + start.setStartingLife(40); + start.setCardsInCommand(deck.get(DeckSection.Commander).toFlatList()); + + players.put(player, start); + } public void addArchenemy(final LobbyPlayer player, final Deck deck, final Iterable schemes) { PlayerStartConditions start = new PlayerStartConditions(deck); diff --git a/src/main/java/forge/game/player/Player.java b/src/main/java/forge/game/player/Player.java index 4f9538a2fde..a9e2798fe1f 100644 --- a/src/main/java/forge/game/player/Player.java +++ b/src/main/java/forge/game/player/Player.java @@ -644,7 +644,7 @@ public abstract class Player extends GameEntity implements Comparable { } this.assignedDamage.put(source, damageToDo); - if(source.isEdhGeneral()) + if(source.isCommander()) this.edhGeneralDamage+= damageToDo; GameActionUtil.executeDamageDealingEffects(source, damageToDo); diff --git a/src/main/java/forge/properties/NewConstants.java b/src/main/java/forge/properties/NewConstants.java index 3a387fef040..c9bb7dd5fe8 100644 --- a/src/main/java/forge/properties/NewConstants.java +++ b/src/main/java/forge/properties/NewConstants.java @@ -75,6 +75,7 @@ public final class NewConstants { public static final String DECK_SEALED_DIR = DECK_BASE_DIR + "sealed/"; public static final String DECK_SCHEME_DIR = DECK_BASE_DIR + "scheme/"; public static final String DECK_PLANE_DIR = DECK_BASE_DIR + "planar/"; + public static final String DECK_COMMANDER_DIR = DECK_BASE_DIR + "commander/"; public static final String QUEST_SAVE_DIR = USER_QUEST_DIR + "saves/"; public static final String MAIN_PREFS_FILE = USER_PREFS_DIR + "forge.preferences"; public static final String QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences"; From 2e98741d8796588aa610bc1d7eb78d19227f6108 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Tue, 2 Apr 2013 06:20:21 +0000 Subject: [PATCH 054/123] reveting incorrect implmentation of damage dealt by general calculation --- src/main/java/forge/game/player/Player.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/forge/game/player/Player.java b/src/main/java/forge/game/player/Player.java index a9e2798fe1f..4f0f30a702a 100644 --- a/src/main/java/forge/game/player/Player.java +++ b/src/main/java/forge/game/player/Player.java @@ -88,8 +88,6 @@ public abstract class Player extends GameEntity implements Comparable { /** The poison counters. */ private int poisonCounters = 0; - private int edhGeneralDamage = 0; - /** The life. */ private int life = 20; @@ -644,9 +642,6 @@ public abstract class Player extends GameEntity implements Comparable { } this.assignedDamage.put(source, damageToDo); - if(source.isCommander()) - this.edhGeneralDamage+= damageToDo; - GameActionUtil.executeDamageDealingEffects(source, damageToDo); GameActionUtil.executeDamageToPlayerEffects(this, source, damageToDo); @@ -2153,10 +2148,6 @@ public abstract class Player extends GameEntity implements Comparable { if (hasNoLife && !this.cantLoseForZeroOrLessLife()) { return this.loseConditionMet(GameLossReason.LifeReachedZero, null); } - - if(this.edhGeneralDamage >= 21) { - return this.loseConditionMet(GameLossReason.EdhGeneralDamage, null); - } return false; } From d9778c9cfaec7bb59fecbc8d67f3b132134511d2 Mon Sep 17 00:00:00 2001 From: moomarc Date: Tue, 2 Apr 2013 07:44:39 +0000 Subject: [PATCH 055/123] - added Chooser and DefinedPlayer params to some ChangeZone hidden origin cards --- res/cardsfolder/a/appetite_for_brains.txt | 2 +- res/cardsfolder/g/ghastlord_of_fugue.txt | 2 +- res/cardsfolder/l/lobotomy.txt | 6 +++--- res/cardsfolder/l/lost_hours.txt | 2 +- res/cardsfolder/m/mesmeric_fiend.txt | 2 +- res/cardsfolder/n/night_terrors.txt | 2 +- res/cardsfolder/p/perish_the_thought.txt | 2 +- res/cardsfolder/t/thoughtseize.txt | 2 +- res/cardsfolder/t/tidehollow_sculler.txt | 6 +++--- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/res/cardsfolder/a/appetite_for_brains.txt b/res/cardsfolder/a/appetite_for_brains.txt index d4eb10f3cb1..0943b32eae4 100644 --- a/res/cardsfolder/a/appetite_for_brains.txt +++ b/res/cardsfolder/a/appetite_for_brains.txt @@ -1,7 +1,7 @@ Name:Appetite for Brains ManaCost:B Types:Sorcery -A:SP$ ChangeZone | Cost$ B | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | ChangeType$ Card.cmcGE4 | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it with converted mana cost 4 or greater and exile that card. +A:SP$ ChangeZone | Cost$ B | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.cmcGE4 | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it with converted mana cost 4 or greater and exile that card. SVar:Picture:http://www.wizards.com/global/images/magic/general/appetite_for_brains.jpg Oracle:Target opponent reveals his or her hand. You choose a card from it with converted mana cost 4 or greater and exile that card. SetInfo:AVR Uncommon \ No newline at end of file diff --git a/res/cardsfolder/g/ghastlord_of_fugue.txt b/res/cardsfolder/g/ghastlord_of_fugue.txt index 4692ed1927b..047d496ca0a 100644 --- a/res/cardsfolder/g/ghastlord_of_fugue.txt +++ b/res/cardsfolder/g/ghastlord_of_fugue.txt @@ -4,7 +4,7 @@ Types:Creature Spirit Avatar PT:4/4 K:Unblockable T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigChangeZone | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, that player reveals his or her hand. You choose a card from it. That player exiles that card. -SVar:TrigChangeZone:AB$ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | DefinedPlayer$ TriggeredTarget | Chooser$ You | ChangeType$ Card | ChangeNum$ 1 +SVar:TrigChangeZone:AB$ ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | DefinedPlayer$ TriggeredTarget | Chooser$ You | ChangeType$ Card | ChangeNum$ 1 SVar:Picture:http://www.wizards.com/global/images/magic/general/ghastlord_of_fugue.jpg Oracle:Ghastlord of Fugue is unblockable.\nWhenever Ghastlord of Fugue deals combat damage to a player, that player reveals his or her hand. You choose a card from it. That player exiles that card. SetInfo:SHM Rare \ No newline at end of file diff --git a/res/cardsfolder/l/lobotomy.txt b/res/cardsfolder/l/lobotomy.txt index 02fa8c2e03c..1f0c66dc03b 100644 --- a/res/cardsfolder/l/lobotomy.txt +++ b/res/cardsfolder/l/lobotomy.txt @@ -1,9 +1,9 @@ Name:Lobotomy ManaCost:2 U B Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 U B | Origin$ Hand | Destination$ Exile | ValidTgts$ Player | ChangeType$ Card.nonBasic | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | ForgetOtherRemembered$ True | SubAbility$ DBSearch | SpellDescription$ Target player reveals his or her hand, then you choose a card other than a basic land card from it. Search that player's graveyard, hand, and library for all cards with the same name as the chosen card and exile them. Then that player shuffles his or her library. -SVar:DBSearch:DB$ChangeZoneAll | Origin$ Graveyard,Hand,Library | Destination$ Exile | ChangeType$ Remembered.sameName | Shuffle$ True| Defined$ Targeted | SubAbility$ DBCleanup -SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True +A:SP$ ChangeZone | Cost$ 2 U B | Origin$ Hand | Destination$ Exile | ValidTgts$ Player | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.nonBasic | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | ForgetOtherRemembered$ True | SubAbility$ DBSearch | SpellDescription$ Target player reveals his or her hand, then you choose a card other than a basic land card from it. Search that player's graveyard, hand, and library for all cards with the same name as the chosen card and exile them. Then that player shuffles his or her library. +SVar:DBSearch:DB$ ChangeZoneAll | Origin$ Graveyard,Hand,Library | Destination$ Exile | ChangeType$ Remembered.sameName | Shuffle$ True| Defined$ Targeted | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Picture:http://www.wizards.com/global/images/magic/general/lobotomy.jpg Oracle:Target player reveals his or her hand, then you choose a card other than a basic land card from it. Search that player's graveyard, hand, and library for all cards with the same name as the chosen card and exile them. Then that player shuffles his or her library. SetInfo:TMP Uncommon diff --git a/res/cardsfolder/l/lost_hours.txt b/res/cardsfolder/l/lost_hours.txt index eac0a38338e..15de3164d2d 100644 --- a/res/cardsfolder/l/lost_hours.txt +++ b/res/cardsfolder/l/lost_hours.txt @@ -1,7 +1,7 @@ Name:Lost Hours ManaCost:1 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 1 B | Origin$ Hand | Destination$ Library | LibraryPosition$ 2 | ValidTgts$ Player | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | SpellDescription$ Target player reveals his or her hand. You choose a nonland card from it. That player puts that card into his or her library third from the top. +A:SP$ ChangeZone | Cost$ 1 B | Origin$ Hand | Destination$ Library | LibraryPosition$ 2 | ValidTgts$ Player | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | SpellDescription$ Target player reveals his or her hand. You choose a nonland card from it. That player puts that card into his or her library third from the top. SVar:Picture:http://www.wizards.com/global/images/magic/general/lost_hours.jpg Oracle:Target player reveals his or her hand. You choose a nonland card from it. That player puts that card into his or her library third from the top. SetInfo:FUT Common \ No newline at end of file diff --git a/res/cardsfolder/m/mesmeric_fiend.txt b/res/cardsfolder/m/mesmeric_fiend.txt index a8c2e34d3ef..44bb38f73ea 100644 --- a/res/cardsfolder/m/mesmeric_fiend.txt +++ b/res/cardsfolder/m/mesmeric_fiend.txt @@ -4,7 +4,7 @@ Types:Creature Nightmare Horror PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMesmericExile | TriggerDescription$ When CARDNAME enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card. When CARDNAME leaves the battlefield, return the exiled card to its owner's hand. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigMesmericBounce | Secondary$ True | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card. When CARDNAME leaves the battlefield, return the exiled card to its owner's hand. -SVar:TrigMesmericExile:AB$ ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True +SVar:TrigMesmericExile:AB$ ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | TgtPrompt$ Select target opponent | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True SVar:TrigMesmericBounce:AB$ ChangeZone | Cost$ 0 | Origin$ Exile | Destination$ Hand | Defined$ Remembered | SubAbility$ DBMesmericCleanup SVar:DBMesmericCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Picture:http://www.wizards.com/global/images/magic/general/mesmeric_fiend.jpg diff --git a/res/cardsfolder/n/night_terrors.txt b/res/cardsfolder/n/night_terrors.txt index 2fe4fb283b0..fe66dd34220 100644 --- a/res/cardsfolder/n/night_terrors.txt +++ b/res/cardsfolder/n/night_terrors.txt @@ -1,7 +1,7 @@ Name:Night Terrors ManaCost:2 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 B | Origin$ Hand | Destination$ Exile | ValidTgts$ Player | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target player reveals his or her hand. You choose a nonland card from it. Exile that card. +A:SP$ ChangeZone | Cost$ 2 B | Origin$ Hand | Destination$ Exile | ValidTgts$ Player | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target player reveals his or her hand. You choose a nonland card from it. Exile that card. SVar:Picture:http://www.wizards.com/global/images/magic/general/night_terrors.jpg Oracle:Target player reveals his or her hand. You choose a nonland card from it. Exile that card. SetInfo:ISD Common \ No newline at end of file diff --git a/res/cardsfolder/p/perish_the_thought.txt b/res/cardsfolder/p/perish_the_thought.txt index ebc0b3ffad0..7d49817042d 100644 --- a/res/cardsfolder/p/perish_the_thought.txt +++ b/res/cardsfolder/p/perish_the_thought.txt @@ -1,7 +1,7 @@ Name:Perish the Thought ManaCost:2 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 B | Origin$ Hand | Destination$ Library | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | Shuffle$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library. +A:SP$ ChangeZone | Cost$ 2 B | Origin$ Hand | Destination$ Library | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | Shuffle$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library. SVar:Picture:http://www.wizards.com/global/images/magic/general/perish_the_thought.jpg Oracle:Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library. SetInfo:ROE Common \ No newline at end of file diff --git a/res/cardsfolder/t/thoughtseize.txt b/res/cardsfolder/t/thoughtseize.txt index ac97202c52e..7fac1fb24b1 100644 --- a/res/cardsfolder/t/thoughtseize.txt +++ b/res/cardsfolder/t/thoughtseize.txt @@ -2,7 +2,7 @@ Name:Thoughtseize ManaCost:B Types:Sorcery A:SP$ Discard | Cost$ B | ValidTgts$ Player | NumCards$ 1 | DiscardValid$ Card.nonLand | Mode$ RevealYouChoose | SubAbility$ DBLoseLife | SpellDescription$ Target player reveals his or her hand. You choose a nonland card from it. That player discards that card. You lose 2 life. -SVar:DBLoseLife:DB$LoseLife | LifeAmount$ 2 +SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 2 SVar:Picture:http://resources.wizards.com/magic/cards/lrw/en/card145969.jpg Oracle:Target player reveals his or her hand. You choose a nonland card from it. That player discards that card. You lose 2 life. SetInfo:LRW Rare \ No newline at end of file diff --git a/res/cardsfolder/t/tidehollow_sculler.txt b/res/cardsfolder/t/tidehollow_sculler.txt index a937bd3a609..7e02b51b715 100644 --- a/res/cardsfolder/t/tidehollow_sculler.txt +++ b/res/cardsfolder/t/tidehollow_sculler.txt @@ -4,9 +4,9 @@ Types:Artifact Creature Zombie PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card. When CARDNAME leaves the battlefield, return the exiled card to its owner's hand. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigBounce | Secondary$ True | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card. When CARDNAME leaves the battlefield, return the exiled card to its owner's hand. -SVar:TrigExile:AB$ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | TgtPrompt$ Select target player | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | ForgetOtherRemembered$ True -SVar:TrigBounce:AB$ChangeZone | Cost$ 0 | Origin$ Exile | Destination$ Hand | Defined$ Remembered | SubAbility$ DBCleanup -SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True +SVar:TrigExile:AB$ ChangeZone | Cost$ 0 | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | TgtPrompt$ Select target opponent | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | ForgetOtherRemembered$ True +SVar:TrigBounce:AB$ ChangeZone | Cost$ 0 | Origin$ Exile | Destination$ Hand | Defined$ Remembered | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Picture:http://www.wizards.com/global/images/magic/general/tidehollow_sculler.jpg Oracle:When Tidehollow Sculler enters the battlefield, target opponent reveals his or her hand and you choose a nonland card from it. Exile that card.\nWhen Tidehollow Sculler leaves the battlefield, return the exiled card to its owner's hand. SetInfo:ALA Uncommon \ No newline at end of file From 1f42e9cc7068e17a7086cdaf6a22ff1729d159fd Mon Sep 17 00:00:00 2001 From: swordshine Date: Tue, 2 Apr 2013 07:57:59 +0000 Subject: [PATCH 056/123] - Added Runed Halo --- .gitattributes | 1 + res/cardsfolder/r/runed_halo.txt | 10 ++++++++++ .../card/staticability/StaticAbilityContinuous.java | 6 ++++++ 3 files changed, 17 insertions(+) create mode 100644 res/cardsfolder/r/runed_halo.txt diff --git a/.gitattributes b/.gitattributes index 70dfe75c578..c98d3efad4c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8856,6 +8856,7 @@ res/cardsfolder/r/runeboggle.txt svneol=native#text/plain res/cardsfolder/r/runechanters_pike.txt -text res/cardsfolder/r/runeclaw_bear.txt svneol=native#text/plain res/cardsfolder/r/runed_arch.txt svneol=native#text/plain +res/cardsfolder/r/runed_halo.txt -text res/cardsfolder/r/runed_servitor.txt svneol=native#text/plain res/cardsfolder/r/runed_stalactite.txt svneol=native#text/plain res/cardsfolder/r/runeflare_trap.txt -text diff --git a/res/cardsfolder/r/runed_halo.txt b/res/cardsfolder/r/runed_halo.txt new file mode 100644 index 00000000000..a0e4629d059 --- /dev/null +++ b/res/cardsfolder/r/runed_halo.txt @@ -0,0 +1,10 @@ +Name:Runed Halo +ManaCost:W W +Types:Enchantment +K:ETBReplacement:Other:DBNameCard +SVar:DBNameCard:DB$ NameCard | Defined$ You | SpellDescription$ As CARDNAME enters the battlefield, name a card. +S:Mode$ Continuous | Affected$ You | AddKeyword$ Protection:ChosenName | Description$ You have protection from the chosen name. (You can't be targeted, dealt damage, or enchanted by anything with that name.) +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/runed_halo.jpg +Oracle:As Runed Halo enters the battlefield, name a card.\nYou have protection from the chosen name. (You can't be targeted, dealt damage, or enchanted by anything with that name.) +SetInfo:SHM Rare \ No newline at end of file diff --git a/src/main/java/forge/card/staticability/StaticAbilityContinuous.java b/src/main/java/forge/card/staticability/StaticAbilityContinuous.java index 9fd6329501b..99107790b03 100644 --- a/src/main/java/forge/card/staticability/StaticAbilityContinuous.java +++ b/src/main/java/forge/card/staticability/StaticAbilityContinuous.java @@ -154,6 +154,12 @@ public class StaticAbilityContinuous { for (int w = 0; w < addKeywords.length; w++) { addKeywords[w] = addKeywords[w].replaceAll("ChosenType", chosenType); } + final String chosenName = hostCard.getNamedCard(); + for (int w = 0; w < addKeywords.length; w++) { + if (addKeywords[w].startsWith("Protection:")) { + addKeywords[w] = addKeywords[w].replaceAll("ChosenName", "Card.named" + chosenName); + } + } } if (params.containsKey("AddHiddenKeyword")) { From 69978b40e9d1232f3d3b3d65da7b153db0253ead Mon Sep 17 00:00:00 2001 From: swordshine Date: Tue, 2 Apr 2013 09:41:00 +0000 Subject: [PATCH 057/123] - Converted Karma to script --- res/cardsfolder/k/karma.txt | 4 ++- src/main/java/forge/game/phase/Upkeep.java | 41 ---------------------- 2 files changed, 3 insertions(+), 42 deletions(-) diff --git a/res/cardsfolder/k/karma.txt b/res/cardsfolder/k/karma.txt index 80220eb9e90..33ddcbfe78f 100644 --- a/res/cardsfolder/k/karma.txt +++ b/res/cardsfolder/k/karma.txt @@ -1,7 +1,9 @@ Name:Karma ManaCost:2 W W Types:Enchantment -Text:At the beginning of each player's upkeep, Karma deals damage to that player equal to the number of Swamps he or she controls. +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ At the beginning of each player's upkeep, CARDNAME deals damage to that player equal to the number of Swamps he or she controls. +SVar:TrigDamage:AB$ DealDamage | Cost$ 0 | Defined$ TriggeredPlayer | NumDmg$ X | References$ X +SVar:X:Count$Valid Swamp.ActivePlayerCtrl SVar:RemRandomDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/karma.jpg Oracle:At the beginning of each player's upkeep, Karma deals damage to that player equal to the number of Swamps he or she controls. diff --git a/src/main/java/forge/game/phase/Upkeep.java b/src/main/java/forge/game/phase/Upkeep.java index aae2ac69e82..306fe5cc6c4 100644 --- a/src/main/java/forge/game/phase/Upkeep.java +++ b/src/main/java/forge/game/phase/Upkeep.java @@ -94,7 +94,6 @@ public class Upkeep extends Phase { Upkeep.upkeepDemonicHordes(game); Upkeep.upkeepTangleWire(game); - Upkeep.upkeepKarma(game); Upkeep.upkeepOathOfDruids(game); Upkeep.upkeepOathOfGhouls(game); Upkeep.upkeepSuspend(game); @@ -875,46 +874,6 @@ public class Upkeep extends Phase { } } // Oath of Ghouls - /** - *

- * upkeepKarma. - *

- */ - private static void upkeepKarma(final GameState game) { - final Player player = game.getPhaseHandler().getPlayerTurn(); - final List karmas = - CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Karma")); - final List swamps = CardLists.getType(player.getCardsIn(ZoneType.Battlefield), "Swamp"); - - // determine how much damage to deal the current player - final int damage = swamps.size(); - - // if there are 1 or more Karmas on the - // battlefield have each of them deal damage. - if (0 < karmas.size()) { - for (final Card karma : karmas) { - final Ability ability = new Ability(karma, ManaCost.ZERO) { - @Override - public void resolve() { - if (damage > 0) { - player.addDamage(damage, karma); - } - } - }; // Ability - if (damage > 0) { - - final StringBuilder sb = new StringBuilder(); - sb.append("Karma deals ").append(damage).append(" damage to ").append(player); - ability.setStackDescription(sb.toString()); - ability.setDescription(sb.toString()); - ability.setActivatingPlayer(karma.getController()); - - game.getStack().addSimultaneousStackEntry(ability); - } - } - } // if - } // upkeepKarma() - /** *

* upkeepPowerSurge. From 18266580a3d258934ac8c47021a06e18c103ebb4 Mon Sep 17 00:00:00 2001 From: moomarc Date: Tue, 2 Apr 2013 10:33:19 +0000 Subject: [PATCH 058/123] - Fixed the hidden origin ChangeZone abilities that use a target (stack descriptions still need to be reworked but that has been an issue for a while) --- src/main/java/forge/card/ability/ai/ChangeZoneAi.java | 2 +- .../java/forge/card/ability/effects/ChangeZoneEffect.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/forge/card/ability/ai/ChangeZoneAi.java b/src/main/java/forge/card/ability/ai/ChangeZoneAi.java index d7a73922bf8..24638acd72b 100644 --- a/src/main/java/forge/card/ability/ai/ChangeZoneAi.java +++ b/src/main/java/forge/card/ability/ai/ChangeZoneAi.java @@ -1071,7 +1071,7 @@ public class ChangeZoneAi extends SpellAbilityAi { if (tgt != null) { if (!tgt.getTargetPlayers().isEmpty()) { - player = player != null ? player : tgt.getTargetPlayers().get(0); + player = sa.hasParam("DefinedPlayer") ? player : tgt.getTargetPlayers().get(0); if (!player.canBeTargetedBy(sa)) { return; } diff --git a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java index c5372498ff5..b62c3962279 100644 --- a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java +++ b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java @@ -528,7 +528,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (decider instanceof AIPlayer) { ChangeZoneAi.hiddenOriginResolveAI(decider, sa, player); } else { - changeHiddenOriginResolveHuman((HumanPlayer)decider, sa, player); + changeHiddenOriginResolveHuman((HumanPlayer) decider, sa, player); } } } @@ -554,7 +554,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final Target tgt = sa.getTarget(); if (tgt != null) { final List players = tgt.getTargetPlayers(); - player = player != null ? player : players.get(0); + player = sa.hasParam("DefinedPlayer") ? player : players.get(0); if (players.contains(player) && !player.canBeTargetedBy(sa)) { return; } @@ -623,7 +623,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } // Look at opponents hand before moving onto choosing a card - if (origin.contains(ZoneType.Hand) && player.isOpponentOf(player)) { + if (origin.contains(ZoneType.Hand) && player.isOpponentOf(decider)) { GuiChoose.oneOrNone(sa.getSourceCard().getName() + " - Looking at Opponent's Hand", player .getCardsIn(ZoneType.Hand)); } From e5b51cef2f8d4f2fafb6fe51642ee7d3942c86f7 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 2 Apr 2013 11:44:28 +0000 Subject: [PATCH 059/123] - Added new card names to changes.txt. --- CHANGES.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 19f8883cd99..7203139aa3c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -72,6 +72,12 @@ Leech Bonder Weight of Conscience Multani's Presence Lullmage Mentor +Fold into AEther +Hinder +Swift Silence +Cut the Tethers +Delay +Runed Halo ---------- From 397d2ce3cd87397d08c00e64d1d2a7cfa4c7fa49 Mon Sep 17 00:00:00 2001 From: swordshine Date: Tue, 2 Apr 2013 12:06:04 +0000 Subject: [PATCH 060/123] - Some stack descriptions --- res/cardsfolder/a/acquire.txt | 2 +- res/cardsfolder/a/appetite_for_brains.txt | 2 +- res/cardsfolder/b/bribery.txt | 2 +- res/cardsfolder/c/castigate.txt | 2 +- res/cardsfolder/e/eternal_dominion.txt | 2 +- res/cardsfolder/h/hide_seek.txt | 2 +- res/cardsfolder/k/knowledge_exploitation.txt | 4 ++-- res/cardsfolder/p/painful_memories.txt | 2 +- res/cardsfolder/p/parallax_nexus.txt | 2 +- res/cardsfolder/p/perish_the_thought.txt | 2 +- res/cardsfolder/p/praetors_grasp.txt | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/res/cardsfolder/a/acquire.txt b/res/cardsfolder/a/acquire.txt index 6505302fcd2..770faaa36c4 100644 --- a/res/cardsfolder/a/acquire.txt +++ b/res/cardsfolder/a/acquire.txt @@ -1,7 +1,7 @@ Name:Acquire ManaCost:3 U U Types:Sorcery -A:SP$ ChangeZone | Cost$ 3 U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Artifact | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | SpellDescription$ Search target opponent's library for an artifact card and put that card onto the battlefield under your control. Then that player shuffles his or her library. +A:SP$ ChangeZone | Cost$ 3 U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Artifact | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for an artifact card and put that card onto the battlefield under your control. Then that player shuffles his or her library. SVar:Picture:http://www.wizards.com/global/images/magic/general/acquire.jpg Oracle:Search target opponent's library for an artifact card and put that card onto the battlefield under your control. Then that player shuffles his or her library. SetInfo:5DN Rare \ No newline at end of file diff --git a/res/cardsfolder/a/appetite_for_brains.txt b/res/cardsfolder/a/appetite_for_brains.txt index 0943b32eae4..d46696fafb8 100644 --- a/res/cardsfolder/a/appetite_for_brains.txt +++ b/res/cardsfolder/a/appetite_for_brains.txt @@ -1,7 +1,7 @@ Name:Appetite for Brains ManaCost:B Types:Sorcery -A:SP$ ChangeZone | Cost$ B | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.cmcGE4 | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it with converted mana cost 4 or greater and exile that card. +A:SP$ ChangeZone | Cost$ B | Origin$ Hand | Destination$ Exile | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card.cmcGE4 | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | StackDescription$ SpellDescription | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it with converted mana cost 4 or greater and exile that card. SVar:Picture:http://www.wizards.com/global/images/magic/general/appetite_for_brains.jpg Oracle:Target opponent reveals his or her hand. You choose a card from it with converted mana cost 4 or greater and exile that card. SetInfo:AVR Uncommon \ No newline at end of file diff --git a/res/cardsfolder/b/bribery.txt b/res/cardsfolder/b/bribery.txt index 0026c3d0096..24fb758b0e4 100644 --- a/res/cardsfolder/b/bribery.txt +++ b/res/cardsfolder/b/bribery.txt @@ -1,7 +1,7 @@ Name:Bribery ManaCost:3 U U Types:Sorcery -A:SP$ ChangeZone | Cost$ 3 U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Creature | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | SpellDescription$ Search target opponent's library for a creature card and put that card onto the battlefield under your control. Then that player shuffles his or her library. +A:SP$ ChangeZone | Cost$ 3 U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Creature | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a creature card and put that card onto the battlefield under your control. Then that player shuffles his or her library. SVar:Picture:http://resources.wizards.com/magic/cards/mm/en-us/card21300.jpg Oracle:Search target opponent's library for a creature card and put that card onto the battlefield under your control. Then that player shuffles his or her library. SetInfo:MMQ Rare diff --git a/res/cardsfolder/c/castigate.txt b/res/cardsfolder/c/castigate.txt index 19cb2ae1d12..38105fcc76d 100644 --- a/res/cardsfolder/c/castigate.txt +++ b/res/cardsfolder/c/castigate.txt @@ -1,7 +1,7 @@ Name:Castigate ManaCost:W B Types:Sorcery -A:SP$ ChangeZone | Cost$ W B | Origin$ Hand | Destination$ Exile | DefinedPlayer$ Targeted | ValidTgts$ Opponent | Chooser$ You | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a nonland card from it and exile that card. +A:SP$ ChangeZone | Cost$ W B | Origin$ Hand | Destination$ Exile | DefinedPlayer$ Targeted | ValidTgts$ Opponent | Chooser$ You | ChangeType$ Card.nonLand | ChangeNum$ 1 | IsCurse$ True | Mandatory$ True | StackDescription$ SpellDescription | SpellDescription$ Target opponent reveals his or her hand. You choose a nonland card from it and exile that card. SVar:Picture:http://www.wizards.com/global/images/magic/general/castigate.jpg Oracle:Target opponent reveals his or her hand. You choose a nonland card from it and exile that card. SetInfo:GPT Common \ No newline at end of file diff --git a/res/cardsfolder/e/eternal_dominion.txt b/res/cardsfolder/e/eternal_dominion.txt index 6298f313d8a..66ab16604bc 100644 --- a/res/cardsfolder/e/eternal_dominion.txt +++ b/res/cardsfolder/e/eternal_dominion.txt @@ -2,7 +2,7 @@ Name:Eternal Dominion ManaCost:7 U U U Types:Sorcery K:Epic -A:SP$ ChangeZone | Cost$ 7 U U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Artifact,Creature,Enchantment,Land | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | SpellDescription$ Search target opponent's library for a creature card and put that card onto the battlefield under your control. Then that player shuffles his or her library. +A:SP$ ChangeZone | Cost$ 7 U U U | Origin$ Library | Destination$ Battlefield | ValidTgts$ Opponent | ChangeType$ Artifact,Creature,Enchantment,Land | ChangeNum$ 1 | GainControl$ True | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a creature card and put that card onto the battlefield under your control. Then that player shuffles his or her library. SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/eternal_dominion.jpg Oracle:Search target opponent's library for an artifact, creature, enchantment, or land card. Put that card onto the battlefield under your control. Then that player shuffles his or her library.\nEpic (For the rest of the game, you can't cast spells. At the beginning of each of your upkeeps, copy this spell except for its epic ability. You may choose a new target for the copy.) diff --git a/res/cardsfolder/h/hide_seek.txt b/res/cardsfolder/h/hide_seek.txt index c5a0d04cdb1..521c315ae8f 100644 --- a/res/cardsfolder/h/hide_seek.txt +++ b/res/cardsfolder/h/hide_seek.txt @@ -12,7 +12,7 @@ ALTERNATE Name:Seek ManaCost:W B Types:Instant -A:SP$ ChangeZone | Cost$ W B | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | Origin$ Library | DefinedPlayer$ Targeted | Chooser$ You | Destination$ Exile | Changetype$ Card | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBGainLife | SpellDescription$ Search target opponent's library for a card and exile it. You gain life equal to its converted mana cost. Then that player shuffles his or her library. +A:SP$ ChangeZone | Cost$ W B | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | Origin$ Library | DefinedPlayer$ Targeted | Chooser$ You | Destination$ Exile | Changetype$ Card | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBGainLife | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a card and exile it. You gain life equal to its converted mana cost. Then that player shuffles his or her library. SVar:DBGainLife:DB$ GainLife | LifeAmount$ X | References$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Remembered$CardManaCost diff --git a/res/cardsfolder/k/knowledge_exploitation.txt b/res/cardsfolder/k/knowledge_exploitation.txt index c9b8e1c34c6..fea12faee6d 100644 --- a/res/cardsfolder/k/knowledge_exploitation.txt +++ b/res/cardsfolder/k/knowledge_exploitation.txt @@ -1,8 +1,8 @@ Name:Knowledge Exploitation ManaCost:5 U U Types:Tribal Instant Rogue -A:SP$ ChangeZone | Cost$ 5 U U | ValidTgts$ Opponent | Origin$ Library | Destination$ Library | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | RememberChanged$ True | Reveal$ True | Shuffle$ False | DefinedPlayer$ Targeted | Chooser$ You | Mandatory$ True | SubAbility$ DBPlay | SpellDescription$ Search target opponent's library for an instant or sorcery card. You may cast that card without paying its mana cost. Then that player shuffles his or her library. -A:SP$ ChangeZone | Cost$ 3 U | Activation$ Prowl | ValidTgts$ Opponent | Origin$ Library | Destination$ Library | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | RememberChanged$ True | Reveal$ True | Shuffle$ False | PrecostDesc$ Prowl 3 U | DefinedPlayer$ Targeted | Chooser$ You | Mandatory$ True | SubAbility$ DBPlay | SpellDescription$ (You may cast this for its prowl cost if you dealt combat damage to a player this turn with a Rogue.) +A:SP$ ChangeZone | Cost$ 5 U U | ValidTgts$ Opponent | Origin$ Library | Destination$ Library | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | RememberChanged$ True | Reveal$ True | Shuffle$ False | DefinedPlayer$ Targeted | Chooser$ You | Mandatory$ True | SubAbility$ DBPlay | StackDescription$ Search {p:Targeted}'s library for an instant or sorcery card | SpellDescription$ Search target opponent's library for an instant or sorcery card. You may cast that card without paying its mana cost. Then that player shuffles his or her library. +A:SP$ ChangeZone | Cost$ 3 U | Activation$ Prowl | ValidTgts$ Opponent | Origin$ Library | Destination$ Library | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | RememberChanged$ True | Reveal$ True | Shuffle$ False | PrecostDesc$ Prowl 3 U | DefinedPlayer$ Targeted | Chooser$ You | Mandatory$ True | SubAbility$ DBPlay | StackDescription$ Search {p:Targeted}'s library for an instant or sorcery card | SpellDescription$ (You may cast this for its prowl cost if you dealt combat damage to a player this turn with a Rogue.) SVar:DBPlay:DB$ Play | Defined$ Remembered | Controller$ You | WithoutManaCost$ True | Optional$ True | SubAbility$ DBShuffle SVar:DBShuffle:DB$ Shuffle | Defined$ RememberedController | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/res/cardsfolder/p/painful_memories.txt b/res/cardsfolder/p/painful_memories.txt index 7e717e433fa..bf26199db3a 100644 --- a/res/cardsfolder/p/painful_memories.txt +++ b/res/cardsfolder/p/painful_memories.txt @@ -1,7 +1,7 @@ Name:Painful Memories ManaCost:1 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 1 B | Origin$ Hand | Destination$ Library | LibraryPosition$ 0 | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | SpellDescription$ Look at target opponent's hand and choose a card from it. Put that card on top of that player's library. +A:SP$ ChangeZone | Cost$ 1 B | Origin$ Hand | Destination$ Library | LibraryPosition$ 0 | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | StackDescription$ SpellDescription | SpellDescription$ Look at target opponent's hand and choose a card from it. Put that card on top of that player's library. SVar:Picture:http://www.wizards.com/global/images/magic/general/painful_memories.jpg Oracle:Look at target opponent's hand and choose a card from it. Put that card on top of that player's library. SetInfo:MIR Uncommon diff --git a/res/cardsfolder/p/parallax_nexus.txt b/res/cardsfolder/p/parallax_nexus.txt index 27cc94eb053..7b3bbb499c6 100644 --- a/res/cardsfolder/p/parallax_nexus.txt +++ b/res/cardsfolder/p/parallax_nexus.txt @@ -2,7 +2,7 @@ Name:Parallax Nexus ManaCost:2 B Types:Enchantment K:Fading:5 -A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Opponent | SorcerySpeed$ True | TgtPrompt$ Select target opponent | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | RememberChanged$ True | Chooser$ Targeted | IsCurse$ True | Mandatory$ True | Hidden$ True | SpellDescription$ Target opponent exiles a card from his or her hand. Activate this ability only any time you could cast a sorcery. +A:AB$ ChangeZone | Cost$ SubCounter<1/FADE> | ValidTgts$ Opponent | SorcerySpeed$ True | TgtPrompt$ Select target opponent | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | RememberChanged$ True | Chooser$ Targeted | IsCurse$ True | Mandatory$ True | Hidden$ True | StackDescription$ SpellDescription | SpellDescription$ Target opponent exiles a card from his or her hand. Activate this ability only any time you could cast a sorcery. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME leaves the battlefield, each player returns to his or her hand all cards he or she owns exiled with CARDNAME. SVar:TrigReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Hand | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/res/cardsfolder/p/perish_the_thought.txt b/res/cardsfolder/p/perish_the_thought.txt index 7d49817042d..1944e0f12af 100644 --- a/res/cardsfolder/p/perish_the_thought.txt +++ b/res/cardsfolder/p/perish_the_thought.txt @@ -1,7 +1,7 @@ Name:Perish the Thought ManaCost:2 B Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 B | Origin$ Hand | Destination$ Library | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | Shuffle$ True | Mandatory$ True | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library. +A:SP$ ChangeZone | Cost$ 2 B | Origin$ Hand | Destination$ Library | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | Shuffle$ True | Mandatory$ True | StackDescription$ SpellDescription | SpellDescription$ Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library. SVar:Picture:http://www.wizards.com/global/images/magic/general/perish_the_thought.jpg Oracle:Target opponent reveals his or her hand. You choose a card from it. That player shuffles that card into his or her library. SetInfo:ROE Common \ No newline at end of file diff --git a/res/cardsfolder/p/praetors_grasp.txt b/res/cardsfolder/p/praetors_grasp.txt index 6fb4fec4d62..10937cc0982 100644 --- a/res/cardsfolder/p/praetors_grasp.txt +++ b/res/cardsfolder/p/praetors_grasp.txt @@ -1,7 +1,7 @@ Name:Praetor's Grasp ManaCost:1 B B Types:Sorcery -A:SP$ ChangeZone | Cost$ 1 B B | Origin$ Library | Destination$ Exile | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ DBPump | SpellDescription$ Search target opponent's library for a card and exile it face down. Then that player shuffles his or her library. You may look at and play that card for as long as it remains exiled. +A:SP$ ChangeZone | Cost$ 1 B B | Origin$ Library | Destination$ Exile | ValidTgts$ Opponent | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | RememberChanged$ True | SubAbility$ DBPump | StackDescription$ SpellDescription | SpellDescription$ Search target opponent's library for a card and exile it face down. Then that player shuffles his or her library. You may look at and play that card for as long as it remains exiled. SVar:DBPump:DB$ Pump | Defined$ Remembered | KW$ Your opponent may look at this card. & May be played by your opponent | PumpZone$ Exile | Permanent$ True SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/praetors_grasp.jpg From f7a5217d6937e52482b67f1e23e212a1b9b577b5 Mon Sep 17 00:00:00 2001 From: moomarc Date: Tue, 2 Apr 2013 13:55:33 +0000 Subject: [PATCH 061/123] - Improved stack description for hidden origin ChangeZone abilities. Probably still room for improvement, but it should be way better than it was. --- .../ability/effects/ChangeZoneEffect.java | 123 ++++++++++++++---- 1 file changed, 97 insertions(+), 26 deletions(-) diff --git a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java index b62c3962279..e71c092672b 100644 --- a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java +++ b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java @@ -64,10 +64,51 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final Card host = sa.getSourceCard(); if (!(sa instanceof AbilitySub)) { - sb.append(host.getName()).append(" -"); + sb.append(" -"); } sb.append(" "); + + // Player whose cards will change zones + List fetchers = new ArrayList(); + if (sa.hasParam("DefinedPlayer")) { + fetchers = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("DefinedPlayer"), sa); + } + if (fetchers.isEmpty() && sa.hasParam("ValidTgts") && sa.getTarget() != null) { + fetchers = sa.getTarget().getTargetPlayers(); + } + if (fetchers.isEmpty()) { + fetchers.add(sa.getSourceCard().getController()); + } + + final StringBuilder fetcherSB = new StringBuilder(); + for (int i = 0; i < fetchers.size(); i++) { + fetcherSB.append(fetchers.get(i).getName()); + fetcherSB.append((i + 2) == fetchers.size() ? " and " : (i + 1) == fetchers.size() ? "" : ", "); + } + final String fetcherNames = fetcherSB.toString(); + + // Player who chooses the cards to move + List choosers = new ArrayList(); + if (sa.hasParam("Chooser")) { + choosers = AbilityUtils.getDefinedPlayers(sa.getSourceCard(), sa.getParam("Chooser"), sa); + } + if (choosers.isEmpty()) { + choosers.add(sa.getActivatingPlayer()); + } + + final StringBuilder chooserSB = new StringBuilder(); + for (int i = 0; i < choosers.size(); i++) { + chooserSB.append(choosers.get(i).getName()); + chooserSB.append((i + 2) == choosers.size() ? " and " : (i + 1) == choosers.size() ? "" : ", "); + } + final String chooserNames = chooserSB.toString(); + + String fetchPlayer = fetcherNames; + if (chooserNames.equals(fetcherNames)) { + fetchPlayer = fetchers.size() > 1 ? "their" : "his/her"; + } + String origin = ""; if (sa.hasParam("Origin")) { origin = sa.getParam("Origin"); @@ -91,49 +132,79 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } sb.append("."); } else if (origin.equals("Library")) { - sb.append("Search your library for ").append(num).append(" ").append(type).append(" and "); + sb.append(chooserNames); + sb.append(" search").append(choosers.size() > 1 ? " " : "es "); + sb.append(fetchPlayer); + sb.append("'s library for ").append(num).append(" ").append(type).append(" and "); - if (num == 1) { - sb.append("put that card "); + if (destination.equals("Exile")) { + if (num == 1) { + sb.append("exiles that card "); + } else { + sb.append("exiles those cards "); + } } else { - sb.append("put those cards "); + if (num == 1) { + sb.append("puts that card "); + } else { + sb.append("puts those cards "); + } + + if (destination.equals("Battlefield")) { + sb.append("onto the battlefield"); + if (sa.hasParam("Tapped")) { + sb.append(" tapped"); + } + if (sa.hasParam("GainControl")) { + sb.append(" under ").append(chooserNames).append("'s control"); + } + + sb.append("."); + + } + if (destination.equals("Hand")) { + sb.append("into its owner's hand."); + } + if (destination.equals("Graveyard")) { + sb.append("into its owners's graveyard."); + } + } + sb.append(" Then shuffle that library."); + } else if (origin.equals("Hand")) { + sb.append(chooserNames); + if (!chooserNames.equals(fetcherNames)) { + sb.append(" looks at " + fetcherNames + "'s hand and "); + sb.append(destination.equals("Exile") ? "exiles " : "puts "); + sb.append(num).append(" of those ").append(type).append(" card(s)"); + } else { + sb.append(destination.equals("Exile") ? " exiles " : " puts "); + sb.append(num).append(" ").append(type).append(" card(s) from"); + sb.append(fetchPlayer).append(" hand"); } if (destination.equals("Battlefield")) { - sb.append("onto the battlefield"); + sb.append(" onto the battlefield"); if (sa.hasParam("Tapped")) { sb.append(" tapped"); } - - sb.append("."); - - } - if (destination.equals("Hand")) { - sb.append("into your hand."); - } - if (destination.equals("Graveyard")) { - sb.append("into your graveyard."); - } - - sb.append(" Then shuffle your library."); - } else if (origin.equals("Hand")) { - sb.append("Put ").append(num).append(" ").append(type).append(" card(s) from your hand "); - - if (destination.equals("Battlefield")) { - sb.append("onto the battlefield."); + if (sa.hasParam("GainControl")) { + sb.append(" under ").append(chooserNames).append("'s control"); + } } if (destination.equals("Library")) { final int libraryPos = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : 0; if (libraryPos == 0) { - sb.append("on top"); + sb.append(" on top"); } if (libraryPos == -1) { - sb.append("on bottom"); + sb.append(" on the bottom"); } - sb.append(" of your library."); + sb.append(" of ").append(fetchPlayer).append("'s library"); } + + sb.append("."); } else if (origin.equals("Battlefield")) { // TODO Expand on this Description as more cards use it // for the non-targeted SAs when you choose what is returned on From bcc11d2391ea658b3f9e7a01f64962248cb78696 Mon Sep 17 00:00:00 2001 From: moomarc Date: Tue, 2 Apr 2013 15:32:08 +0000 Subject: [PATCH 062/123] - Converted Splitting Headache to proper Charm --- res/cardsfolder/s/splitting_headache.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/res/cardsfolder/s/splitting_headache.txt b/res/cardsfolder/s/splitting_headache.txt index ed9bb6e4a0d..aa47a6ce60d 100644 --- a/res/cardsfolder/s/splitting_headache.txt +++ b/res/cardsfolder/s/splitting_headache.txt @@ -1,8 +1,9 @@ Name:Splitting Headache ManaCost:3 B Types:Sorcery -A:SP$ Discard | Cost$ 3 B | ValidTgts$ Player | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ Choose one - Target player discards two cards; -A:SP$ Discard | Cost$ 3 B | ValidTgts$ Player | NumCards$ 1 | Mode$ RevealYouChoose | SpellDescription$ or target player reveals his or her hand, you choose a card from it, then that player discards that card. +A:SP$ Charm | Cost$ 3 B | Choices$ SplitDiscard,FocusDiscard | CharmNum$ 1 | SpellDescription$ Choose one - Target player discards two cards; or target player reveals his or her hand, you choose a card from it, then that player discards that card. +SVar:SplitDiscard:DB$ Discard | ValidTgts$ Player | NumCards$ 2 | Mode$ TgtChoose | SpellDescription$ Choose one - Target player discards two cards; +SVar:FocusDiscard:DB$ Discard | ValidTgts$ Player | NumCards$ 1 | Mode$ RevealYouChoose | SpellDescription$ Target player reveals his or her hand, you choose a card from it, then that player discards that card. SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/splitting_headache.jpg Oracle:Choose one - Target player discards two cards; or target player reveals his or her hand, you choose a card from it, then that player discards that card. From 3e0cd0ad44fa661b31345f8c5834fa42911e5f11 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Tue, 2 Apr 2013 19:00:50 +0000 Subject: [PATCH 063/123] Player.playSpellAbility moved to HumanPlayer since that method is used only for it. Removed a restriction that made impossible to play abilities from opoonent's cards --- .gitattributes | 2 +- .../forge/card/ability/effects/PlayEffect.java | 3 ++- .../forge/card/cardfactory/CardFactoryUtil.java | 3 ++- .../card/replacement/ReplacementHandler.java | 3 ++- ...uirements.java => HumanPlaySpellAbility.java} | 4 ++-- .../java/forge/card/trigger/TriggerHandler.java | 3 ++- .../java/forge/card/trigger/WrappedAbility.java | 3 ++- .../java/forge/control/input/InputMulligan.java | 3 ++- .../forge/control/input/InputPassPriority.java | 2 +- src/main/java/forge/game/GameActionPlay.java | 15 ++++++++------- src/main/java/forge/game/GameNew.java | 11 ++++++++++- src/main/java/forge/game/MatchController.java | 3 ++- src/main/java/forge/game/player/HumanPlayer.java | 14 ++++++++++++++ src/main/java/forge/game/player/Player.java | 14 -------------- .../forge/game/player/PlayerControllerHuman.java | 4 ++-- src/main/java/forge/game/zone/MagicStack.java | 3 ++- src/main/java/forge/gui/match/CMatchUI.java | 3 ++- .../forge/gui/match/nonsingleton/CField.java | 16 ++++++++-------- .../forge/gui/match/nonsingleton/VField.java | 3 ++- 19 files changed, 66 insertions(+), 46 deletions(-) rename src/main/java/forge/card/spellability/{SpellAbilityRequirements.java => HumanPlaySpellAbility.java} (98%) diff --git a/.gitattributes b/.gitattributes index c98d3efad4c..161b4226c87 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13761,11 +13761,11 @@ src/main/java/forge/card/spellability/AbilityManaPart.java svneol=native#text/pl src/main/java/forge/card/spellability/AbilityStatic.java svneol=native#text/plain src/main/java/forge/card/spellability/AbilitySub.java svneol=native#text/plain src/main/java/forge/card/spellability/AbilityTriggered.java svneol=native#text/plain +src/main/java/forge/card/spellability/HumanPlaySpellAbility.java svneol=native#text/plain src/main/java/forge/card/spellability/ISpellAbility.java -text src/main/java/forge/card/spellability/Spell.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbility.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbilityCondition.java svneol=native#text/plain -src/main/java/forge/card/spellability/SpellAbilityRequirements.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbilityRestriction.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbilityStackInstance.java svneol=native#text/plain src/main/java/forge/card/spellability/SpellAbilityVariables.java svneol=native#text/plain diff --git a/src/main/java/forge/card/ability/effects/PlayEffect.java b/src/main/java/forge/card/ability/effects/PlayEffect.java index 9c392278a10..e7fa53acdf4 100644 --- a/src/main/java/forge/card/ability/effects/PlayEffect.java +++ b/src/main/java/forge/card/ability/effects/PlayEffect.java @@ -20,6 +20,7 @@ import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -202,7 +203,7 @@ public class PlayEffect extends SpellAbilityEffect { boolean noManaCost = sa.hasParam("WithoutManaCost"); if (controller.isHuman()) { SpellAbility newSA = noManaCost ? tgtSA.copyWithNoManaCost() : tgtSA; - game.getActionPlay().playSpellAbility(newSA, activator); + game.getActionPlay().playSpellAbility(newSA, (HumanPlayer)activator); } else { if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell? Spell spell = (Spell) tgtSA; diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index e3dffb594cd..b73396e34a9 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -72,6 +72,7 @@ import forge.game.ai.ComputerUtilCost; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.PlayerZone; import forge.game.zone.Zone; @@ -2913,7 +2914,7 @@ public class CardFactoryUtil { } if (card.getController().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(card.getController(), origSA); + game.getActionPlay().playSpellAbilityNoStack((HumanPlayer)card.getController(), origSA); } else { ComputerUtil.playNoStack((AIPlayer) card.getController(), origSA, game); } diff --git a/src/main/java/forge/card/replacement/ReplacementHandler.java b/src/main/java/forge/card/replacement/ReplacementHandler.java index 7da37539dbc..2de3a967b87 100644 --- a/src/main/java/forge/card/replacement/ReplacementHandler.java +++ b/src/main/java/forge/card/replacement/ReplacementHandler.java @@ -31,6 +31,7 @@ import forge.card.spellability.SpellAbility; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -239,7 +240,7 @@ public class ReplacementHandler { Player player = replacementEffect.getHostCard().getController(); //player.getController().playNoStack() if (player.isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(player, effectSA); + game.getActionPlay().playSpellAbilityNoStack((HumanPlayer)player, effectSA); } else { ComputerUtil.playNoStack((AIPlayer) player, effectSA, game); } diff --git a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java similarity index 98% rename from src/main/java/forge/card/spellability/SpellAbilityRequirements.java rename to src/main/java/forge/card/spellability/HumanPlaySpellAbility.java index 114297c078e..171ce0d12ce 100644 --- a/src/main/java/forge/card/spellability/SpellAbilityRequirements.java +++ b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java @@ -37,11 +37,11 @@ import forge.game.zone.Zone; * @author Forge * @version $Id$ */ -public class SpellAbilityRequirements { +public class HumanPlaySpellAbility { private final SpellAbility ability; private final CostPayment payment; - public SpellAbilityRequirements(final SpellAbility sa, final CostPayment cp) { + public HumanPlaySpellAbility(final SpellAbility sa, final CostPayment cp) { this.ability = sa; this.payment = cp; } diff --git a/src/main/java/forge/card/trigger/TriggerHandler.java b/src/main/java/forge/card/trigger/TriggerHandler.java index ed81f1b4009..6905be17009 100644 --- a/src/main/java/forge/card/trigger/TriggerHandler.java +++ b/src/main/java/forge/card/trigger/TriggerHandler.java @@ -39,6 +39,7 @@ import forge.game.GlobalRuleChange; import forge.game.ai.ComputerUtil; import forge.game.phase.PhaseType; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; @@ -419,7 +420,7 @@ public class TriggerHandler { if (regtrig.isStatic()) { if (wrapperAbility.getActivatingPlayer().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(wrapperAbility.getActivatingPlayer(), wrapperAbility); + game.getActionPlay().playSpellAbilityNoStack((HumanPlayer)wrapperAbility.getActivatingPlayer(), wrapperAbility); } else { wrapperAbility.doTrigger(isMandatory, (AIPlayer)wrapperAbility.getActivatingPlayer()); ComputerUtil.playNoStack((AIPlayer)wrapperAbility.getActivatingPlayer(), wrapperAbility, game); diff --git a/src/main/java/forge/card/trigger/WrappedAbility.java b/src/main/java/forge/card/trigger/WrappedAbility.java index e98286e78a7..9bd4e7dcc77 100644 --- a/src/main/java/forge/card/trigger/WrappedAbility.java +++ b/src/main/java/forge/card/trigger/WrappedAbility.java @@ -19,6 +19,7 @@ import forge.card.spellability.Target; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.gui.GuiDialog; @@ -414,7 +415,7 @@ public class WrappedAbility extends Ability implements ISpellAbility { } if (getActivatingPlayer().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack(getActivatingPlayer(), sa, true); + game.getActionPlay().playSpellAbilityNoStack((HumanPlayer)getActivatingPlayer(), sa, true); } else { // commented out because i don't think this should be called // again here diff --git a/src/main/java/forge/control/input/InputMulligan.java b/src/main/java/forge/control/input/InputMulligan.java index 45be101462c..5a740107af0 100644 --- a/src/main/java/forge/control/input/InputMulligan.java +++ b/src/main/java/forge/control/input/InputMulligan.java @@ -33,6 +33,7 @@ import forge.game.GameType; import forge.game.MatchController; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; @@ -130,7 +131,7 @@ public class InputMulligan extends InputBase { if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { // If we ever let the AI memorize cards in the players // hand, this would be a place to do so. - game.getActionPlay().playSpellAbilityNoStack(p, effect); + game.getActionPlay().playSpellAbilityNoStack((HumanPlayer)p, effect); } } } diff --git a/src/main/java/forge/control/input/InputPassPriority.java b/src/main/java/forge/control/input/InputPassPriority.java index 2523deecadd..cb70b4fd519 100644 --- a/src/main/java/forge/control/input/InputPassPriority.java +++ b/src/main/java/forge/control/input/InputPassPriority.java @@ -86,7 +86,7 @@ public class InputPassPriority extends InputBase { Runnable execAbility = new Runnable() { @Override public void run() { - player.playSpellAbility(card, ab); + ((HumanPlayer)player).playSpellAbility(card, ab); } }; diff --git a/src/main/java/forge/game/GameActionPlay.java b/src/main/java/forge/game/GameActionPlay.java index b5d0950368c..cacc8b2ffe7 100644 --- a/src/main/java/forge/game/GameActionPlay.java +++ b/src/main/java/forge/game/GameActionPlay.java @@ -19,11 +19,12 @@ import forge.card.cost.CostPayment; import forge.card.mana.ManaCostBeingPaid; import forge.card.mana.ManaCostShard; import forge.card.spellability.SpellAbility; -import forge.card.spellability.SpellAbilityRequirements; +import forge.card.spellability.HumanPlaySpellAbility; import forge.card.spellability.Target; import forge.card.staticability.StaticAbility; import forge.control.input.InputPayManaSimple; import forge.game.ai.ComputerUtilCard; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -76,7 +77,7 @@ public class GameActionPlay { } final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); - final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment); + final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); req.fillRequirements(false, true, false); } else { if (sa.isSpell()) { @@ -351,7 +352,7 @@ public class GameActionPlay { * @param sa * a {@link forge.card.spellability.SpellAbility} object. */ - public final void playSpellAbility(SpellAbility sa, Player activator) { + public final void playSpellAbility(SpellAbility sa, HumanPlayer activator) { FThreads.checkEDT("Player.playSpellAbility", false); sa.setActivatingPlayer(activator); @@ -389,7 +390,7 @@ public class GameActionPlay { payment = new CostPayment(sa.getPayCosts(), sa); } - final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment); + final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); req.fillRequirements(false, false, false); } else { ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); @@ -424,10 +425,10 @@ public class GameActionPlay { * @param skipTargeting * a boolean. */ - public final void playSpellAbilityNoStack(final Player human, final SpellAbility sa) { + public final void playSpellAbilityNoStack(final HumanPlayer human, final SpellAbility sa) { playSpellAbilityNoStack(human, sa, false); } - public final void playSpellAbilityNoStack(final Player human, final SpellAbility sa, boolean useOldTargets) { + public final void playSpellAbilityNoStack(final HumanPlayer human, final SpellAbility sa, boolean useOldTargets) { sa.setActivatingPlayer(human); if (sa.getPayCosts() != null) { @@ -437,7 +438,7 @@ public class GameActionPlay { payment.changeCost(); } - final SpellAbilityRequirements req = new SpellAbilityRequirements(sa, payment); + final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); req.fillRequirements(useOldTargets, false, true); } else { diff --git a/src/main/java/forge/game/GameNew.java b/src/main/java/forge/game/GameNew.java index eaa8c82c76e..c10f43516cf 100644 --- a/src/main/java/forge/game/GameNew.java +++ b/src/main/java/forge/game/GameNew.java @@ -11,6 +11,8 @@ import java.util.Set; import javax.swing.JOptionPane; +import org.apache.commons.lang.time.StopWatch; + import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; @@ -19,6 +21,7 @@ import com.google.common.collect.Lists; import forge.Card; import forge.CardLists; import forge.CardPredicates; +import forge.FThreads; import forge.Singletons; import forge.card.trigger.TriggerHandler; import forge.card.trigger.TriggerType; @@ -117,7 +120,13 @@ public class GameNew { boolean canSideBoard = !isFirstGame && gameType.isSideboardingAllowed() && hasSideboard; if (canSideBoard) { - psc.setCurrentDeck(player.getController().sideboard(psc.getCurrentDeck(), gameType)); + StopWatch sw = new StopWatch(); + sw.start(); + System.out.println(FThreads.debugGetCurrThreadId() + " > " + "entering sideboard routine"); + Deck sideboarded = player.getController().sideboard(psc.getCurrentDeck(), gameType); + sw.stop(); + System.out.println(FThreads.debugGetCurrThreadId() + " > " + "sideboard routine returned after " + sw.getTime() + " ms"); + psc.setCurrentDeck(sideboarded); } else { psc.restoreOriginalDeck(); } diff --git a/src/main/java/forge/game/MatchController.java b/src/main/java/forge/game/MatchController.java index 1119eb528d6..0688f3345aa 100644 --- a/src/main/java/forge/game/MatchController.java +++ b/src/main/java/forge/game/MatchController.java @@ -16,6 +16,7 @@ import forge.error.BugReporter; import forge.game.ai.AiProfileUtil; import forge.game.event.DuelOutcomeEvent; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.LobbyPlayer; import forge.game.player.Player; import forge.game.player.PlayerStatistics; @@ -156,7 +157,7 @@ public class MatchController { try { - Player localHuman = Aggregates.firstFieldEquals(currentGame.getPlayers(), Player.Accessors.FN_GET_TYPE, PlayerType.HUMAN); + HumanPlayer localHuman = (HumanPlayer) Aggregates.firstFieldEquals(currentGame.getPlayers(), Player.Accessors.FN_GET_TYPE, PlayerType.HUMAN); FControl.SINGLETON_INSTANCE.setPlayer(localHuman); CMatchUI.SINGLETON_INSTANCE.initMatch(currentGame.getRegisteredPlayers(), localHuman); CDock.SINGLETON_INSTANCE.onGameStarts(currentGame, localHuman); diff --git a/src/main/java/forge/game/player/HumanPlayer.java b/src/main/java/forge/game/player/HumanPlayer.java index 26722254817..a8c14d7ab0d 100644 --- a/src/main/java/forge/game/player/HumanPlayer.java +++ b/src/main/java/forge/game/player/HumanPlayer.java @@ -21,6 +21,7 @@ import java.util.List; import forge.Card; import forge.FThreads; +import forge.card.spellability.Ability; import forge.card.spellability.SpellAbility; import forge.control.input.InputSelectCards; import forge.control.input.InputSelectCardsFromList; @@ -57,6 +58,19 @@ public class HumanPlayer extends Player { c.getController().discard(c, sa); } // input_discardNumUnless + /** + * TODO: Write javadoc for this method. + * @param card + * @param ab + */ + public void playSpellAbility(Card c, SpellAbility ab) { + if (ab == Ability.PLAY_LAND_SURROGATE) + this.playLand(c); + else { + game.getActionPlay().playSpellAbility(ab, this); + } + game.getPhaseHandler().setPriority(this); + } @Override public PlayerType getType() { diff --git a/src/main/java/forge/game/player/Player.java b/src/main/java/forge/game/player/Player.java index 4f0f30a702a..29b0cbef43b 100644 --- a/src/main/java/forge/game/player/Player.java +++ b/src/main/java/forge/game/player/Player.java @@ -2890,20 +2890,6 @@ public abstract class Player extends GameEntity implements Comparable { this.startingHandSize = shs; } - /** - * TODO: Write javadoc for this method. - * @param card - * @param ab - */ - public void playSpellAbility(Card c, SpellAbility ab) { - if (ab == Ability.PLAY_LAND_SURROGATE) - this.playLand(c); - else { - game.getActionPlay().playSpellAbility(ab, this); - } - game.getPhaseHandler().setPriority(this); - } - /** * * Takes the top plane of the planar deck and put it face up in the command zone. diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index 97a735e5b0b..dc52d39ab0c 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -364,14 +364,14 @@ public class PlayerControllerHuman extends PlayerController { @Override public void playMiracle(SpellAbility miracle, Card card) { if (GuiDialog.confirm(card, card + " - Drawn. Play for Miracle Cost?")) { - game.getActionPlay().playSpellAbility(miracle, getPlayer()); + game.getActionPlay().playSpellAbility(miracle, player); } } @Override public void playMadness(SpellAbility madness) { if (GuiDialog.confirm(madness.getSourceCard(), madness.getSourceCard() + " - Discarded. Pay Madness Cost?")) { - game.getActionPlay().playSpellAbility(madness, getPlayer()); + game.getActionPlay().playSpellAbility(madness, player); } } } diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index 9253d8a6824..349d1f8d1b6 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -57,6 +57,7 @@ import forge.game.ai.ComputerUtilCost; import forge.game.event.SpellResolvedEvent; import forge.game.phase.PhaseType; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.gui.GuiChoose; import forge.gui.framework.EDocID; @@ -1124,7 +1125,7 @@ public class MagicStack extends MyObservable { for (int i = size - 1; i >= 0; i--) { SpellAbility next = orderedSAs.get(i); if (next.isTrigger()) { - game.getActionPlay().playSpellAbility(next, activePlayer); + game.getActionPlay().playSpellAbility(next, (HumanPlayer)activePlayer); } else { this.add(next); } diff --git a/src/main/java/forge/gui/match/CMatchUI.java b/src/main/java/forge/gui/match/CMatchUI.java index 505a8deac99..618f34c5897 100644 --- a/src/main/java/forge/gui/match/CMatchUI.java +++ b/src/main/java/forge/gui/match/CMatchUI.java @@ -29,6 +29,7 @@ import forge.GameEntity; import forge.ImageCache; import forge.Singletons; import forge.game.phase.PhaseType; +import forge.game.player.HumanPlayer; import forge.game.player.LobbyPlayer; import forge.game.player.Player; import forge.gui.framework.EDocID; @@ -77,7 +78,7 @@ public enum CMatchUI { * @param numFieldPanels int * @param numHandPanels int */ - public void initMatch(final List players, Player localPlayer) { + public void initMatch(final List players, HumanPlayer localPlayer) { // TODO fix for use with multiplayer final String[] indices = Singletons.getModel().getPreferences().getPref(FPref.UI_AVATARS).split(","); diff --git a/src/main/java/forge/gui/match/nonsingleton/CField.java b/src/main/java/forge/gui/match/nonsingleton/CField.java index 51e5f29125a..46411578830 100644 --- a/src/main/java/forge/gui/match/nonsingleton/CField.java +++ b/src/main/java/forge/gui/match/nonsingleton/CField.java @@ -43,7 +43,9 @@ import forge.control.input.Input; import forge.control.input.InputAttack; import forge.control.input.InputBlock; import forge.control.input.InputPayManaBase; +import forge.game.GameState; import forge.game.phase.CombatUtil; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.PlayerZone; import forge.game.zone.ZoneType; @@ -63,7 +65,7 @@ public class CField implements ICDoc { // The one who owns cards on this side of table private final Player player; // Tho one who looks at screen and 'performs actions' - private final Player playerViewer; + private final HumanPlayer playerViewer; private final VField view; private boolean initializedAlready = false; @@ -155,9 +157,9 @@ public class CField implements ICDoc { * @param v0   {@link forge.gui.match.nonsingleton.VField} * @param playerViewer */ - public CField(final Player p0, final VField v0, Player playerViewer) { + public CField(final Player p0, final VField v0, HumanPlayer playerViewer) { this.player = p0; - this.playerViewer = playerViewer;; + this.playerViewer = playerViewer; this.view = v0; } @@ -341,13 +343,11 @@ public class CField implements ICDoc { @Override protected void doAction(final Card c) { - if ( CField.this.player != CField.this.playerViewer ) - return; - - final SpellAbility ab = player.getController().getAbilityToPlay(player.getGame().getAbilitesOfCard(c, player)); + final GameState game = player.getGame(); + final SpellAbility ab = CField.this.playerViewer.getController().getAbilityToPlay(game.getAbilitesOfCard(c, CField.this.playerViewer)); if ( null != ab) { FThreads.invokeInNewThread(new Runnable(){ @Override public void run(){ - player.playSpellAbility(c, ab); + CField.this.playerViewer.playSpellAbility(c, ab); }}); } } diff --git a/src/main/java/forge/gui/match/nonsingleton/VField.java b/src/main/java/forge/gui/match/nonsingleton/VField.java index 1efc4e95d9f..c414639ec33 100644 --- a/src/main/java/forge/gui/match/nonsingleton/VField.java +++ b/src/main/java/forge/gui/match/nonsingleton/VField.java @@ -36,6 +36,7 @@ import net.miginfocom.swing.MigLayout; import forge.card.cardfactory.CardFactoryUtil; import forge.card.mana.ManaPool; import forge.game.phase.PhaseType; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.framework.DragCell; @@ -111,7 +112,7 @@ public class VField implements IVDoc { * @param playerOnwer   {@link forge.game.player.Player} * @param id0   {@link forge.gui.framework.EDocID} */ - public VField(final EDocID id0, final Player playerOnwer, Player playerViewer) { + public VField(final EDocID id0, final Player playerOnwer, HumanPlayer playerViewer) { this.docID = id0; id0.setDoc(this); From f760d39e7e2c714a9f7dbd55a9652da9bd69c39b Mon Sep 17 00:00:00 2001 From: Hellfish Date: Tue, 2 Apr 2013 19:13:44 +0000 Subject: [PATCH 064/123] *Added support for Partial Paris Mulligan. --- .gitattributes | 1 + .../forge/control/input/InputControl.java | 4 +- .../input/InputPartialParisMulligan.java | 234 ++++++++++++++++++ 3 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 src/main/java/forge/control/input/InputPartialParisMulligan.java diff --git a/.gitattributes b/.gitattributes index 161b4226c87..0e721e172b4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13842,6 +13842,7 @@ src/main/java/forge/control/input/InputCleanup.java svneol=native#text/plain src/main/java/forge/control/input/InputControl.java svneol=native#text/plain src/main/java/forge/control/input/InputLockUI.java -text src/main/java/forge/control/input/InputMulligan.java svneol=native#text/plain +src/main/java/forge/control/input/InputPartialParisMulligan.java -text src/main/java/forge/control/input/InputPassPriority.java svneol=native#text/plain src/main/java/forge/control/input/InputPayManaBase.java -text src/main/java/forge/control/input/InputPayManaExecuteCommands.java svneol=native#text/plain diff --git a/src/main/java/forge/control/input/InputControl.java b/src/main/java/forge/control/input/InputControl.java index cba5c1c743e..1dc92439be2 100644 --- a/src/main/java/forge/control/input/InputControl.java +++ b/src/main/java/forge/control/input/InputControl.java @@ -116,8 +116,10 @@ public class InputControl extends MyObservable implements java.io.Serializable { */ public final Input getActualInput(GameState game) { if ( !game.hasMulliganned() ) + { return new InputMulligan(Singletons.getModel().getMatch(), Singletons.getControl().getPlayer()); - + //return new InputPartialParisMulligan(Singletons.getModel().getMatch(), Singletons.getControl().getPlayer()); + } final PhaseHandler handler = game.getPhaseHandler(); final PhaseType phase = handler.getPhase(); final Player playerTurn = handler.getPlayerTurn(); diff --git a/src/main/java/forge/control/input/InputPartialParisMulligan.java b/src/main/java/forge/control/input/InputPartialParisMulligan.java new file mode 100644 index 00000000000..b8e5802bbd9 --- /dev/null +++ b/src/main/java/forge/control/input/InputPartialParisMulligan.java @@ -0,0 +1,234 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.control.input; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.Iterables; + +import forge.Card; +import forge.CardPredicates; +import forge.FThreads; +import forge.card.ability.AbilityFactory; +import forge.card.spellability.SpellAbility; +import forge.game.GameAction; +import forge.game.GameState; +import forge.game.GameType; +import forge.game.MatchController; +import forge.game.ai.ComputerUtil; +import forge.game.player.AIPlayer; +import forge.game.player.Player; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; +import forge.gui.GuiDialog; +import forge.gui.framework.SDisplayUtil; +import forge.gui.match.CMatchUI; +import forge.gui.match.nonsingleton.VField; +import forge.gui.match.views.VMessage; +import forge.view.ButtonUtil; + /** + *

+ * InputMulligan class. + *

+ * + * @author Forge + * @version $Id: InputMulligan.java 20698 2013-04-01 09:56:12Z Max mtg $ + */ +public class InputPartialParisMulligan extends InputBase { + /** Constant serialVersionUID=-8112954303001155622L. */ + private static final long serialVersionUID = -8112954303001155622L; + + private final MatchController match; + + private final List lastExiled = new ArrayList(); + + public InputPartialParisMulligan(MatchController match0, Player humanPlayer) { + super(humanPlayer); + match = match0; + } + + /** {@inheritDoc} */ + @Override + public final void showMessage() { + ButtonUtil.setButtonText("Done", "Exile"); + ButtonUtil.enableOnlyOk(); + + GameState game = match.getCurrentGame(); + Player startingPlayer = game.getPhaseHandler().getPlayerTurn(); + + StringBuilder sb = new StringBuilder(); + sb.append(startingPlayer.getName()).append(" is going first. "); + + if (!startingPlayer.equals(player)) { + sb.append("You are going ").append(game.getOrdinalPosition(player, startingPlayer)).append(". "); + } + + sb.append("Do you want to Mulligan?"); + showMessage(sb.toString()); + } + + /** {@inheritDoc} */ + @Override + public final void selectButtonOK() { + this.end(); + } + + /** {@inheritDoc} */ + @Override + public final void selectButtonCancel() { + for(Card c : lastExiled) + { + match.getCurrentGame().action.exile(c); + } + + player.drawCards(lastExiled.size()-1); + lastExiled.clear(); + + if (player.getCardsIn(ZoneType.Hand).isEmpty()) { + this.end(); + } else { + ButtonUtil.enableAllFocusOk(); + } + } + + final void end() { + + final GameState game = match.getCurrentGame(); + + // Computer mulligan + //TODO: How should AI approach Partial Paris? + /* + for (Player p : game.getPlayers()) { + if (!(p instanceof AIPlayer)) { + continue; + } + AIPlayer ai = (AIPlayer) p; + while (ComputerUtil.wantMulligan(ai)) { + ai.doMulligan(); + } + }*/ + + // Human Leylines & Chancellors + ButtonUtil.reset(); + + final GameAction ga = game.getAction(); + for (Player p : game.getPlayers()) { + final List openingHand = new ArrayList(p.getCardsIn(ZoneType.Hand)); + + for (final Card c : openingHand) { + if (p.isHuman()) { + for (String kw : c.getKeyword()) { + if (kw.startsWith("MayEffectFromOpeningHand")) { + final String effName = kw.split(":")[1]; + + final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); + if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { + // If we ever let the AI memorize cards in the players + // hand, this would be a place to do so. + game.getActionPlay().playSpellAbilityNoStack(p, effect); + } + } + } + if (c.getName().startsWith("Leyline of")) { + if (GuiDialog.confirm(c, "Use " + c + "'s ability?")) { + ga.moveToPlay(c); + } + } + } else { // Computer Leylines & Chancellors + if (!c.getName().startsWith("Leyline of")) { + for (String kw : c.getKeyword()) { + if (kw.startsWith("MayEffectFromOpeningHand")) { + final String effName = kw.split(":")[1]; + + final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); + + // Is there a better way for the AI to decide this? + if (effect.doTrigger(false, (AIPlayer)p)) { + GuiDialog.message("Computer reveals " + c.getName() + "(" + c.getUniqueNumber() + ")."); + ComputerUtil.playNoStack((AIPlayer)p, effect, game); + } + } + } + } + if (c.getName().startsWith("Leyline of") + && !(c.getName().startsWith("Leyline of Singularity") + && (Iterables.any(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Leyline of Singularity"))))) { + ga.moveToPlay(c); + //ga.checkStateEffects(); + } + } + } + } + + ga.checkStateEffects(); + + Player next = game.getPhaseHandler().getPlayerTurn(); + + if(game.getType() == GameType.Planechase) + { + next.initPlane(); + } + + //Set Field shown to current player. + VField nextField = CMatchUI.SINGLETON_INSTANCE.getFieldViewFor(next); + SDisplayUtil.showTab(nextField); + + game.setMulliganned(true); + FThreads.invokeInNewThread( new Runnable() { + @Override + public void run() { + match.getInput().clearInput(); + } + }); + } + + @Override + public void selectCard(Card c0) { + if(lastExiled.contains(c0)) + { + lastExiled.remove(c0); + c0.setUsedToPay(false); + return; + } + + Zone z0 = match.getCurrentGame().getZoneOf(c0); + if (c0.getName().equals("Serum Powder") && z0.is(ZoneType.Hand)) { + if (GuiDialog.confirm(c0, "Use " + c0.getName() + "'s ability?")) { + List hand = new ArrayList(c0.getController().getCardsIn(ZoneType.Hand)); + for (Card c : hand) { + match.getCurrentGame().getAction().exile(c); + } + c0.getController().drawCards(hand.size()); + } + else + { + lastExiled.add(c0); + c0.setUsedToPay(true); + } + } else { + lastExiled.add(c0); + c0.setUsedToPay(true); + } + + if(lastExiled.size() > 0) + ButtonUtil.enableAllFocusOk(); + else + ButtonUtil.enableOnlyOk(); + } +} From 02cff88cf9c39ede0af97b3f6579ad9c272dc159 Mon Sep 17 00:00:00 2001 From: Hellfish Date: Tue, 2 Apr 2013 19:27:51 +0000 Subject: [PATCH 065/123] *Fixed enabling/disabling of buttons in partial paris mulligan. --- .../forge/control/input/InputControl.java | 11 +++++-- .../input/InputPartialParisMulligan.java | 31 ++++++++++--------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/main/java/forge/control/input/InputControl.java b/src/main/java/forge/control/input/InputControl.java index 1dc92439be2..5a92aa81191 100644 --- a/src/main/java/forge/control/input/InputControl.java +++ b/src/main/java/forge/control/input/InputControl.java @@ -21,6 +21,7 @@ import java.util.Stack; import forge.Singletons; import forge.game.GameState; +import forge.game.GameType; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -117,8 +118,14 @@ public class InputControl extends MyObservable implements java.io.Serializable { public final Input getActualInput(GameState game) { if ( !game.hasMulliganned() ) { - return new InputMulligan(Singletons.getModel().getMatch(), Singletons.getControl().getPlayer()); - //return new InputPartialParisMulligan(Singletons.getModel().getMatch(), Singletons.getControl().getPlayer()); + if(game.getType() == GameType.Commander) + { + return new InputPartialParisMulligan(Singletons.getModel().getMatch(), Singletons.getControl().getPlayer()); + } + else + { + return new InputMulligan(Singletons.getModel().getMatch(), Singletons.getControl().getPlayer()); + } } final PhaseHandler handler = game.getPhaseHandler(); final PhaseType phase = handler.getPhase(); diff --git a/src/main/java/forge/control/input/InputPartialParisMulligan.java b/src/main/java/forge/control/input/InputPartialParisMulligan.java index b8e5802bbd9..8ae068d8033 100644 --- a/src/main/java/forge/control/input/InputPartialParisMulligan.java +++ b/src/main/java/forge/control/input/InputPartialParisMulligan.java @@ -204,26 +204,27 @@ public class InputPartialParisMulligan extends InputBase { { lastExiled.remove(c0); c0.setUsedToPay(false); - return; } - - Zone z0 = match.getCurrentGame().getZoneOf(c0); - if (c0.getName().equals("Serum Powder") && z0.is(ZoneType.Hand)) { - if (GuiDialog.confirm(c0, "Use " + c0.getName() + "'s ability?")) { - List hand = new ArrayList(c0.getController().getCardsIn(ZoneType.Hand)); - for (Card c : hand) { - match.getCurrentGame().getAction().exile(c); + else + { + Zone z0 = match.getCurrentGame().getZoneOf(c0); + if (c0.getName().equals("Serum Powder") && z0.is(ZoneType.Hand)) { + if (GuiDialog.confirm(c0, "Use " + c0.getName() + "'s ability?")) { + List hand = new ArrayList(c0.getController().getCardsIn(ZoneType.Hand)); + for (Card c : hand) { + match.getCurrentGame().getAction().exile(c); + } + c0.getController().drawCards(hand.size()); } - c0.getController().drawCards(hand.size()); - } - else - { + else + { + lastExiled.add(c0); + c0.setUsedToPay(true); + } + } else { lastExiled.add(c0); c0.setUsedToPay(true); } - } else { - lastExiled.add(c0); - c0.setUsedToPay(true); } if(lastExiled.size() > 0) From 81b988ae1400508428bcd7ab5dcc1bc7dd63bf9d Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Tue, 2 Apr 2013 19:54:17 +0000 Subject: [PATCH 066/123] GuiChoose invokes ListChooser from EDT thread. --- src/main/java/forge/FThreads.java | 8 ++++ .../forge/control/input/InputPayManaBase.java | 5 +-- src/main/java/forge/gui/GuiChoose.java | 45 ++++++++++++------- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/main/java/forge/FThreads.java b/src/main/java/forge/FThreads.java index 8ca661eb834..f2924d62f9c 100644 --- a/src/main/java/forge/FThreads.java +++ b/src/main/java/forge/FThreads.java @@ -15,6 +15,14 @@ import forge.control.input.InputSynchronized; * */ public class FThreads { + + // This could be some bad copy of SwingWorker + public abstract static class RunnableWithResult implements Runnable { + protected T result = null; + public T getResult() { return result; } + + } + static { System.out.printf("(FThreads static ctor): Running on a machine with %d cpu core(s)%n", Runtime.getRuntime().availableProcessors() ); } diff --git a/src/main/java/forge/control/input/InputPayManaBase.java b/src/main/java/forge/control/input/InputPayManaBase.java index 4438fb45ecd..02ed79ecf6e 100644 --- a/src/main/java/forge/control/input/InputPayManaBase.java +++ b/src/main/java/forge/control/input/InputPayManaBase.java @@ -14,7 +14,7 @@ import forge.card.mana.ManaPool; import forge.card.spellability.AbilityManaPart; import forge.card.spellability.SpellAbility; import forge.game.GameState; -import forge.game.player.Player; +import forge.game.player.HumanPlayer; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.framework.SDisplayUtil; @@ -291,8 +291,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I Runnable proc = new Runnable() { @Override public void run() { - final Player p = chosen.getActivatingPlayer(); - p.getGame().getActionPlay().playSpellAbility(chosen, p); + player.getGame().getActionPlay().playSpellAbility(chosen, (HumanPlayer)chosen.getActivatingPlayer()); onManaAbilityPlayed(chosen); } }; diff --git a/src/main/java/forge/gui/GuiChoose.java b/src/main/java/forge/gui/GuiChoose.java index 714165b3d88..28915386545 100644 --- a/src/main/java/forge/gui/GuiChoose.java +++ b/src/main/java/forge/gui/GuiChoose.java @@ -7,6 +7,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.concurrent.Callable; import javax.swing.JDialog; import javax.swing.JFrame; @@ -16,6 +17,7 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import forge.Card; +import forge.FThreads; import forge.gui.match.CMatchUI; import forge.item.InventoryItem; @@ -102,25 +104,34 @@ public class GuiChoose { } } - ListChooser c = new ListChooser(message, min, max, choices); - final JList list = c.getJList(); - list.addListSelectionListener(new ListSelectionListener() { + FThreads.RunnableWithResult> showChoice = new FThreads.RunnableWithResult>() { + @Override - public void valueChanged(final ListSelectionEvent ev) { - if (list.getSelectedValue() instanceof Card) { - CMatchUI.SINGLETON_INSTANCE.setCard((Card) list.getSelectedValue()); - - GuiUtils.clearPanelSelections(); - GuiUtils.setPanelSelection((Card) list.getSelectedValue()); - } - if (list.getSelectedValue() instanceof InventoryItem) { - CMatchUI.SINGLETON_INSTANCE.setCard((InventoryItem) list.getSelectedValue()); - } + public void run() { + ListChooser c = new ListChooser(message, min, max, choices); + final JList list = c.getJList(); + list.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(final ListSelectionEvent ev) { + if (list.getSelectedValue() instanceof Card) { + CMatchUI.SINGLETON_INSTANCE.setCard((Card) list.getSelectedValue()); + + GuiUtils.clearPanelSelections(); + GuiUtils.setPanelSelection((Card) list.getSelectedValue()); + } + if (list.getSelectedValue() instanceof InventoryItem) { + CMatchUI.SINGLETON_INSTANCE.setCard((InventoryItem) list.getSelectedValue()); + } + } + }); + c.show(); + GuiUtils.clearPanelSelections(); + result = c.getSelectedValues(); } - }); - c.show(); - GuiUtils.clearPanelSelections(); - return c.getSelectedValues(); + }; + + FThreads.invokeInEDTAndWait(showChoice); + return showChoice.getResult(); } // Nothing to choose here. Code uses this to just show a card. From a7a6a199e8dbc7b37831ce4cbe1b5fd0a84d3a4c Mon Sep 17 00:00:00 2001 From: Hellfish Date: Tue, 2 Apr 2013 20:15:14 +0000 Subject: [PATCH 067/123] *How did I misread the part about shuffling exiled cards back in after mulligan? Jeez. --- .../control/input/InputPartialParisMulligan.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/forge/control/input/InputPartialParisMulligan.java b/src/main/java/forge/control/input/InputPartialParisMulligan.java index 8ae068d8033..4fa8d68e9eb 100644 --- a/src/main/java/forge/control/input/InputPartialParisMulligan.java +++ b/src/main/java/forge/control/input/InputPartialParisMulligan.java @@ -57,6 +57,7 @@ public class InputPartialParisMulligan extends InputBase { private final MatchController match; private final List lastExiled = new ArrayList(); + private final List allExiled = new ArrayList(); public InputPartialParisMulligan(MatchController match0, Player humanPlayer) { super(humanPlayer); @@ -98,6 +99,7 @@ public class InputPartialParisMulligan extends InputBase { } player.drawCards(lastExiled.size()-1); + allExiled.addAll(lastExiled); lastExiled.clear(); if (player.getCardsIn(ZoneType.Hand).isEmpty()) { @@ -107,9 +109,15 @@ public class InputPartialParisMulligan extends InputBase { } } - final void end() { - + final void end() { + final GameState game = match.getCurrentGame(); + + for(Card c : allExiled) + { + game.action.moveToLibrary(c); + } + player.shuffle(); // Computer mulligan //TODO: How should AI approach Partial Paris? From 1f9e4b07e3e1eb0a2b8e477a6bde41a38195c871 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Tue, 2 Apr 2013 20:21:11 +0000 Subject: [PATCH 068/123] GuiChoose.order is also always invoked by EDT. Found a right wrapper class to pass Callables to EDT, --- src/main/java/forge/FThreads.java | 7 -- src/main/java/forge/gui/GuiChoose.java | 95 ++++++++++++++++---------- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/src/main/java/forge/FThreads.java b/src/main/java/forge/FThreads.java index f2924d62f9c..a5e4be097b0 100644 --- a/src/main/java/forge/FThreads.java +++ b/src/main/java/forge/FThreads.java @@ -16,13 +16,6 @@ import forge.control.input.InputSynchronized; */ public class FThreads { - // This could be some bad copy of SwingWorker - public abstract static class RunnableWithResult implements Runnable { - protected T result = null; - public T getResult() { return result; } - - } - static { System.out.printf("(FThreads static ctor): Running on a machine with %d cpu core(s)%n", Runtime.getRuntime().availableProcessors() ); } diff --git a/src/main/java/forge/gui/GuiChoose.java b/src/main/java/forge/gui/GuiChoose.java index 28915386545..da5b50cac0f 100644 --- a/src/main/java/forge/gui/GuiChoose.java +++ b/src/main/java/forge/gui/GuiChoose.java @@ -8,6 +8,9 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; import javax.swing.JDialog; import javax.swing.JFrame; @@ -104,10 +107,9 @@ public class GuiChoose { } } - FThreads.RunnableWithResult> showChoice = new FThreads.RunnableWithResult>() { - + Callable> showChoice = new Callable>() { @Override - public void run() { + public List call() { ListChooser c = new ListChooser(message, min, max, choices); final JList list = c.getJList(); list.addListSelectionListener(new ListSelectionListener() { @@ -126,12 +128,18 @@ public class GuiChoose { }); c.show(); GuiUtils.clearPanelSelections(); - result = c.getSelectedValues(); + return c.getSelectedValues(); } }; - FThreads.invokeInEDTAndWait(showChoice); - return showChoice.getResult(); + FutureTask> future = new FutureTask>(showChoice); + FThreads.invokeInEDTAndWait(future); + try { + return future.get(); + } catch (Exception e) { // should be no exception here + e.printStackTrace(); + } + return null; } // Nothing to choose here. Code uses this to just show a card. @@ -153,39 +161,54 @@ public class GuiChoose { } - public static List order(final String title, final String top, int remainingObjects, - final List sourceChoices, List destChoices, Card referenceCard, boolean sideboardingMode) { + public static List order(final String title, final String top, final int remainingObjects, + final List sourceChoices, final List destChoices, final Card referenceCard, final boolean sideboardingMode) { // An input box for handling the order of choices. - final JFrame frame = new JFrame(); - DualListBox dual = new DualListBox(remainingObjects, sourceChoices, destChoices); - dual.setSecondColumnLabelText(top); + + Callable> callable = new Callable>() { + @Override + public List call() throws Exception { + final JFrame frame = new JFrame(); + DualListBox dual = new DualListBox(remainingObjects, sourceChoices, destChoices); + dual.setSecondColumnLabelText(top); + + frame.setLayout(new BorderLayout()); + frame.setSize(dual.getPreferredSize()); + frame.add(dual); + frame.setTitle(title); + frame.setVisible(false); + + dual.setSideboardMode(sideboardingMode); + + final JDialog dialog = new JDialog(frame, true); + dialog.setTitle(title); + dialog.setContentPane(dual); + dialog.setSize(dual.getPreferredSize()); + dialog.setLocationRelativeTo(null); + dialog.pack(); + dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + if (referenceCard != null) { + CMatchUI.SINGLETON_INSTANCE.setCard(referenceCard); + // MARKED FOR UPDATE + } + dialog.setVisible(true); + + List objects = dual.getOrderedList(); + + dialog.dispose(); + GuiUtils.clearPanelSelections(); + return objects; + } + }; - frame.setLayout(new BorderLayout()); - frame.setSize(dual.getPreferredSize()); - frame.add(dual); - frame.setTitle(title); - frame.setVisible(false); - - dual.setSideboardMode(sideboardingMode); - - final JDialog dialog = new JDialog(frame, true); - dialog.setTitle(title); - dialog.setContentPane(dual); - dialog.setSize(dual.getPreferredSize()); - dialog.setLocationRelativeTo(null); - dialog.pack(); - dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - if (referenceCard != null) { - CMatchUI.SINGLETON_INSTANCE.setCard(referenceCard); - // MARKED FOR UPDATE + FutureTask> ft = new FutureTask>(callable); + FThreads.invokeInEDTAndWait(ft); + try { + return ft.get(); + } catch (Exception e) { // we have waited enough + e.printStackTrace(); } - dialog.setVisible(true); - - List objects = dual.getOrderedList(); - - dialog.dispose(); - GuiUtils.clearPanelSelections(); - return objects; + return null; } // If comparer is NULL, T has to be comparable. Otherwise you'll get an exception from inside the Arrays.sort() routine From d725eda5bca4b29abdf599668d7e9a15c7ddbf6b Mon Sep 17 00:00:00 2001 From: Sloth Date: Tue, 2 Apr 2013 21:55:19 +0000 Subject: [PATCH 069/123] - Fixed chooseAndPlaySa not setting activating player. --- src/main/java/forge/game/ai/AiController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/forge/game/ai/AiController.java b/src/main/java/forge/game/ai/AiController.java index 31715f92cde..9245a14eca6 100644 --- a/src/main/java/forge/game/ai/AiController.java +++ b/src/main/java/forge/game/ai/AiController.java @@ -750,6 +750,7 @@ public class AiController { /** Returns the spell ability which has already been played - use it for reference only */ public SpellAbility chooseAndPlaySa(final List choices, boolean mandatory, boolean withoutPayingManaCost) { for (final SpellAbility sa : choices) { + sa.setActivatingPlayer(player); //Spells if (sa instanceof Spell) { if (!((Spell) sa).canPlayFromEffectAI(mandatory, withoutPayingManaCost)) { From c4ee8cb9317a2276af1114c3878f1a0d1dfd21e4 Mon Sep 17 00:00:00 2001 From: Sloth Date: Tue, 2 Apr 2013 22:18:10 +0000 Subject: [PATCH 070/123] - Fixed Mercadian Atlas. --- res/cardsfolder/m/mercadian_atlas.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/cardsfolder/m/mercadian_atlas.txt b/res/cardsfolder/m/mercadian_atlas.txt index f56da9590af..382d01bae7f 100644 --- a/res/cardsfolder/m/mercadian_atlas.txt +++ b/res/cardsfolder/m/mercadian_atlas.txt @@ -1,7 +1,7 @@ Name:Mercadian Atlas ManaCost:5 Types:Artifact -T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ AtlasDraw | CheckSVar$ X | SVarCompare$ EQ0 | Optional$ True | TriggerDescription$ At the beginning of your end step, if you didn't play a land this turn, you may draw a card. +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ AtlasDraw | CheckSVar$ X | SVarCompare$ EQ0 | Optional$ True | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your end step, if you didn't play a land this turn, you may draw a card. SVar:AtlasDraw:DB$ Draw | Defined$ You | NumCards$ 1 SVar:X:Count$YourLandsPlayed SVar:Picture:http://www.wizards.com/global/images/magic/general/mercadian_atlas.jpg From 36cbb95d3c30e56b0ad6e36eb45fab05db04a4fe Mon Sep 17 00:00:00 2001 From: Sloth Date: Tue, 2 Apr 2013 22:34:18 +0000 Subject: [PATCH 071/123] - Improved Encode AI. --- .../card/ability/effects/EncodeEffect.java | 6 ++--- src/main/java/forge/game/ai/AiController.java | 24 +++++++++---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/main/java/forge/card/ability/effects/EncodeEffect.java b/src/main/java/forge/card/ability/effects/EncodeEffect.java index f8f57be27fb..39f72e203a4 100644 --- a/src/main/java/forge/card/ability/effects/EncodeEffect.java +++ b/src/main/java/forge/card/ability/effects/EncodeEffect.java @@ -35,8 +35,8 @@ public class EncodeEffect extends SpellAbilityEffect { choices = CardLists.getValidCards(choices, "Creature.YouCtrl", host.getController(), host); // if no creatures on battlefield, cannot encoded - if (choices.size() == 0) { - + if (choices.isEmpty()) { + return; } // Handle choice of whether or not to encoded @@ -46,8 +46,6 @@ public class EncodeEffect extends SpellAbilityEffect { if (!player.getController().confirmAction(sa, null, sb.toString())) { return; } - // Note: AI will always choose to encode - // TODO add better AI choice here // move host card to exile Card movedCard = Singletons.getModel().getGame().getAction().moveTo(ZoneType.Exile, host); diff --git a/src/main/java/forge/game/ai/AiController.java b/src/main/java/forge/game/ai/AiController.java index 9245a14eca6..ac386890e3e 100644 --- a/src/main/java/forge/game/ai/AiController.java +++ b/src/main/java/forge/game/ai/AiController.java @@ -45,6 +45,7 @@ import forge.card.spellability.SpellAbility; import forge.card.spellability.SpellPermanent; import forge.game.GameActionUtil; import forge.game.GameState; +import forge.game.phase.CombatUtil; import forge.game.phase.PhaseType; import forge.game.player.AIPlayer; import forge.game.player.Player; @@ -661,20 +662,17 @@ public class AiController { case Encode: if (logic == null) { - // Base Logic is choose "best" - choice = ComputerUtilCard.getBestAI(options); - } else if ("WorstCard".equals(logic)) { - choice = ComputerUtilCard.getWorstAI(options); - } else if (logic.equals("BestBlocker")) { - if (!CardLists.filter(options, Presets.UNTAPPED).isEmpty()) { - options = CardLists.filter(options, Presets.UNTAPPED); + List attackers = CardLists.filter(options, new Predicate() { + @Override + public boolean apply(final Card c) { + return CombatUtil.canAttackNextTurn(c); + } + }); + if (attackers.isEmpty()) { + choice = ComputerUtilCard.getBestAI(options); + } else { + choice = ComputerUtilCard.getBestAI(attackers); } - choice = ComputerUtilCard.getBestCreatureAI(options); - } else if (logic.equals("Clone")) { - if (!CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host).isEmpty()) { - options = CardLists.getValidCards(options, "Permanent.YouDontCtrl,Permanent.nonLegendary", host.getController(), host); - } - choice = ComputerUtilCard.getBestAI(options); } return choice; From 6e3d39b9ce437c15ce2e2f6fbfffb11fca6f7b32 Mon Sep 17 00:00:00 2001 From: Sloth Date: Tue, 2 Apr 2013 22:41:21 +0000 Subject: [PATCH 072/123] - Updated some SVars. --- res/cardsfolder/m/multanis_presence.txt | 1 + res/cardsfolder/r/reconnaissance.txt | 2 +- res/cardsfolder/r/runed_halo.txt | 1 + res/cardsfolder/s/starke_of_rath.txt | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/res/cardsfolder/m/multanis_presence.txt b/res/cardsfolder/m/multanis_presence.txt index b48909644dd..856e077dbfb 100644 --- a/res/cardsfolder/m/multanis_presence.txt +++ b/res/cardsfolder/m/multanis_presence.txt @@ -3,6 +3,7 @@ ManaCost:G Types:Enchantment T:Mode$ Countered | ValidCard$ Card | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ DBDraw | TriggerDescription$ Whenever a spell you've cast is countered, draw a card. SVar:DBDraw:AB$ Draw | Cost$ 0 | NumCards$ 1 +SVar:RemRandomDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/multanis_presence.jpg Oracle:Whenever a spell you've cast is countered, draw a card. SetInfo:ULG Uncommon \ No newline at end of file diff --git a/res/cardsfolder/r/reconnaissance.txt b/res/cardsfolder/r/reconnaissance.txt index 6488600f54f..8a1c56c5ad4 100644 --- a/res/cardsfolder/r/reconnaissance.txt +++ b/res/cardsfolder/r/reconnaissance.txt @@ -2,7 +2,7 @@ Name:Reconnaissance ManaCost:W Types:Enchantment A:AB$ RemoveFromCombat | Cost$ 0 | ValidTgts$ Creature.attacking+YouCtrl | TgtPrompt$ Select target attacking creature you control. | SubAbility$ DBUntap | SpellDescription$ Remove target attacking creature you control from combat and untap it. -SVar:DBUntap:DB$Untap | Defined$ Targeted +SVar:DBUntap:DB$ Untap | Defined$ Targeted SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/reconnaissance.jpg Oracle:{0}: Remove target attacking creature you control from combat and untap it. diff --git a/res/cardsfolder/r/runed_halo.txt b/res/cardsfolder/r/runed_halo.txt index a0e4629d059..95b189b4d5e 100644 --- a/res/cardsfolder/r/runed_halo.txt +++ b/res/cardsfolder/r/runed_halo.txt @@ -5,6 +5,7 @@ K:ETBReplacement:Other:DBNameCard SVar:DBNameCard:DB$ NameCard | Defined$ You | SpellDescription$ As CARDNAME enters the battlefield, name a card. S:Mode$ Continuous | Affected$ You | AddKeyword$ Protection:ChosenName | Description$ You have protection from the chosen name. (You can't be targeted, dealt damage, or enchanted by anything with that name.) SVar:RemAIDeck:True +SVar:RemRandomDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/runed_halo.jpg Oracle:As Runed Halo enters the battlefield, name a card.\nYou have protection from the chosen name. (You can't be targeted, dealt damage, or enchanted by anything with that name.) SetInfo:SHM Rare \ No newline at end of file diff --git a/res/cardsfolder/s/starke_of_rath.txt b/res/cardsfolder/s/starke_of_rath.txt index 4a14492b82e..fff730cf691 100644 --- a/res/cardsfolder/s/starke_of_rath.txt +++ b/res/cardsfolder/s/starke_of_rath.txt @@ -3,7 +3,7 @@ ManaCost:1 R R Types:Legendary Creature Human Rogue PT:2/2 A:AB$ Destroy | Cost$ T | ValidTgts$ Artifact,Creature | TgtPrompt$ Select target artifact or creature | SubAbility$ TrigControl | SpellDescription$ Destroy target artifact or creature. -SVar:TrigControl:DB$GainControl | Cost$ 0 | Defined$ Self | NewController$ TargetedController | SubAbility$ RemCombat | SpellDescription$ That permanent's controller gains control of CARDNAME. (This effect lasts indefinitely.) +SVar:TrigControl:DB$ GainControl | Cost$ 0 | Defined$ Self | NewController$ TargetedController | SubAbility$ RemCombat | SpellDescription$ That permanent's controller gains control of CARDNAME. (This effect lasts indefinitely.) SVar:RemCombat:DB$ RemoveFromCombat | Defined$ Targeted SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/starke_of_rath.jpg From 9679890ede4ec5b32cd3a3fa722ee23706a613ce Mon Sep 17 00:00:00 2001 From: swordshine Date: Wed, 3 Apr 2013 00:19:14 +0000 Subject: [PATCH 073/123] - Fixed Jihad (IsPresent is not used in static abilities) --- res/cardsfolder/c/crag_puca.txt | 1 + res/cardsfolder/e/ensouled_scimitar.txt | 2 +- res/cardsfolder/j/jihad.txt | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/res/cardsfolder/c/crag_puca.txt b/res/cardsfolder/c/crag_puca.txt index 1558912759f..182506d73ab 100644 --- a/res/cardsfolder/c/crag_puca.txt +++ b/res/cardsfolder/c/crag_puca.txt @@ -3,6 +3,7 @@ ManaCost:UR UR UR Types:Creature Shapeshifter PT:2/4 A:AB$ Pump | Cost$ UR | KW$ HIDDEN CARDNAME's power and toughness are switched | SpellDescription$ Switch CARDNAME's power and toughness until end of turn. +SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/crag_puca.jpg Oracle:{U/R}: Switch Crag Puca's power and toughness until end of turn. SetInfo:EVE Uncommon \ No newline at end of file diff --git a/res/cardsfolder/e/ensouled_scimitar.txt b/res/cardsfolder/e/ensouled_scimitar.txt index 772d7f39083..d46ef558847 100644 --- a/res/cardsfolder/e/ensouled_scimitar.txt +++ b/res/cardsfolder/e/ensouled_scimitar.txt @@ -3,7 +3,7 @@ ManaCost:3 Types:Artifact Equipment K:Equip 2 A:AB$ Animate | Cost$ 3 | Defined$ Self | Power$ 1 | Toughness$ 5 | Types$ Creature,Artifact,Spirit | Keywords$ Flying | OverwriteTypes$ True | SpellDescription$ CARDNAME becomes a 1/5 Spirit artifact creature with flying until end of turn. (Equipment that's a creature can't equip a creature.) -S:Mode$ Continuous | Affected$ Card.EquippedBy | IsPresent$ Card.Self+nonCreature | AddPower$ 1 | AddToughness$ 5 | Description$ Equipped creature gets +1/+5. +S:Mode$ Continuous | Affected$ Card.EquippedBy | AddPower$ 1 | AddToughness$ 5 | Description$ Equipped creature gets +1/+5. SVar:Picture:http://www.wizards.com/global/images/magic/general/ensouled_scimitar.jpg Oracle:{3}: Ensouled Scimitar becomes a 1/5 Spirit artifact creature with flying until end of turn. (Equipment that's a creature can't equip a creature.)\nEquipped creature gets +1/+5.\nEquip {2} ({2}: Attach to target creature you control. Equip only as a sorcery.) SetInfo:5DN Uncommon \ No newline at end of file diff --git a/res/cardsfolder/j/jihad.txt b/res/cardsfolder/j/jihad.txt index f5e7aef6286..c0a22479a43 100644 --- a/res/cardsfolder/j/jihad.txt +++ b/res/cardsfolder/j/jihad.txt @@ -4,9 +4,10 @@ Types:Enchantment K:ETBReplacement:Other:ChooseColor SVar:ChooseColor:DB$ ChooseColor | Defined$ You | SubAbility$ ChooseP | SpellDescription$ As CARDNAME enters the battlefield, choose a color and an opponent. SVar:ChooseP:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent -S:Mode$ Continuous | Affected$ Creature.White | AddPower$ 2 | AddToughness$ 1 | IsPresent$ Permanent.nontoken+ChosenColor+YouDontCtrl | Description$ White creatures get +2/+1 as long as the chosen player controls a nontoken permanent of the chosen color. -T:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Permanent.nontoken+ChosenColor+YouDontCtrl | PresentCompare$ EQ0 | Execute$ TrigSac | TriggerDescription$ When the chosen player controls no nontoken permanents of the chosen color, sacrifice CARDNAME. +S:Mode$ Continuous | Affected$ Creature.White | AddPower$ 2 | AddToughness$ 1 | CheckSVar$ X | Description$ White creatures get +2/+1 as long as the chosen player controls a nontoken permanent of the chosen color. +T:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Permanent.nontoken+ChosenColor+ChosenCtrl | PresentCompare$ EQ0 | Execute$ TrigSac | TriggerDescription$ When the chosen player controls no nontoken permanents of the chosen color, sacrifice CARDNAME. SVar:TrigSac:AB$ Sacrifice | Cost$ 0 | Defined$ Self +SVar:X:Count$Valid Permanent.nontoken+ChosenColor+ChosenCtrl SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/jihad.jpg Oracle:As Jihad enters the battlefield, choose a color and an opponent.\nWhite creatures get +2/+1 as long as the chosen player controls a nontoken permanent of the chosen color.\nWhen the chosen player controls no nontoken permanents of the chosen color, sacrifice Jihad. From 24145e7da80019eb9c8bfb6cc63d0a0e41ba5710 Mon Sep 17 00:00:00 2001 From: swordshine Date: Wed, 3 Apr 2013 00:20:08 +0000 Subject: [PATCH 074/123] - Added Heroism --- .gitattributes | 1 + res/cardsfolder/h/heroism.txt | 10 ++++++++++ src/main/java/forge/card/ability/AbilityUtils.java | 2 +- src/main/java/forge/game/GameActionUtil.java | 4 ++-- 4 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 res/cardsfolder/h/heroism.txt diff --git a/.gitattributes b/.gitattributes index 0e721e172b4..7133280fb28 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4867,6 +4867,7 @@ res/cardsfolder/h/hero_of_oxid_ridge.txt svneol=native#text/plain res/cardsfolder/h/heroes_remembered.txt svneol=native#text/plain res/cardsfolder/h/heroes_reunion.txt svneol=native#text/plain res/cardsfolder/h/heroic_defiance.txt -text +res/cardsfolder/h/heroism.txt -text res/cardsfolder/h/heros_demise.txt svneol=native#text/plain res/cardsfolder/h/heros_resolve.txt svneol=native#text/plain res/cardsfolder/h/hesitation.txt -text svneol=unset#text/plain diff --git a/res/cardsfolder/h/heroism.txt b/res/cardsfolder/h/heroism.txt new file mode 100644 index 00000000000..6c626fbaa5c --- /dev/null +++ b/res/cardsfolder/h/heroism.txt @@ -0,0 +1,10 @@ +Name:Heroism +ManaCost:2 W +Types:Enchantment +A:AB$ RepeatEach | Cost$ Sac<1/Creature.White/White Creature> | RepeatCards$ Creature.attacking+Red | RepeatSubAbility$ DBPump | SpellDescription$ For each attacking red creature, prevent all combat damage that would be dealt by that creature this turn unless its controller pays 2 R. +SVar:DBPump:DB$ Pump | Defined$ Remembered | KW$ HIDDEN Prevent all combat damage that would be dealt by CARDNAME. | UnlessCost$ 2 R | UnlessPayer$ RememberedController +SVar:RemRandomDeck:True +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/heroism.jpg +Oracle:Sacrifice a white creature: For each attacking red creature, prevent all combat damage that would be dealt by that creature this turn unless its controller pays {2}{R}. +SetInfo:FEM Uncommon \ No newline at end of file diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index c6c8c5cea1d..eecefd1e829 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -1181,7 +1181,7 @@ public class AbilityUtils { private static boolean willAIPayForAbility(SpellAbility sa, Player payer, SpellAbility ability, boolean paid, List payers) { Card source = sa.getSourceCard(); boolean payForOwnOnly = "OnlyOwn".equals(sa.getParam("UnlessAI")); - boolean payOwner = sa.getParam("UnlessAI").startsWith("Defined"); + boolean payOwner = sa.hasParam("UnlessAI") ? sa.getParam("UnlessAI").startsWith("Defined") : false; boolean payNever = "Never".equals(sa.getParam("UnlessAI")); boolean isMine = sa.getActivatingPlayer().equals(payer); diff --git a/src/main/java/forge/game/GameActionUtil.java b/src/main/java/forge/game/GameActionUtil.java index 6e01632b5f3..d117414a1b5 100644 --- a/src/main/java/forge/game/GameActionUtil.java +++ b/src/main/java/forge/game/GameActionUtil.java @@ -406,12 +406,12 @@ public final class GameActionUtil { final Player p = ability.getActivatingPlayer(); final Card source = ability.getSourceCard(); Card current = null; // Used in spells with RepeatEach effect to distinguish cards, Cut the Tethers - if (!source.getRemembered().isEmpty() && source.isSpell()) { + if (!source.getRemembered().isEmpty()) { if (source.getRemembered().get(0) instanceof Card) { current = (Card) source.getRemembered().get(0); } } - if (!source.getImprinted().isEmpty() && source.isSpell()) { + if (!source.getImprinted().isEmpty()) { current = source.getImprinted().get(0); } From 4481fb72bd5a9cc573ba05a1e3057b250b69c5d2 Mon Sep 17 00:00:00 2001 From: swordshine Date: Wed, 3 Apr 2013 00:22:36 +0000 Subject: [PATCH 075/123] - Vanguard: Added Akroma, Angel of Wrath Avatar and Eight-and-a-Half-Tails Avatar --- .gitattributes | 2 ++ .../a/akroma_angel_of_wrath_avatar.txt | 9 +++++++ .../e/eight_and_a_half_tails_avatar.txt | 8 ++++++ .../card/ability/effects/PumpEffect.java | 27 +++++++++++++++++-- 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 res/cardsfolder/a/akroma_angel_of_wrath_avatar.txt create mode 100644 res/cardsfolder/e/eight_and_a_half_tails_avatar.txt diff --git a/.gitattributes b/.gitattributes index 7133280fb28..a5a98ce2322 100644 --- a/.gitattributes +++ b/.gitattributes @@ -176,6 +176,7 @@ res/cardsfolder/a/akoum_refuge.txt svneol=native#text/plain res/cardsfolder/a/akrasan_squire.txt svneol=native#text/plain res/cardsfolder/a/akroma_angel_of_fury.txt svneol=native#text/plain res/cardsfolder/a/akroma_angel_of_wrath.txt svneol=native#text/plain +res/cardsfolder/a/akroma_angel_of_wrath_avatar.txt -text res/cardsfolder/a/akromas_blessing.txt -text res/cardsfolder/a/akromas_devoted.txt svneol=native#text/plain res/cardsfolder/a/akromas_memorial.txt svneol=native#text/plain @@ -3040,6 +3041,7 @@ res/cardsfolder/e/ego_erasure.txt -text res/cardsfolder/e/eiganjo_castle.txt svneol=native#text/plain res/cardsfolder/e/eiganjo_free_riders.txt svneol=native#text/plain res/cardsfolder/e/eight_and_a_half_tails.txt -text +res/cardsfolder/e/eight_and_a_half_tails_avatar.txt -text res/cardsfolder/e/eightfold_maze.txt svneol=native#text/plain res/cardsfolder/e/ekundu_griffin.txt svneol=native#text/plain res/cardsfolder/e/el_hajjaj.txt svneol=native#text/plain diff --git a/res/cardsfolder/a/akroma_angel_of_wrath_avatar.txt b/res/cardsfolder/a/akroma_angel_of_wrath_avatar.txt new file mode 100644 index 00000000000..df1c3c39866 --- /dev/null +++ b/res/cardsfolder/a/akroma_angel_of_wrath_avatar.txt @@ -0,0 +1,9 @@ +Name:Akroma, Angel of Wrath Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+1/+7 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | TriggerZones$ Command | ValidCard$ Creature.YouCtrl | Execute$ PumpRandom | TriggerDescription$ Whenever a creature enters the battlefield under your control, it gains two abilities chosen at random from flying, first strike, trample, haste, protection from black, protection from red, and vigilance. +SVar:PumpRandom:AB$ Pump | Cost$ 0 | Defined$ TriggeredCard | Permanent$ True | KW$ Flying & First Strike & Trample & Haste & Protection from black & Protection from red & Vigilance | RandomKeyword$ True | RandomKWNum$ 2 +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/akroma_angel_of_wrath_avatar.jpg +Oracle:Hand +1, life +7\nWhenever a creature enters the battlefield under your control, it gains two abilities chosen at random from flying, first strike, trample, haste, protection from black, protection from red, and vigilance. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/e/eight_and_a_half_tails_avatar.txt b/res/cardsfolder/e/eight_and_a_half_tails_avatar.txt new file mode 100644 index 00000000000..5ad0a177728 --- /dev/null +++ b/res/cardsfolder/e/eight_and_a_half_tails_avatar.txt @@ -0,0 +1,8 @@ +Name:Eight-and-a-Half-Tails Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+2/-3 +A:AB$ Pump | Cost$ 1 | ActivationZone$ Command | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | KW$ Protection from red & Protection from blue & Protection from black & Protection from white & Protection from green | RandomKeyword$ True | NoRepetition$ True | SpellDescription$ Until end of turn, target permanent you control gains protection from a color chosen at random from colors it doesn't have protection from. +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/eight_and_a_half_tails_avatar.jpg +Oracle:Hand +2, life -3\n{1}: Until end of turn, target permanent you control gains protection from a color chosen at random from colors it doesn't have protection from. +SetInfo:VAN Special \ No newline at end of file diff --git a/src/main/java/forge/card/ability/effects/PumpEffect.java b/src/main/java/forge/card/ability/effects/PumpEffect.java index 7691505f010..ee4ec1ec289 100644 --- a/src/main/java/forge/card/ability/effects/PumpEffect.java +++ b/src/main/java/forge/card/ability/effects/PumpEffect.java @@ -16,6 +16,7 @@ import forge.card.spellability.Target; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiDialog; +import forge.util.Aggregates; public class PumpEffect extends SpellAbilityEffect { @@ -179,7 +180,7 @@ public class PumpEffect extends SpellAbilityEffect { String pumpForget = null; String pumpImprint = null; - final List keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList(); + List keywords = sa.hasParam("KW") ? Arrays.asList(sa.getParam("KW").split(" & ")) : new ArrayList(); final int a = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumAtt"), sa); final int d = AbilityUtils.calculateAmount(sa.getSourceCard(), sa.getParam("NumDef"), sa); @@ -194,7 +195,29 @@ public class PumpEffect extends SpellAbilityEffect { tgtCards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa); } } - + + if (sa.hasParam("RandomKeyword")) { + final String num = sa.hasParam("RandomKWNum") ? sa.getParam("RandomKWNum") : "1"; + final int numkw = AbilityUtils.calculateAmount(sa.getSourceCard(), num, sa); + List choice = new ArrayList(); + List total = new ArrayList(keywords); + if (sa.hasParam("NoRepetition")) { + final List tgtCardskws = tgtCards.get(0).getKeyword(); + for (int i = 0; i < tgtCardskws.size(); i++) { + if (total.contains(tgtCardskws.get(i))) { + total.remove(tgtCardskws.get(i)); + } + } + } + final int min = Math.min(total.size(), numkw); + for (int i = 0; i < min; i++) { + final String random = Aggregates.random(total); + choice.add(random); + total.remove(random); + } + keywords = choice; + } + if (sa.hasParam("Optional")) { if (sa.getActivatingPlayer().isHuman()) { final StringBuilder targets = new StringBuilder(); From 9f83c38d75d931ce303d588bf41051a516b9f2e1 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 05:14:35 +0000 Subject: [PATCH 076/123] copy-pasters must die! (moved after mulligan actions to MatchController (temporary)) --- .../forge/control/input/InputMulligan.java | 68 +------------------ .../input/InputPartialParisMulligan.java | 68 +------------------ src/main/java/forge/game/MatchController.java | 67 ++++++++++++++++++ src/main/java/forge/gui/GuiChoose.java | 2 - 4 files changed, 72 insertions(+), 133 deletions(-) diff --git a/src/main/java/forge/control/input/InputMulligan.java b/src/main/java/forge/control/input/InputMulligan.java index 5a740107af0..81eaab21545 100644 --- a/src/main/java/forge/control/input/InputMulligan.java +++ b/src/main/java/forge/control/input/InputMulligan.java @@ -20,20 +20,13 @@ package forge.control.input; import java.util.ArrayList; import java.util.List; -import com.google.common.collect.Iterables; - import forge.Card; -import forge.CardPredicates; import forge.FThreads; -import forge.card.ability.AbilityFactory; -import forge.card.spellability.SpellAbility; -import forge.game.GameAction; import forge.game.GameState; import forge.game.GameType; import forge.game.MatchController; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; -import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; @@ -114,76 +107,21 @@ public class InputMulligan extends InputBase { } } - // Human Leylines & Chancellors + ButtonUtil.reset(); - - final GameAction ga = game.getAction(); - for (Player p : game.getPlayers()) { - final List openingHand = new ArrayList(p.getCardsIn(ZoneType.Hand)); - - for (final Card c : openingHand) { - if (p.isHuman()) { - for (String kw : c.getKeyword()) { - if (kw.startsWith("MayEffectFromOpeningHand")) { - final String effName = kw.split(":")[1]; - - final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); - if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { - // If we ever let the AI memorize cards in the players - // hand, this would be a place to do so. - game.getActionPlay().playSpellAbilityNoStack((HumanPlayer)p, effect); - } - } - } - if (c.getName().startsWith("Leyline of")) { - if (GuiDialog.confirm(c, "Use " + c + "'s ability?")) { - ga.moveToPlay(c); - } - } - } else { // Computer Leylines & Chancellors - if (!c.getName().startsWith("Leyline of")) { - for (String kw : c.getKeyword()) { - if (kw.startsWith("MayEffectFromOpeningHand")) { - final String effName = kw.split(":")[1]; - - final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); - - // Is there a better way for the AI to decide this? - if (effect.doTrigger(false, (AIPlayer)p)) { - GuiDialog.message("Computer reveals " + c.getName() + "(" + c.getUniqueNumber() + ")."); - ComputerUtil.playNoStack((AIPlayer)p, effect, game); - } - } - } - } - if (c.getName().startsWith("Leyline of") - && !(c.getName().startsWith("Leyline of Singularity") - && (Iterables.any(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Leyline of Singularity"))))) { - ga.moveToPlay(c); - //ga.checkStateEffects(); - } - } - } - } - - ga.checkStateEffects(); - Player next = game.getPhaseHandler().getPlayerTurn(); - if(game.getType() == GameType.Planechase) { next.initPlane(); } - //Set Field shown to current player. VField nextField = CMatchUI.SINGLETON_INSTANCE.getFieldViewFor(next); SDisplayUtil.showTab(nextField); - - game.setMulliganned(true); + FThreads.invokeInNewThread( new Runnable() { @Override public void run() { - match.getInput().clearInput(); + match.afterMulligans(); } }); } diff --git a/src/main/java/forge/control/input/InputPartialParisMulligan.java b/src/main/java/forge/control/input/InputPartialParisMulligan.java index 4fa8d68e9eb..416086d358d 100644 --- a/src/main/java/forge/control/input/InputPartialParisMulligan.java +++ b/src/main/java/forge/control/input/InputPartialParisMulligan.java @@ -20,19 +20,11 @@ package forge.control.input; import java.util.ArrayList; import java.util.List; -import com.google.common.collect.Iterables; - import forge.Card; -import forge.CardPredicates; import forge.FThreads; -import forge.card.ability.AbilityFactory; -import forge.card.spellability.SpellAbility; -import forge.game.GameAction; import forge.game.GameState; import forge.game.GameType; import forge.game.MatchController; -import forge.game.ai.ComputerUtil; -import forge.game.player.AIPlayer; import forge.game.player.Player; import forge.game.zone.Zone; import forge.game.zone.ZoneType; @@ -40,7 +32,6 @@ import forge.gui.GuiDialog; import forge.gui.framework.SDisplayUtil; import forge.gui.match.CMatchUI; import forge.gui.match.nonsingleton.VField; -import forge.gui.match.views.VMessage; import forge.view.ButtonUtil; /** *

@@ -134,74 +125,19 @@ public class InputPartialParisMulligan extends InputBase { // Human Leylines & Chancellors ButtonUtil.reset(); - - final GameAction ga = game.getAction(); - for (Player p : game.getPlayers()) { - final List openingHand = new ArrayList(p.getCardsIn(ZoneType.Hand)); - - for (final Card c : openingHand) { - if (p.isHuman()) { - for (String kw : c.getKeyword()) { - if (kw.startsWith("MayEffectFromOpeningHand")) { - final String effName = kw.split(":")[1]; - - final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); - if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { - // If we ever let the AI memorize cards in the players - // hand, this would be a place to do so. - game.getActionPlay().playSpellAbilityNoStack(p, effect); - } - } - } - if (c.getName().startsWith("Leyline of")) { - if (GuiDialog.confirm(c, "Use " + c + "'s ability?")) { - ga.moveToPlay(c); - } - } - } else { // Computer Leylines & Chancellors - if (!c.getName().startsWith("Leyline of")) { - for (String kw : c.getKeyword()) { - if (kw.startsWith("MayEffectFromOpeningHand")) { - final String effName = kw.split(":")[1]; - - final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); - - // Is there a better way for the AI to decide this? - if (effect.doTrigger(false, (AIPlayer)p)) { - GuiDialog.message("Computer reveals " + c.getName() + "(" + c.getUniqueNumber() + ")."); - ComputerUtil.playNoStack((AIPlayer)p, effect, game); - } - } - } - } - if (c.getName().startsWith("Leyline of") - && !(c.getName().startsWith("Leyline of Singularity") - && (Iterables.any(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Leyline of Singularity"))))) { - ga.moveToPlay(c); - //ga.checkStateEffects(); - } - } - } - } - - ga.checkStateEffects(); - Player next = game.getPhaseHandler().getPlayerTurn(); - if(game.getType() == GameType.Planechase) { next.initPlane(); } - //Set Field shown to current player. VField nextField = CMatchUI.SINGLETON_INSTANCE.getFieldViewFor(next); SDisplayUtil.showTab(nextField); - - game.setMulliganned(true); + FThreads.invokeInNewThread( new Runnable() { @Override public void run() { - match.getInput().clearInput(); + match.afterMulligans(); } }); } diff --git a/src/main/java/forge/game/MatchController.java b/src/main/java/forge/game/MatchController.java index 0688f3345aa..5884945e7ff 100644 --- a/src/main/java/forge/game/MatchController.java +++ b/src/main/java/forge/game/MatchController.java @@ -7,13 +7,20 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import com.google.common.collect.Iterables; + import forge.Constant.Preferences; +import forge.Card; +import forge.CardPredicates; import forge.Singletons; +import forge.card.ability.AbilityFactory; +import forge.card.spellability.SpellAbility; import forge.control.FControl; import forge.control.input.InputControl; import forge.deck.Deck; import forge.error.BugReporter; import forge.game.ai.AiProfileUtil; +import forge.game.ai.ComputerUtil; import forge.game.event.DuelOutcomeEvent; import forge.game.player.AIPlayer; import forge.game.player.HumanPlayer; @@ -22,6 +29,7 @@ import forge.game.player.Player; import forge.game.player.PlayerStatistics; import forge.game.player.PlayerType; import forge.game.zone.ZoneType; +import forge.gui.GuiDialog; import forge.gui.InputProxy; import forge.gui.framework.EDocID; import forge.gui.framework.SDisplayUtil; @@ -339,4 +347,63 @@ public class MatchController { public static int getPoisonCountersAmountToLose() { return 10; } + + private void handleLeylinesAndChancellors() { + for (Player p : currentGame.getPlayers()) { + final List openingHand = new ArrayList(p.getCardsIn(ZoneType.Hand)); + + for (final Card c : openingHand) { + if (p.isHuman()) { + for (String kw : c.getKeyword()) { + if (kw.startsWith("MayEffectFromOpeningHand")) { + final String effName = kw.split(":")[1]; + + final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); + if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { + // If we ever let the AI memorize cards in the players + // hand, this would be a place to do so. + currentGame.getActionPlay().playSpellAbilityNoStack((HumanPlayer)p, effect); + } + } + } + if (c.getName().startsWith("Leyline of")) { + if (GuiDialog.confirm(c, "Use " + c + "'s ability?")) { + currentGame.getAction().moveToPlay(c); + } + } + } else { // Computer Leylines & Chancellors + if (!c.getName().startsWith("Leyline of")) { + for (String kw : c.getKeyword()) { + if (kw.startsWith("MayEffectFromOpeningHand")) { + final String effName = kw.split(":")[1]; + + final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); + + // Is there a better way for the AI to decide this? + if (effect.doTrigger(false, (AIPlayer)p)) { + GuiDialog.message("Computer reveals " + c.getName() + "(" + c.getUniqueNumber() + ")."); + ComputerUtil.playNoStack((AIPlayer)p, effect, currentGame); + } + } + } + } + if (c.getName().startsWith("Leyline of") + && !(c.getName().startsWith("Leyline of Singularity") + && (Iterables.any(currentGame.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Leyline of Singularity"))))) { + currentGame.getAction().moveToPlay(c); + //ga.checkStateEffects(); + } + } + } + } + + currentGame.getAction().checkStateEffects(); + } + + public void afterMulligans() + { + handleLeylinesAndChancellors(); + currentGame.setMulliganned(true); + getInput().clearInput(); + } } diff --git a/src/main/java/forge/gui/GuiChoose.java b/src/main/java/forge/gui/GuiChoose.java index da5b50cac0f..607c810f090 100644 --- a/src/main/java/forge/gui/GuiChoose.java +++ b/src/main/java/forge/gui/GuiChoose.java @@ -8,8 +8,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import javax.swing.JDialog; From fc15876846d1fada2bf4285b3c2c8d20990dc3b8 Mon Sep 17 00:00:00 2001 From: swordshine Date: Wed, 3 Apr 2013 05:20:27 +0000 Subject: [PATCH 077/123] - Fixed a typo --- res/cardsfolder/f/fold_into_aether.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/cardsfolder/f/fold_into_aether.txt b/res/cardsfolder/f/fold_into_aether.txt index 7f337309a76..ba33f8e2617 100644 --- a/res/cardsfolder/f/fold_into_aether.txt +++ b/res/cardsfolder/f/fold_into_aether.txt @@ -2,7 +2,7 @@ Name:Fold into AEther ManaCost:2 U U Types:Instant A:SP$ Counter | Cost$ 2 U U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | RememberCountered$ True | SubAbility$ DBChangeZone | SpellDescription$ Counter target spell. If that spell is countered this way, its controller may put a creature card from his or her hand onto the battlefield. -SVar:DBChangeZone:DB$ ChangeZone | ChangeType$ Creature | Origin$ Hand | Destination$ Battlefield | ChangeNum$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBleanup +SVar:DBChangeZone:DB$ ChangeZone | ChangeType$ Creature | Origin$ Hand | Destination$ Battlefield | ChangeNum$ 1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Picture:http://www.wizards.com/global/images/magic/general/fold_into_aether.jpg Oracle:Counter target spell. If that spell is countered this way, its controller may put a creature card from his or her hand onto the battlefield. From 8368b4c8c6ef6a3bd7cabc2048f3db7375d66d15 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 05:32:09 +0000 Subject: [PATCH 078/123] GameActionPlay.playXXX => HumanPlayer (the only one who used them) GameActionPlay.setSplitCardState => Card class (a logical place and will require less parameters) --- src/main/java/forge/Card.java | 22 ++- .../card/ability/effects/PlayEffect.java | 2 +- .../card/cardfactory/CardFactoryUtil.java | 2 +- .../card/replacement/ReplacementHandler.java | 2 +- .../forge/card/trigger/TriggerHandler.java | 2 +- .../forge/card/trigger/WrappedAbility.java | 2 +- .../forge/control/input/InputPayManaBase.java | 2 +- src/main/java/forge/game/GameActionPlay.java | 164 +----------------- src/main/java/forge/game/MatchController.java | 2 +- .../java/forge/game/player/HumanPlayer.java | 150 +++++++++++++++- .../game/player/PlayerControllerHuman.java | 4 +- src/main/java/forge/game/zone/MagicStack.java | 2 +- 12 files changed, 181 insertions(+), 175 deletions(-) diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index e3b599575be..351303af20a 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -9152,5 +9152,25 @@ public class Card extends GameEntity implements Comparable { public void setCommander(boolean b) { this.isCommander = b; } - + + public void setSplitStateToPlayAbility(SpellAbility sa) { + if( !isSplitCard() ) return; // just in case + // Split card support + List leftSplitAbilities = getState(CardCharacteristicName.LeftSplit).getSpellAbility(); + List rightSplitAbilities = getState(CardCharacteristicName.RightSplit).getSpellAbility(); + for (SpellAbility a : leftSplitAbilities) { + if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { + setState(CardCharacteristicName.LeftSplit); + return; + } + } + for (SpellAbility a : rightSplitAbilities) { + if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { + setState(CardCharacteristicName.RightSplit); + return; + } + } + throw new RuntimeException("Not found which part to choose for ability " + sa + " from card " + this); + } + } // end Card class diff --git a/src/main/java/forge/card/ability/effects/PlayEffect.java b/src/main/java/forge/card/ability/effects/PlayEffect.java index e7fa53acdf4..462dc538810 100644 --- a/src/main/java/forge/card/ability/effects/PlayEffect.java +++ b/src/main/java/forge/card/ability/effects/PlayEffect.java @@ -203,7 +203,7 @@ public class PlayEffect extends SpellAbilityEffect { boolean noManaCost = sa.hasParam("WithoutManaCost"); if (controller.isHuman()) { SpellAbility newSA = noManaCost ? tgtSA.copyWithNoManaCost() : tgtSA; - game.getActionPlay().playSpellAbility(newSA, (HumanPlayer)activator); + ((HumanPlayer)activator).playSpellAbility(newSA); } else { if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell? Spell spell = (Spell) tgtSA; diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index b73396e34a9..4fd6836501e 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -2914,7 +2914,7 @@ public class CardFactoryUtil { } if (card.getController().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack((HumanPlayer)card.getController(), origSA); + ((HumanPlayer)card.getController()).playSpellAbilityNoStack(origSA); } else { ComputerUtil.playNoStack((AIPlayer) card.getController(), origSA, game); } diff --git a/src/main/java/forge/card/replacement/ReplacementHandler.java b/src/main/java/forge/card/replacement/ReplacementHandler.java index 2de3a967b87..1bad49e95c3 100644 --- a/src/main/java/forge/card/replacement/ReplacementHandler.java +++ b/src/main/java/forge/card/replacement/ReplacementHandler.java @@ -240,7 +240,7 @@ public class ReplacementHandler { Player player = replacementEffect.getHostCard().getController(); //player.getController().playNoStack() if (player.isHuman()) { - game.getActionPlay().playSpellAbilityNoStack((HumanPlayer)player, effectSA); + ((HumanPlayer)player).playSpellAbilityNoStack(effectSA); } else { ComputerUtil.playNoStack((AIPlayer) player, effectSA, game); } diff --git a/src/main/java/forge/card/trigger/TriggerHandler.java b/src/main/java/forge/card/trigger/TriggerHandler.java index 6905be17009..6a0c5c18db1 100644 --- a/src/main/java/forge/card/trigger/TriggerHandler.java +++ b/src/main/java/forge/card/trigger/TriggerHandler.java @@ -420,7 +420,7 @@ public class TriggerHandler { if (regtrig.isStatic()) { if (wrapperAbility.getActivatingPlayer().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack((HumanPlayer)wrapperAbility.getActivatingPlayer(), wrapperAbility); + ((HumanPlayer)wrapperAbility.getActivatingPlayer()).playSpellAbilityNoStack(wrapperAbility); } else { wrapperAbility.doTrigger(isMandatory, (AIPlayer)wrapperAbility.getActivatingPlayer()); ComputerUtil.playNoStack((AIPlayer)wrapperAbility.getActivatingPlayer(), wrapperAbility, game); diff --git a/src/main/java/forge/card/trigger/WrappedAbility.java b/src/main/java/forge/card/trigger/WrappedAbility.java index 9bd4e7dcc77..30770fc53ef 100644 --- a/src/main/java/forge/card/trigger/WrappedAbility.java +++ b/src/main/java/forge/card/trigger/WrappedAbility.java @@ -415,7 +415,7 @@ public class WrappedAbility extends Ability implements ISpellAbility { } if (getActivatingPlayer().isHuman()) { - game.getActionPlay().playSpellAbilityNoStack((HumanPlayer)getActivatingPlayer(), sa, true); + ((HumanPlayer)getActivatingPlayer()).playSpellAbilityNoStack(sa, true); } else { // commented out because i don't think this should be called // again here diff --git a/src/main/java/forge/control/input/InputPayManaBase.java b/src/main/java/forge/control/input/InputPayManaBase.java index 02ed79ecf6e..1e633147e52 100644 --- a/src/main/java/forge/control/input/InputPayManaBase.java +++ b/src/main/java/forge/control/input/InputPayManaBase.java @@ -291,7 +291,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I Runnable proc = new Runnable() { @Override public void run() { - player.getGame().getActionPlay().playSpellAbility(chosen, (HumanPlayer)chosen.getActivatingPlayer()); + ((HumanPlayer)chosen.getActivatingPlayer()).playSpellAbility(chosen); onManaAbilityPlayed(chosen); } }; diff --git a/src/main/java/forge/game/GameActionPlay.java b/src/main/java/forge/game/GameActionPlay.java index cacc8b2ffe7..fff072e4b3e 100644 --- a/src/main/java/forge/game/GameActionPlay.java +++ b/src/main/java/forge/game/GameActionPlay.java @@ -5,26 +5,20 @@ import java.util.List; import com.google.common.collect.Lists; import forge.Card; -import forge.CardCharacteristicName; import forge.CardColor; import forge.CardLists; import forge.CardPredicates; import forge.FThreads; import forge.card.MagicColor; -import forge.card.ability.AbilityUtils; import forge.card.ability.ApiType; import forge.card.ability.effects.CharmEffect; -import forge.card.cost.Cost; import forge.card.cost.CostPayment; import forge.card.mana.ManaCostBeingPaid; import forge.card.mana.ManaCostShard; import forge.card.spellability.SpellAbility; import forge.card.spellability.HumanPlaySpellAbility; -import forge.card.spellability.Target; import forge.card.staticability.StaticAbility; -import forge.control.input.InputPayManaSimple; import forge.game.ai.ComputerUtilCard; -import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -68,8 +62,7 @@ public class GameActionPlay { FThreads.checkEDT("GameActionPlay.playSpellAbilityWithoutPayingManaCost", false); final Card source = sa.getSourceCard(); - if( source.isSplitCard()) - setSplitCardState(source, sa); + source.setSplitStateToPlayAbility(sa); if (sa.getPayCosts() != null) { if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { @@ -325,142 +318,6 @@ public class GameActionPlay { return manaCost; } - /** - * choose optional additional costs. For HUMAN only - * @param activator - * - * @param original - * the original sa - * @return an ArrayList. - */ - public static SpellAbility chooseOptionalAdditionalCosts(Player activator, final SpellAbility original) { - //final HashMap map = new HashMap(); - final ArrayList abilities = GameActionUtil.getOptionalAdditionalCosts(original); - - if (!original.isSpell()) { - return original; - } - - return activator.getController().getAbilityToPlay(abilities); - } - - /** - *

- * playSpellAbility. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - */ - public final void playSpellAbility(SpellAbility sa, HumanPlayer activator) { - FThreads.checkEDT("Player.playSpellAbility", false); - sa.setActivatingPlayer(activator); - - final Card source = sa.getSourceCard(); - - if(source.isSplitCard()) - setSplitCardState(source, sa); - - if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - CharmEffect.makeChoices(sa); - } - - sa = chooseOptionalAdditionalCosts(activator, sa); - - if (sa == null) { - return; - } - - // Need to check PayCosts, and Ability + All SubAbilities for Target - boolean newAbility = sa.getPayCosts() != null; - SpellAbility ability = sa; - while ((ability != null) && !newAbility) { - final Target tgt = ability.getTarget(); - - newAbility |= tgt != null; - ability = ability.getSubAbility(); - } - - // System.out.println("Playing:" + sa.getDescription() + " of " + sa.getSourceCard() + " new = " + newAbility); - if (newAbility) { - CostPayment payment = null; - if (sa.getPayCosts() == null) { - payment = new CostPayment(new Cost(sa.getSourceCard(), "0", sa.isAbility()), sa); - } else { - payment = new CostPayment(sa.getPayCosts(), sa); - } - - final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); - req.fillRequirements(false, false, false); - } else { - ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); - if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { - manaCost = new ManaCostBeingPaid("0"); - } else { - manaCost = this.getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())); - } - - if (!manaCost.isPaid()) { - FThreads.setInputAndWait(new InputPayManaSimple(game, sa, manaCost)); - } - - - if (manaCost.isPaid()) { - if (sa.isSpell() && !source.isCopiedSpell()) { - sa.setSourceCard(game.getAction().moveToStack(source)); - } - - game.getStack().add(sa); - } - } - } - - /** - *

- * playSpellAbility_NoStack. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - * @param skipTargeting - * a boolean. - */ - public final void playSpellAbilityNoStack(final HumanPlayer human, final SpellAbility sa) { - playSpellAbilityNoStack(human, sa, false); - } - public final void playSpellAbilityNoStack(final HumanPlayer human, final SpellAbility sa, boolean useOldTargets) { - sa.setActivatingPlayer(human); - - if (sa.getPayCosts() != null) { - final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); - - if (!sa.isTrigger()) { - payment.changeCost(); - } - - final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); - - req.fillRequirements(useOldTargets, false, true); - } else { - ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); - if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { - manaCost = new ManaCostBeingPaid("0"); - } else { - manaCost = this.getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())); - } - - if( !manaCost.isPaid() ) { - FThreads.setInputAndWait(new InputPayManaSimple(game, sa, getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())))); - } - - if (manaCost.isPaid()) { - AbilityUtils.resolve(sa, false); - } - - } - } - - /** * Gets the convokable colors. * @@ -489,23 +346,4 @@ public class GameActionPlay { return usableColors; } - - private void setSplitCardState(final Card source, SpellAbility sa) { - // Split card support - List leftSplitAbilities = source.getState(CardCharacteristicName.LeftSplit).getSpellAbility(); - List rightSplitAbilities = source.getState(CardCharacteristicName.RightSplit).getSpellAbility(); - for (SpellAbility a : leftSplitAbilities) { - if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { - source.setState(CardCharacteristicName.LeftSplit); - return; - } - } - for (SpellAbility a : rightSplitAbilities) { - if (sa == a || sa.getDescription().equals(String.format("%s (without paying its mana cost)", a.getDescription()))) { - source.setState(CardCharacteristicName.RightSplit); - return; - } - } - throw new RuntimeException("Not found which part to choose for ability " + sa + " from card " + source); - } } diff --git a/src/main/java/forge/game/MatchController.java b/src/main/java/forge/game/MatchController.java index 5884945e7ff..edf8d8c174c 100644 --- a/src/main/java/forge/game/MatchController.java +++ b/src/main/java/forge/game/MatchController.java @@ -362,7 +362,7 @@ public class MatchController { if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { // If we ever let the AI memorize cards in the players // hand, this would be a place to do so. - currentGame.getActionPlay().playSpellAbilityNoStack((HumanPlayer)p, effect); + ((HumanPlayer)p).playSpellAbilityNoStack(effect); } } } diff --git a/src/main/java/forge/game/player/HumanPlayer.java b/src/main/java/forge/game/player/HumanPlayer.java index a8c14d7ab0d..80fe463e5d3 100644 --- a/src/main/java/forge/game/player/HumanPlayer.java +++ b/src/main/java/forge/game/player/HumanPlayer.java @@ -17,14 +17,25 @@ */ package forge.game.player; +import java.util.ArrayList; import java.util.List; import forge.Card; import forge.FThreads; +import forge.card.ability.AbilityUtils; +import forge.card.ability.ApiType; +import forge.card.ability.effects.CharmEffect; +import forge.card.cost.Cost; +import forge.card.cost.CostPayment; +import forge.card.mana.ManaCostBeingPaid; import forge.card.spellability.Ability; +import forge.card.spellability.HumanPlaySpellAbility; import forge.card.spellability.SpellAbility; +import forge.card.spellability.Target; +import forge.control.input.InputPayManaSimple; import forge.control.input.InputSelectCards; import forge.control.input.InputSelectCardsFromList; +import forge.game.GameActionUtil; import forge.game.GameState; import forge.game.zone.ZoneType; @@ -67,7 +78,7 @@ public class HumanPlayer extends Player { if (ab == Ability.PLAY_LAND_SURROGATE) this.playLand(c); else { - game.getActionPlay().playSpellAbility(ab, this); + this.playSpellAbility(ab); } game.getPhaseHandler().setPriority(this); } @@ -79,5 +90,142 @@ public class HumanPlayer extends Player { public PlayerController getController() { return controller; } + + + /** + *

+ * playSpellAbility. + *

+ * + * @param sa + * a {@link forge.card.spellability.SpellAbility} object. + */ + public final void playSpellAbility(SpellAbility sa) { + FThreads.checkEDT("Player.playSpellAbility", false); + sa.setActivatingPlayer(this); + + final Card source = sa.getSourceCard(); + + source.setSplitStateToPlayAbility(sa); + + if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { + CharmEffect.makeChoices(sa); + } + + sa = chooseOptionalAdditionalCosts(sa); + + if (sa == null) { + return; + } + + // Need to check PayCosts, and Ability + All SubAbilities for Target + boolean newAbility = sa.getPayCosts() != null; + SpellAbility ability = sa; + while ((ability != null) && !newAbility) { + final Target tgt = ability.getTarget(); + + newAbility |= tgt != null; + ability = ability.getSubAbility(); + } + + // System.out.println("Playing:" + sa.getDescription() + " of " + sa.getSourceCard() + " new = " + newAbility); + if (newAbility) { + CostPayment payment = null; + if (sa.getPayCosts() == null) { + payment = new CostPayment(new Cost(sa.getSourceCard(), "0", sa.isAbility()), sa); + } else { + payment = new CostPayment(sa.getPayCosts(), sa); + } + + final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); + req.fillRequirements(false, false, false); + } else { + ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); + if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { + manaCost = new ManaCostBeingPaid("0"); + } else { + manaCost = game.getActionPlay().getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())); + } + + if (!manaCost.isPaid()) { + FThreads.setInputAndWait(new InputPayManaSimple(game, sa, manaCost)); + } + + + if (manaCost.isPaid()) { + if (sa.isSpell() && !source.isCopiedSpell()) { + sa.setSourceCard(game.getAction().moveToStack(source)); + } + + game.getStack().add(sa); + } + } + } + + /** + *

+ * playSpellAbility_NoStack. + *

+ * + * @param sa + * a {@link forge.card.spellability.SpellAbility} object. + * @param skipTargeting + * a boolean. + */ + public final void playSpellAbilityNoStack( final SpellAbility sa) { + playSpellAbilityNoStack(sa, false); + } + public final void playSpellAbilityNoStack(final SpellAbility sa, boolean useOldTargets) { + sa.setActivatingPlayer(this); + + if (sa.getPayCosts() != null) { + final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); + + if (!sa.isTrigger()) { + payment.changeCost(); + } + + final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); + + req.fillRequirements(useOldTargets, false, true); + } else { + ManaCostBeingPaid manaCost = new ManaCostBeingPaid(sa.getManaCost()); + if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { + manaCost = new ManaCostBeingPaid("0"); + } else { + manaCost = game.getActionPlay().getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())); + } + + if( !manaCost.isPaid() ) { + FThreads.setInputAndWait(new InputPayManaSimple(game, sa, manaCost)); + } + + if (manaCost.isPaid()) { + AbilityUtils.resolve(sa, false); + } + + } + } + + /** + * choose optional additional costs. For HUMAN only + * @param activator + * + * @param original + * the original sa + * @return an ArrayList. + */ + public SpellAbility chooseOptionalAdditionalCosts(final SpellAbility original) { + //final HashMap map = new HashMap(); + final ArrayList abilities = GameActionUtil.getOptionalAdditionalCosts(original); + + if (!original.isSpell()) { + return original; + } + + return getController().getAbilityToPlay(abilities); + } + + } // end HumanPlayer class diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index dc52d39ab0c..2dd14120caf 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -364,14 +364,14 @@ public class PlayerControllerHuman extends PlayerController { @Override public void playMiracle(SpellAbility miracle, Card card) { if (GuiDialog.confirm(card, card + " - Drawn. Play for Miracle Cost?")) { - game.getActionPlay().playSpellAbility(miracle, player); + player.playSpellAbility(miracle); } } @Override public void playMadness(SpellAbility madness) { if (GuiDialog.confirm(madness.getSourceCard(), madness.getSourceCard() + " - Discarded. Pay Madness Cost?")) { - game.getActionPlay().playSpellAbility(madness, player); + player.playSpellAbility(madness); } } } diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index 349d1f8d1b6..e14a936792a 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -1125,7 +1125,7 @@ public class MagicStack extends MyObservable { for (int i = size - 1; i >= 0; i--) { SpellAbility next = orderedSAs.get(i); if (next.isTrigger()) { - game.getActionPlay().playSpellAbility(next, (HumanPlayer)activePlayer); + ((HumanPlayer)activePlayer).playSpellAbility(next); } else { this.add(next); } From b8107ac97f16372ec8ab5b0dd75717bc4b4464b6 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 06:59:25 +0000 Subject: [PATCH 079/123] SpellAbility cost adjustment moved to ManaCostBeingPaid the adjustment modifies the instance of ManaCostBeingPaid it was called on (all callers do create a new instance anyway and call this first) --- src/main/java/forge/card/cost/Cost.java | 7 +- .../forge/card/mana/ManaCostBeingPaid.java | 174 +++++++++++ .../card/staticability/StaticAbility.java | 14 +- .../StaticAbilityCostChange.java | 63 ++-- src/main/java/forge/game/GameActionPlay.java | 274 ------------------ src/main/java/forge/game/ai/AiController.java | 30 ++ .../java/forge/game/ai/ComputerUtilMana.java | 4 +- .../java/forge/game/player/HumanPlayer.java | 6 +- .../forge/game/player/PlayerController.java | 1 + .../forge/game/player/PlayerControllerAi.java | 8 + .../game/player/PlayerControllerHuman.java | 27 ++ 11 files changed, 283 insertions(+), 325 deletions(-) diff --git a/src/main/java/forge/card/cost/Cost.java b/src/main/java/forge/card/cost/Cost.java index b9ed4412aa5..016ea4e9982 100644 --- a/src/main/java/forge/card/cost/Cost.java +++ b/src/main/java/forge/card/cost/Cost.java @@ -23,7 +23,6 @@ import java.util.regex.Pattern; import forge.Card; import forge.CounterType; -import forge.Singletons; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostBeingPaid; import forge.card.mana.ManaCostParser; @@ -366,7 +365,8 @@ public class Cost { for (final CostPart part : this.costParts) { if (part instanceof CostPartMana) { final ManaCost mana = new ManaCost(new ManaCostParser(part.toString())); - final ManaCostBeingPaid changedCost = Singletons.getModel().getGame().getActionPlay().getSpellCostChange(sa, new ManaCostBeingPaid(mana)); + final ManaCostBeingPaid changedCost = new ManaCostBeingPaid(mana); + changedCost.applySpellCostChange(sa); ((CostPartMana)part).setAdjustedMana(changedCost.toString(false)); costChanged = true; @@ -374,7 +374,8 @@ public class Cost { } if (!costChanged) { // Spells with a cost of 0 should be affected too - final ManaCostBeingPaid changedCost = Singletons.getModel().getGame().getActionPlay().getSpellCostChange(sa, new ManaCostBeingPaid("0")); + final ManaCostBeingPaid changedCost = new ManaCostBeingPaid("0"); + changedCost.applySpellCostChange(sa); this.costParts.add(new CostPartMana(changedCost.toString(), 0, false)); } } diff --git a/src/main/java/forge/card/mana/ManaCostBeingPaid.java b/src/main/java/forge/card/mana/ManaCostBeingPaid.java index 224309c1312..dd4ebeb4803 100644 --- a/src/main/java/forge/card/mana/ManaCostBeingPaid.java +++ b/src/main/java/forge/card/mana/ManaCostBeingPaid.java @@ -22,8 +22,21 @@ import java.util.HashMap; import java.util.List; import java.util.Map.Entry; +import com.google.common.collect.Lists; + +import forge.Card; +import forge.CardColor; +import forge.CardLists; +import forge.CardPredicates; import forge.Constant; import forge.card.MagicColor; +import forge.card.spellability.SpellAbility; +import forge.card.staticability.StaticAbility; +import forge.game.GameState; +import forge.game.player.Player; +import forge.game.zone.ZoneType; +import forge.gui.GuiChoose; +import forge.util.MyRandom; /** *

@@ -652,4 +665,165 @@ public class ManaCostBeingPaid { public ManaCost getStartingCost() { return originalCost; } + + public final void applySpellCostChange(final SpellAbility sa) { + final GameState game = sa.getActivatingPlayer().getGame(); + // Beached + final Card originalCard = sa.getSourceCard(); + final SpellAbility spell = sa; + + + if (sa.isXCost() && !originalCard.isCopiedSpell()) { + originalCard.setXManaCostPaid(0); + } + + if (sa.isTrigger()) { + return; + } + + if (spell.isSpell()) { + if (spell.isDelve()) { + final Player pc = originalCard.getController(); + final List mutableGrave = Lists.newArrayList(pc.getZone(ZoneType.Graveyard).getCards()); + final List toExile = pc.getController().chooseCardsToDelve(this.getColorlessManaAmount(), mutableGrave); + for (final Card c : toExile) { + pc.getGame().getAction().exile(c); + decreaseColorlessMana(1); + } + } else if (spell.getSourceCard().hasKeyword("Convoke")) { + adjustCostByConvoke(sa, spell); + } + } // isSpell + + List cardsOnBattlefield = Lists.newArrayList(game.getCardsIn(ZoneType.Battlefield)); + cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Stack)); + cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Command)); + if (!cardsOnBattlefield.contains(originalCard)) { + cardsOnBattlefield.add(originalCard); + } + final ArrayList raiseAbilities = new ArrayList(); + final ArrayList reduceAbilities = new ArrayList(); + final ArrayList setAbilities = new ArrayList(); + + // Sort abilities to apply them in proper order + for (Card c : cardsOnBattlefield) { + final ArrayList staticAbilities = c.getStaticAbilities(); + for (final StaticAbility stAb : staticAbilities) { + if (stAb.getMapParams().get("Mode").equals("RaiseCost")) { + raiseAbilities.add(stAb); + } else if (stAb.getMapParams().get("Mode").equals("ReduceCost")) { + reduceAbilities.add(stAb); + } else if (stAb.getMapParams().get("Mode").equals("SetCost")) { + setAbilities.add(stAb); + } + } + } + // Raise cost + for (final StaticAbility stAb : raiseAbilities) { + stAb.applyAbility("RaiseCost", spell, this); + } + + // Reduce cost + for (final StaticAbility stAb : reduceAbilities) { + stAb.applyAbility("ReduceCost", spell, this); + } + + // Set cost (only used by Trinisphere) is applied last + for (final StaticAbility stAb : setAbilities) { + stAb.applyAbility("SetCost", spell, this); + } + } // GetSpellCostChange + + private void adjustCostByConvoke(final SpellAbility sa, final SpellAbility spell) { + + List untappedCreats = CardLists.filter(spell.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); + untappedCreats = CardLists.filter(untappedCreats, CardPredicates.Presets.UNTAPPED); + + while (!untappedCreats.isEmpty() && getConvertedManaCost() > 0) { + Card workingCard = null; + String chosenColor = null; + if (sa.getActivatingPlayer().isHuman()) { + workingCard = GuiChoose.oneOrNone("Tap for Convoke? " + toString(), untappedCreats); + if( null == workingCard ) + break; // that means "I'm done" + + List usableColors = getConvokableColors(workingCard); + if ( !usableColors.isEmpty() ) { + chosenColor = usableColors.size() == 1 ? usableColors.get(0) : GuiChoose.one("Convoke for which color?", usableColors); + } + } else { + // TODO: AI to choose a creature to tap would go here + // Probably along with deciding how many creatures to + // tap + + if ( MyRandom.getRandom().nextInt(3) == 0 ) // 66% chance to chose first creature, 33% to cancel + workingCard = untappedCreats.get(0); + + if( null == workingCard ) + break; // that means "I'm done" + + List usableColors = getConvokableColors(workingCard); + if ( !usableColors.isEmpty() ) { + // TODO: AI for choosing which color to convoke goes here. + chosenColor = usableColors.get(0); + } + + } + untappedCreats.remove(workingCard); + + + if ( null == chosenColor ) + continue; + else if (chosenColor.equals("colorless")) { + decreaseColorlessMana(1); + } else { + decreaseShard(ManaCostShard.valueOf(MagicColor.fromName(chosenColor)), 1); + } + + sa.addTappedForConvoke(workingCard); + } + + // Convoked creats are tapped here with triggers + // suppressed, + // Then again when payment is done(In + // InputPayManaCost.done()) with suppression cleared. + // This is to make sure that triggers go off at the + // right time + // AND that you can't use mana tapabilities of convoked + // creatures + // to pay the convoked cost. + for (final Card c : sa.getTappedForConvoke()) { + c.setTapped(true); + } + + } + + /** + * Gets the convokable colors. + * + * @param cardToConvoke + * the card to convoke + * @param cost + * the cost + * @return the convokable colors + */ + private List getConvokableColors(final Card cardToConvoke) { + final ArrayList usableColors = new ArrayList(); + + if (getColorlessManaAmount() > 0) { + usableColors.add("colorless"); + } + for (final CardColor col : cardToConvoke.getColor()) { + for (final String strCol : col.toStringList()) { + if (strCol.equals("colorless")) { + continue; + } + if (toString().contains(MagicColor.toShortString(strCol))) { + usableColors.add(strCol.toString()); + } + } + } + + return usableColors; + } } diff --git a/src/main/java/forge/card/staticability/StaticAbility.java b/src/main/java/forge/card/staticability/StaticAbility.java index 3ca3ff50eba..709fd184631 100644 --- a/src/main/java/forge/card/staticability/StaticAbility.java +++ b/src/main/java/forge/card/staticability/StaticAbility.java @@ -358,28 +358,26 @@ public class StaticAbility { * the originalCost * @return the modified ManaCost */ - public final ManaCostBeingPaid applyAbility(final String mode, final SpellAbility sa, final ManaCostBeingPaid originalCost) { + public final void applyAbility(final String mode, final SpellAbility sa, final ManaCostBeingPaid originalCost) { // don't apply the ability if it hasn't got the right mode if (!this.params.get("Mode").equals(mode)) { - return originalCost; + return; } if (this.isSuppressed() || !this.checkConditions()) { - return originalCost; + return; } if (mode.equals("RaiseCost")) { - return StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); + StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); } if (mode.equals("ReduceCost")) { - return StaticAbilityCostChange.applyReduceCostAbility(this, sa, originalCost); + StaticAbilityCostChange.applyReduceCostAbility(this, sa, originalCost); } if (mode.equals("SetCost")) { //Set cost is only used by Trinisphere - return StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); + StaticAbilityCostChange.applyRaiseCostAbility(this, sa, originalCost); } - - return originalCost; } /** diff --git a/src/main/java/forge/card/staticability/StaticAbilityCostChange.java b/src/main/java/forge/card/staticability/StaticAbilityCostChange.java index c989725b489..468ff7ab41f 100644 --- a/src/main/java/forge/card/staticability/StaticAbilityCostChange.java +++ b/src/main/java/forge/card/staticability/StaticAbilityCostChange.java @@ -45,51 +45,50 @@ public class StaticAbilityCostChange { * @param originalCost * a ManaCost */ - public static ManaCostBeingPaid applyRaiseCostAbility(final StaticAbility staticAbility, final SpellAbility sa - , final ManaCostBeingPaid originalCost) { + public static void applyRaiseCostAbility(final StaticAbility staticAbility, final SpellAbility sa, final ManaCostBeingPaid manaCost) { final HashMap params = staticAbility.getMapParams(); final Card hostCard = staticAbility.getHostCard(); final Player activator = sa.getActivatingPlayer(); final Card card = sa.getSourceCard(); final String amount = params.get("Amount"); - final ManaCostBeingPaid manaCost = new ManaCostBeingPaid(originalCost.toString()); + if (params.containsKey("ValidCard") && !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard)) { - return originalCost; + return; } if (params.containsKey("Activator") && ((activator == null) || !activator.isValid(params.get("Activator"), hostCard.getController(), hostCard))) { - return originalCost; + return; } if (params.containsKey("Type")) { if (params.get("Type").equals("Spell")) { if (!sa.isSpell()) { - return originalCost; + return; } } else if (params.get("Type").equals("Ability")) { if (!(sa instanceof AbilityActivated)) { - return originalCost; + return; } } else if (params.get("Type").equals("NonManaAbility")) { if (!(sa instanceof AbilityActivated) || sa.isManaAbility()) { - return originalCost; + return; } } else if (params.get("Type").equals("Flashback")) { if (!sa.isFlashBackAbility()) { - return originalCost; + return; } } } if (params.containsKey("AffectedZone") && !card.isInZone(ZoneType.smartValueOf(params.get("AffectedZone")))) { - return originalCost; + return; } if (params.containsKey("ValidTarget")) { Target tgt = sa.getTarget(); if (tgt == null) { - return originalCost; + return; } boolean targetValid = false; for (Object target : tgt.getTargets()) { @@ -101,13 +100,13 @@ public class StaticAbilityCostChange { } } if (!targetValid) { - return originalCost; + return; } } if (params.containsKey("ValidSpellTarget")) { Target tgt = sa.getTarget(); if (tgt == null) { - return originalCost; + return; } boolean targetValid = false; for (Object target : tgt.getTargets()) { @@ -119,7 +118,7 @@ public class StaticAbilityCostChange { } } if (!targetValid) { - return originalCost; + return; } } int value = 0; @@ -155,8 +154,6 @@ public class StaticAbilityCostChange { manaCost.increaseShard(ManaCostShard.GREEN, value); } } - - return manaCost; } /** @@ -169,58 +166,57 @@ public class StaticAbilityCostChange { * @param originalCost * a ManaCost */ - public static ManaCostBeingPaid applyReduceCostAbility(final StaticAbility staticAbility, final SpellAbility sa - , final ManaCostBeingPaid originalCost) { + public static void applyReduceCostAbility(final StaticAbility staticAbility, final SpellAbility sa, final ManaCostBeingPaid manaCost) { //Can't reduce zero cost - if (originalCost.toString().equals("0")) { - return originalCost; + if (manaCost.toString().equals("0")) { + return; } final HashMap params = staticAbility.getMapParams(); final Card hostCard = staticAbility.getHostCard(); final Player activator = sa.getActivatingPlayer(); final Card card = sa.getSourceCard(); final String amount = params.get("Amount"); - final ManaCostBeingPaid manaCost = new ManaCostBeingPaid(originalCost.toString()); + if (params.containsKey("ValidCard") && !card.isValid(params.get("ValidCard").split(","), hostCard.getController(), hostCard)) { - return originalCost; + return; } if (params.containsKey("Activator") && ((activator == null) || !activator.isValid(params.get("Activator"), hostCard.getController(), hostCard))) { - return originalCost; + return; } if (params.containsKey("Type")) { if (params.get("Type").equals("Spell")) { if (!sa.isSpell()) { - return originalCost; + return; } } else if (params.get("Type").equals("Ability")) { if (!(sa instanceof AbilityActivated)) { - return originalCost; + return; } } else if (params.get("Type").equals("Buyback")) { if (!sa.isBuyBackAbility()) { - return originalCost; + return; } } else if (params.get("Type").equals("Cycling")) { if (!sa.isCycling()) { - return originalCost; + return; } } else if (params.get("Type").equals("Equip")) { if (!(sa instanceof AbilityActivated) || !sa.hasParam("Equip")) { - return originalCost; + return; } } else if (params.get("Type").equals("Flashback")) { if (!sa.isFlashBackAbility()) { - return originalCost; + return; } } } if (params.containsKey("ValidTarget")) { Target tgt = sa.getTarget(); if (tgt == null) { - return originalCost; + return; } boolean targetValid = false; for (Object target : tgt.getTargets()) { @@ -232,11 +228,11 @@ public class StaticAbilityCostChange { } } if (!targetValid) { - return originalCost; + return; } } if (params.containsKey("AffectedZone") && !card.isInZone(ZoneType.smartValueOf(params.get("AffectedZone")))) { - return originalCost; + return; } int value = 0; if ("X".equals(amount)) { @@ -259,8 +255,5 @@ public class StaticAbilityCostChange { manaCost.decreaseShard(ManaCostShard.GREEN, value); } } - - - return manaCost; } } diff --git a/src/main/java/forge/game/GameActionPlay.java b/src/main/java/forge/game/GameActionPlay.java index fff072e4b3e..99724219ab2 100644 --- a/src/main/java/forge/game/GameActionPlay.java +++ b/src/main/java/forge/game/GameActionPlay.java @@ -1,27 +1,15 @@ package forge.game; -import java.util.ArrayList; import java.util.List; -import com.google.common.collect.Lists; - import forge.Card; -import forge.CardColor; -import forge.CardLists; -import forge.CardPredicates; import forge.FThreads; -import forge.card.MagicColor; import forge.card.ability.ApiType; import forge.card.ability.effects.CharmEffect; import forge.card.cost.CostPayment; -import forge.card.mana.ManaCostBeingPaid; import forge.card.mana.ManaCostShard; import forge.card.spellability.SpellAbility; import forge.card.spellability.HumanPlaySpellAbility; -import forge.card.staticability.StaticAbility; -import forge.game.ai.ComputerUtilCard; import forge.game.player.Player; -import forge.game.zone.ZoneType; -import forge.gui.GuiChoose; /** * TODO: Write javadoc for this type. @@ -84,266 +72,4 @@ public class GameActionPlay { game.getStack().add(sa, x); } } - - /** - *

- * getSpellCostChange. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - * @param originalCost - * a {@link forge.card.mana.ManaCostBeingPaid} object. - * @return a {@link forge.card.mana.ManaCostBeingPaid} object. - */ - public final ManaCostBeingPaid getSpellCostChange(final SpellAbility sa, final ManaCostBeingPaid originalCost) { - // Beached - final Card originalCard = sa.getSourceCard(); - final SpellAbility spell = sa; - String mana = originalCost.toString(); - ManaCostBeingPaid manaCost = new ManaCostBeingPaid(mana); - if (sa.isXCost() && !originalCard.isCopiedSpell()) { - originalCard.setXManaCostPaid(0); - } - - if (game == null || sa.isTrigger()) { - return manaCost; - } - - if (spell.isSpell()) { - if (spell.isDelve()) { - manaCost = getCostAfterDelve(originalCost, originalCard); - } else if (spell.getSourceCard().hasKeyword("Convoke")) { - ManaCostBeingPaid convokeCost = getCostAfterConvoke(sa, originalCost, spell); - if ( null != convokeCost ) - manaCost = convokeCost; - } - } // isSpell - - List cardsOnBattlefield = Lists.newArrayList(game.getCardsIn(ZoneType.Battlefield)); - cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Stack)); - cardsOnBattlefield.addAll(game.getCardsIn(ZoneType.Command)); - if (!cardsOnBattlefield.contains(originalCard)) { - cardsOnBattlefield.add(originalCard); - } - final ArrayList raiseAbilities = new ArrayList(); - final ArrayList reduceAbilities = new ArrayList(); - final ArrayList setAbilities = new ArrayList(); - - // Sort abilities to apply them in proper order - for (Card c : cardsOnBattlefield) { - final ArrayList staticAbilities = c.getStaticAbilities(); - for (final StaticAbility stAb : staticAbilities) { - if (stAb.getMapParams().get("Mode").equals("RaiseCost")) { - raiseAbilities.add(stAb); - } else if (stAb.getMapParams().get("Mode").equals("ReduceCost")) { - reduceAbilities.add(stAb); - } else if (stAb.getMapParams().get("Mode").equals("SetCost")) { - setAbilities.add(stAb); - } - } - } - // Raise cost - for (final StaticAbility stAb : raiseAbilities) { - manaCost = stAb.applyAbility("RaiseCost", spell, manaCost); - } - - // Reduce cost - for (final StaticAbility stAb : reduceAbilities) { - manaCost = stAb.applyAbility("ReduceCost", spell, manaCost); - } - - // Set cost (only used by Trinisphere) is applied last - for (final StaticAbility stAb : setAbilities) { - manaCost = stAb.applyAbility("SetCost", spell, manaCost); - } - - return manaCost; - } // GetSpellCostChange - - private ManaCostBeingPaid getCostAfterConvoke(final SpellAbility sa, final ManaCostBeingPaid originalCost, final SpellAbility spell) { - - List untappedCreats = CardLists.filter(spell.getActivatingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES); - untappedCreats = CardLists.filter(untappedCreats, CardPredicates.Presets.UNTAPPED); - - if (!untappedCreats.isEmpty()) { - final List choices = new ArrayList(); - choices.addAll(untappedCreats); - ArrayList usableColors = new ArrayList(); - ManaCostBeingPaid newCost = new ManaCostBeingPaid(originalCost.toString()); - Card tapForConvoke = null; - if (sa.getActivatingPlayer().isHuman()) { - tapForConvoke = GuiChoose.oneOrNone("Tap for Convoke? " + newCost.toString(), choices); - } else { - // TODO: AI to choose a creature to tap would go here - // Probably along with deciding how many creatures to - // tap - } - while (tapForConvoke != null && !untappedCreats.isEmpty()) { - final Card workingCard = (Card) tapForConvoke; - usableColors = GameActionPlay.getConvokableColors(workingCard, newCost); - - if (usableColors.size() != 0) { - String chosenColor = usableColors.get(0); - if (usableColors.size() > 1) { - if (sa.getActivatingPlayer().isHuman()) { - chosenColor = GuiChoose.one("Convoke for which color?", usableColors); - } else { - // TODO: AI for choosing which color to - // convoke goes here. - } - } - - if (chosenColor.equals("colorless")) { - newCost.decreaseColorlessMana(1); - } else { - String newCostStr = newCost.toString(); - newCostStr = newCostStr.replaceFirst( - MagicColor.toShortString(chosenColor), "").replaceFirst(" ", " "); - newCost = new ManaCostBeingPaid(newCostStr.trim()); - } - - sa.addTappedForConvoke(workingCard); - choices.remove(workingCard); - untappedCreats.remove(workingCard); - if (choices.isEmpty() || (newCost.getConvertedManaCost() == 0)) { - break; - } - } else { - untappedCreats.remove(workingCard); - } - - if (sa.getActivatingPlayer().isHuman()) { - tapForConvoke = GuiChoose.oneOrNone("Tap for Convoke? " + newCost.toString(), choices); - } else { - // TODO: AI to choose a creature to tap would go - // here - } - } - - // will only be null if user cancelled. - if (!sa.getTappedForConvoke().isEmpty()) { - // Convoked creats are tapped here with triggers - // suppressed, - // Then again when payment is done(In - // InputPayManaCost.done()) with suppression cleared. - // This is to make sure that triggers go off at the - // right time - // AND that you can't use mana tapabilities of convoked - // creatures - // to pay the convoked cost. - for (final Card c : sa.getTappedForConvoke()) { - c.setTapped(true); - } - - return newCost; - } - } - return null; - } - - private ManaCostBeingPaid getCostAfterDelve(final ManaCostBeingPaid originalCost, final Card originalCard) { - ManaCostBeingPaid manaCost; - final int cardsInGrave = originalCard.getController().getCardsIn(ZoneType.Graveyard).size(); - - final Player pc = originalCard.getController(); - if (pc.isHuman()) { - final Integer[] cntChoice = new Integer[cardsInGrave + 1]; - for (int i = 0; i <= cardsInGrave; i++) { - cntChoice[i] = Integer.valueOf(i); - } - - final Integer chosenAmount = GuiChoose.one("Exile how many cards?", cntChoice); - System.out.println("Delve for " + chosenAmount); - final List choices = new ArrayList(pc.getCardsIn(ZoneType.Graveyard)); - final List chosen = new ArrayList(); - for (int i = 0; i < chosenAmount; i++) { - final Card nowChosen = GuiChoose.oneOrNone("Exile which card?", choices); - - if (nowChosen == null) { - // User canceled,abort delving. - chosen.clear(); - break; - } - - choices.remove(nowChosen); - chosen.add(nowChosen); - } - - for (final Card c : chosen) { - game.getAction().exile(c); - } - - manaCost = new ManaCostBeingPaid(originalCost.toString()); - manaCost.decreaseColorlessMana(chosenAmount); - } else { - // AI - int numToExile = 0; - final int colorlessCost = originalCost.getColorlessManaAmount(); - - if (cardsInGrave <= colorlessCost) { - numToExile = cardsInGrave; - } else { - numToExile = colorlessCost; - } - - for (int i = 0; i < numToExile; i++) { - final List grave = pc.getZone(ZoneType.Graveyard).getCards(); - Card chosen = null; - for (final Card c : grave) { // Exile noncreatures first - // in - // case we can revive. Might - // wanna do some additional - // checking here for Flashback - // and the like. - if (!c.isCreature()) { - chosen = c; - break; - } - } - if (chosen == null) { - chosen = ComputerUtilCard.getWorstCreatureAI(grave); - } - - if (chosen == null) { - // Should never get here but... You know how it is. - chosen = grave.get(0); - } - - game.getAction().exile(chosen); - } - manaCost = new ManaCostBeingPaid(originalCost.toString()); - manaCost.decreaseColorlessMana(numToExile); - } - return manaCost; - } - - /** - * Gets the convokable colors. - * - * @param cardToConvoke - * the card to convoke - * @param cost - * the cost - * @return the convokable colors - */ - public static ArrayList getConvokableColors(final Card cardToConvoke, final ManaCostBeingPaid cost) { - final ArrayList usableColors = new ArrayList(); - - if (cost.getColorlessManaAmount() > 0) { - usableColors.add("colorless"); - } - for (final CardColor col : cardToConvoke.getColor()) { - for (final String strCol : col.toStringList()) { - if (strCol.equals("colorless")) { - continue; - } - if (cost.toString().contains(MagicColor.toShortString(strCol))) { - usableColors.add(strCol.toString()); - } - } - } - - return usableColors; - } } diff --git a/src/main/java/forge/game/ai/AiController.java b/src/main/java/forge/game/ai/AiController.java index ac386890e3e..1c17d34b6fe 100644 --- a/src/main/java/forge/game/ai/AiController.java +++ b/src/main/java/forge/game/ai/AiController.java @@ -864,5 +864,35 @@ public class AiController { } while ( sa != null ); } + public List chooseCardsToDelve(int colorlessCost, List grave) { + List toExile = new ArrayList(); + int numToExile = Math.min(grave.size(), colorlessCost); + + for (int i = 0; i < numToExile; i++) { + Card chosen = null; + for (final Card c : grave) { // Exile noncreatures first in + // case we can revive. Might + // wanna do some additional + // checking here for Flashback + // and the like. + if (!c.isCreature()) { + chosen = c; + break; + } + } + if (chosen == null) { + chosen = ComputerUtilCard.getWorstCreatureAI(grave); + } + + if (chosen == null) { + // Should never get here but... You know how it is. + chosen = grave.get(0); + } + + toExile.add(chosen); + grave.remove(chosen); + } + return toExile; + } } diff --git a/src/main/java/forge/game/ai/ComputerUtilMana.java b/src/main/java/forge/game/ai/ComputerUtilMana.java index 123a7807f15..343242388d2 100644 --- a/src/main/java/forge/game/ai/ComputerUtilMana.java +++ b/src/main/java/forge/game/ai/ComputerUtilMana.java @@ -13,7 +13,6 @@ import forge.Card; import forge.CardLists; import forge.CardUtil; import forge.Constant; -import forge.Singletons; import forge.card.MagicColor; import forge.card.ability.AbilityUtils; import forge.card.ability.ApiType; @@ -398,8 +397,7 @@ public class ComputerUtilMana { final ManaCost mana = sa.getPayCosts() != null ? sa.getPayCosts().getTotalMana() : sa.getManaCost(); ManaCostBeingPaid cost = new ManaCostBeingPaid(mana); - - cost = Singletons.getModel().getGame().getActionPlay().getSpellCostChange(sa, cost); + cost.applySpellCostChange(sa); final Card card = sa.getSourceCard(); // Tack xMana Payments into mana here if X is a set value diff --git a/src/main/java/forge/game/player/HumanPlayer.java b/src/main/java/forge/game/player/HumanPlayer.java index 80fe463e5d3..1e329bd33b6 100644 --- a/src/main/java/forge/game/player/HumanPlayer.java +++ b/src/main/java/forge/game/player/HumanPlayer.java @@ -144,7 +144,8 @@ public class HumanPlayer extends Player { if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { manaCost = new ManaCostBeingPaid("0"); } else { - manaCost = game.getActionPlay().getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())); + manaCost = new ManaCostBeingPaid(sa.getManaCost()); + manaCost.applySpellCostChange(sa); } if (!manaCost.isPaid()) { @@ -193,7 +194,8 @@ public class HumanPlayer extends Player { if (sa.getSourceCard().isCopiedSpell() && sa.isSpell()) { manaCost = new ManaCostBeingPaid("0"); } else { - manaCost = game.getActionPlay().getSpellCostChange(sa, new ManaCostBeingPaid(sa.getManaCost())); + manaCost = new ManaCostBeingPaid(sa.getManaCost()); + manaCost.applySpellCostChange(sa); } if( !manaCost.isPaid() ) { diff --git a/src/main/java/forge/game/player/PlayerController.java b/src/main/java/forge/game/player/PlayerController.java index 223969c1676..093a5b82b97 100644 --- a/src/main/java/forge/game/player/PlayerController.java +++ b/src/main/java/forge/game/player/PlayerController.java @@ -118,4 +118,5 @@ public abstract class PlayerController { public abstract void playMiracle(SpellAbility miracle, Card card); public abstract void playMadness(SpellAbility madness); + public abstract List chooseCardsToDelve(int colorLessAmount, List grave); } diff --git a/src/main/java/forge/game/player/PlayerControllerAi.java b/src/main/java/forge/game/player/PlayerControllerAi.java index 9b7e0f7c909..5760322bc4f 100644 --- a/src/main/java/forge/game/player/PlayerControllerAi.java +++ b/src/main/java/forge/game/player/PlayerControllerAi.java @@ -244,4 +244,12 @@ public class PlayerControllerAi extends PlayerController { getAi().chooseAndPlaySa(false, false, madness); } + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseCardsToDelve(int, java.util.List) + */ + @Override + public List chooseCardsToDelve(int colorlessCost, List grave) { + return getAi().chooseCardsToDelve(colorlessCost, grave); + } + } diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index 2dd14120caf..54b951f445c 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -374,4 +374,31 @@ public class PlayerControllerHuman extends PlayerController { player.playSpellAbility(madness); } } + + @Override + public List chooseCardsToDelve(int colorLessAmount, List grave) { + List toExile = new ArrayList(); + int cardsInGrave = grave.size(); + final Integer[] cntChoice = new Integer[cardsInGrave + 1]; + for (int i = 0; i <= cardsInGrave; i++) { + cntChoice[i] = Integer.valueOf(i); + } + + final Integer chosenAmount = GuiChoose.one("Exile how many cards?", cntChoice); + System.out.println("Delve for " + chosenAmount); + + for (int i = 0; i < chosenAmount; i++) { + final Card nowChosen = GuiChoose.oneOrNone("Exile which card?", grave); + + if (nowChosen == null) { + // User canceled,abort delving. + toExile.clear(); + break; + } + + grave.remove(nowChosen); + toExile.add(nowChosen); + } + return toExile; + } } From 10f4be4c97988c494b90396454ee6fc67303ffed Mon Sep 17 00:00:00 2001 From: swordshine Date: Wed, 3 Apr 2013 06:59:40 +0000 Subject: [PATCH 080/123] - vanguard: Added Peacekeeper Avatar and Stonehewer Giant Avatar --- .gitattributes | 2 + res/cardsfolder/p/peacekeeper_avatar.txt | 10 +++ res/cardsfolder/s/stonehewer_giant_avatar.txt | 10 +++ .../java/forge/card/CardRulesPredicates.java | 4 + .../ability/effects/CopyPermanentEffect.java | 90 ++++++++++++++++++- 5 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 res/cardsfolder/p/peacekeeper_avatar.txt create mode 100644 res/cardsfolder/s/stonehewer_giant_avatar.txt diff --git a/.gitattributes b/.gitattributes index a5a98ce2322..3ef50bba9ad 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7654,6 +7654,7 @@ res/cardsfolder/p/peace_and_quiet.txt svneol=native#text/plain res/cardsfolder/p/peace_of_mind.txt svneol=native#text/plain res/cardsfolder/p/peace_strider.txt svneol=native#text/plain res/cardsfolder/p/peacekeeper.txt svneol=native#text/plain +res/cardsfolder/p/peacekeeper_avatar.txt -text res/cardsfolder/p/peach_garden_oath.txt svneol=native#text/plain res/cardsfolder/p/pearl_dragon.txt svneol=native#text/plain res/cardsfolder/p/pearl_medallion.txt svneol=native#text/plain @@ -10345,6 +10346,7 @@ res/cardsfolder/s/stonefare_crocodile.txt -text res/cardsfolder/s/stoneforge_mystic.txt svneol=native#text/plain res/cardsfolder/s/stonehands.txt svneol=native#text/plain res/cardsfolder/s/stonehewer_giant.txt -text +res/cardsfolder/s/stonehewer_giant_avatar.txt -text res/cardsfolder/s/stonehorn_dignitary.txt -text res/cardsfolder/s/stonewood_invocation.txt svneol=native#text/plain res/cardsfolder/s/stonewood_invoker.txt svneol=native#text/plain diff --git a/res/cardsfolder/p/peacekeeper_avatar.txt b/res/cardsfolder/p/peacekeeper_avatar.txt new file mode 100644 index 00000000000..5cdb64ff96a --- /dev/null +++ b/res/cardsfolder/p/peacekeeper_avatar.txt @@ -0,0 +1,10 @@ +Name:Peacekeeper Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+0/+9 +A:AB$ RepeatEach | Cost$ 3 | ActivationZone$ Command | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ ArrestEach | StackDescription$ SpellDescription | SpellDescription$ For each opponent who controls a creature, put a token onto the battlefield that's a copy of a card named Arrest and attach it to a creature that player controls chosen at random. +SVar:ArrestEach:DB$ ChooseCard | Amount$ 1 | Choices$ Creature.RememberedPlayerCtrl | AtRandom$ True | SubAbility$ DBAttach +SVar:DBAttach:DB$ CopyPermanent | NumCopies$ 1 | ValidSupportedCopy$ Card.namedArrest | DefinedName$ Arrest | AttachedTo$ ChosenCard | ConditionDefined$ ChosenCard | ConditionPresent$ Creature | ConditionCompare$ GE1 +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/peacekeeper_avatar.jpg +Oracle:Hand +0, life +9\n{3}: For each opponent who controls a creature, put a token onto the battlefield that's a copy of a card named Arrest and attach it to a creature that player controls chosen at random. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/s/stonehewer_giant_avatar.txt b/res/cardsfolder/s/stonehewer_giant_avatar.txt new file mode 100644 index 00000000000..fa1328a2d4a --- /dev/null +++ b/res/cardsfolder/s/stonehewer_giant_avatar.txt @@ -0,0 +1,10 @@ +Name:Stonehewer Giant Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+1/-5 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | TriggerZones$ Command | Execute$ TrigCopyEquip | TriggerDescription$ Whenever a creature enters the battlefield under your control, put a token onto the battlefield that's a copy of a random Equipment card with converted mana cost less than or equal to that creature's converted mana cost. Attach that Equipment to that creature. +SVar:TrigCopyEquip:AB$ CopyPermanent | Cost$ 0 | Defined$ TriggeredCard | NumCopies$ 1 | ValidSupportedCopy$ Equipment.cmcLEX | References$ X | RandomCopied$ True | RandomNum$ 1 | RememberCopied$ True | AttachedTo$ TriggeredCard +SVar:X:TriggeredCard$CardManaCost +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/stonehewer_giant_avatar.jpg +Oracle:Hand +1, life -5\nWhenever a creature enters the battlefield under your control, put a token onto the battlefield that's a copy of a random Equipment card with converted mana cost less than or equal to that creature's converted mana cost. Attach that Equipment to that creature. +SetInfo:VAN Special \ No newline at end of file diff --git a/src/main/java/forge/card/CardRulesPredicates.java b/src/main/java/forge/card/CardRulesPredicates.java index 733fb683029..37a5f18fd6e 100644 --- a/src/main/java/forge/card/CardRulesPredicates.java +++ b/src/main/java/forge/card/CardRulesPredicates.java @@ -462,6 +462,10 @@ public final class CardRulesPredicates { /** The Constant isArtifact. */ public static final Predicate IS_ARTIFACT = CardRulesPredicates .coreType(true, CardCoreType.Artifact); + + /** The Constant isEquipment. */ + public static final Predicate IS_EQUIPMENT = CardRulesPredicates + .subType("Equipment"); /** The Constant isLand. */ public static final Predicate IS_LAND = CardRulesPredicates.coreType(true, CardCoreType.Land); diff --git a/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java b/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java index 81520c3faab..f9cdecb4a34 100644 --- a/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java +++ b/src/main/java/forge/card/ability/effects/CopyPermanentEffect.java @@ -6,10 +6,17 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + import forge.Card; import forge.CardCharacteristicName; +import forge.CardLists; import forge.Command; import forge.Singletons; +import forge.card.CardRulesPredicates; import forge.card.ability.AbilityUtils; import forge.card.ability.SpellAbilityEffect; import forge.card.cardfactory.CardFactory; @@ -18,8 +25,15 @@ import forge.card.mana.ManaCost; import forge.card.spellability.Ability; import forge.card.spellability.SpellAbility; import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCard; +import forge.game.player.HumanPlayer; import forge.game.player.Player; +import forge.game.zone.ZoneType; +import forge.gui.GuiChoose; import forge.item.CardDb; +import forge.item.CardPrinted; +import forge.util.Aggregates; +import forge.util.PredicateString.StringOp; public class CopyPermanentEffect extends SpellAbilityEffect { @@ -36,6 +50,9 @@ public class CopyPermanentEffect extends SpellAbilityEffect { return sb.toString(); } + /* (non-Javadoc) + * @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility) + */ @Override public void resolve(final SpellAbility sa) { final Card hostCard = sa.getSourceCard(); @@ -46,9 +63,47 @@ public class CopyPermanentEffect extends SpellAbilityEffect { final int numCopies = sa.hasParam("NumCopies") ? AbilityUtils.calculateAmount(hostCard, sa.getParam("NumCopies"), sa) : 1; - final List tgtCards = getTargetCards(sa); + List tgtCards = getTargetCards(sa); final Target tgt = sa.getTarget(); + if (sa.hasParam("ValidSupportedCopy")) { + List cards = Lists.newArrayList(CardDb.instance().getUniqueCards()); + String valid = sa.getParam("ValidSupportedCopy"); + if (valid.contains("X")) { + valid = valid.replace("X", Integer.toString(AbilityUtils.calculateAmount(hostCard, "X", sa))); + } + if (StringUtils.containsIgnoreCase(valid, "creature")) { + Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_CREATURE, CardPrinted.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + } + if (StringUtils.containsIgnoreCase(valid, "equipment")) { + Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_EQUIPMENT, CardPrinted.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + } + if (sa.hasParam("RandomCopied")) { + List copysource = new ArrayList(cards); + List choice = new ArrayList(); + final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1"; + int ncopied = AbilityUtils.calculateAmount(hostCard, num, sa); + while(ncopied > 0) { + final CardPrinted cp = Aggregates.random(copysource); + if (cp.getMatchingForgeCard().isValid(valid, hostCard.getController(), hostCard)) { + choice.add(cp.getMatchingForgeCard()); + copysource.remove(cp); + ncopied -= 1; + } + } + tgtCards = choice; + } else if (sa.hasParam("DefinedName")) { + final String name = sa.getParam("DefinedName"); + Predicate cpp = Predicates.compose(CardRulesPredicates.name(StringOp.EQUALS, name), CardPrinted.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + Card c = cards.get(0).getMatchingForgeCard(); + tgtCards.clear(); + tgtCards.add(c); + } + } + Player controller = null; if (sa.hasParam("Controller")) { List defined = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("Controller"), sa); @@ -137,6 +192,38 @@ public class CopyPermanentEffect extends SpellAbilityEffect { if (c.isFaceDown()) { c.setState(CardCharacteristicName.FaceDown); } + + if (sa.hasParam("AttachedTo")) { + List list = AbilityUtils.getDefinedCards(hostCard, + sa.getParam("AttachedTo"), sa); + if (list.isEmpty()) { + list = copy.getController().getGame().getCardsIn(ZoneType.Battlefield); + list = CardLists.getValidCards(list, sa.getParam("AttachedTo"), copy.getController(), copy); + } + if (!list.isEmpty()) { + Card attachedTo = null; + if (sa.getActivatingPlayer() instanceof HumanPlayer) { + if (list.size() > 1) { + attachedTo = GuiChoose.one(copy + " - Select a card to attach to.", list); + } else { + attachedTo = list.get(0); + } + } else { // AI player + attachedTo = ComputerUtilCard.getBestAI(list); + } + if (copy.isAura()) { + if (attachedTo.canBeEnchantedBy(copy)) { + copy.enchantEntity(attachedTo); + } else {//can't enchant + continue; + } + } else { //Equipment + copy.equipCard(attachedTo); + } + } else { + continue; + } + } copy = Singletons.getModel().getGame().getAction().moveToPlay(copy); copy.setCloneOrigin(hostCard); @@ -147,6 +234,7 @@ public class CopyPermanentEffect extends SpellAbilityEffect { if (wasInAlt) { c.setState(stateName); } + // have to do this since getTargetCard() might change // if Kiki-Jiki somehow gets untapped again From d9e3978babd1630c77fecb170e21e3d4263cf5ba Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 07:19:05 +0000 Subject: [PATCH 081/123] the remaining 2 methods used to play human's spells also moved to HumanPlayer GameActionPlay.java eliminated. --- .gitattributes | 1 - .../cardfactory/CardFactorySorceries.java | 3 +- src/main/java/forge/control/FControl.java | 8 +- src/main/java/forge/game/GameActionPlay.java | 75 ------------------- src/main/java/forge/game/GameActionUtil.java | 3 +- src/main/java/forge/game/GameState.java | 11 --- .../java/forge/game/player/HumanPlayer.java | 48 ++++++++++++ .../forge/game/player/PlayerControllerAi.java | 2 +- .../game/player/PlayerControllerHuman.java | 6 +- src/main/java/forge/gui/GuiDisplayUtil.java | 5 +- 10 files changed, 63 insertions(+), 99 deletions(-) delete mode 100644 src/main/java/forge/game/GameActionPlay.java diff --git a/.gitattributes b/.gitattributes index 3ef50bba9ad..3f6b8f4316a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13891,7 +13891,6 @@ src/main/java/forge/deck/package-info.java svneol=native#text/plain src/main/java/forge/error/ExceptionHandler.java svneol=native#text/plain src/main/java/forge/error/package-info.java svneol=native#text/plain src/main/java/forge/game/GameAction.java svneol=native#text/plain -src/main/java/forge/game/GameActionPlay.java -text src/main/java/forge/game/GameActionUtil.java svneol=native#text/plain src/main/java/forge/game/GameEndReason.java -text src/main/java/forge/game/GameFormat.java -text diff --git a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java index 47fbdd47bf9..c7d2539eba0 100644 --- a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java +++ b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java @@ -45,6 +45,7 @@ import forge.control.input.InputSelectCardsFromList; import forge.game.GameState; import forge.game.ai.ComputerUtil; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -225,7 +226,7 @@ public class CardFactorySorceries { JOptionPane.INFORMATION_MESSAGE); } } else { - Singletons.getModel().getGame().getActionPlay().playCardWithoutManaCost(playing, card.getController()); + ((HumanPlayer)card.getController()).playCardWithoutManaCost(playing); } chosen.remove(playing); } diff --git a/src/main/java/forge/control/FControl.java b/src/main/java/forge/control/FControl.java index 21d56e514c5..8589c833d54 100644 --- a/src/main/java/forge/control/FControl.java +++ b/src/main/java/forge/control/FControl.java @@ -34,7 +34,7 @@ import javax.swing.WindowConstants; import forge.Singletons; import forge.control.KeyboardShortcuts.Shortcut; import forge.game.ai.AiProfileUtil; -import forge.game.player.Player; +import forge.game.player.HumanPlayer; import forge.gui.SOverlayUtils; import forge.gui.deckeditor.CDeckEditorUI; import forge.gui.deckeditor.VDeckEditorUI; @@ -284,8 +284,8 @@ public enum FControl { } /** @return {@link forge.game.player.Player} */ - private Player localPlayer; - public Player getPlayer() { + private HumanPlayer localPlayer; + public HumanPlayer getPlayer() { return localPlayer; } @@ -305,7 +305,7 @@ public enum FControl { * TODO: Write javadoc for this method. * @param localHuman */ - public void setPlayer(Player localHuman) { + public void setPlayer(HumanPlayer localHuman) { localPlayer = localHuman; } diff --git a/src/main/java/forge/game/GameActionPlay.java b/src/main/java/forge/game/GameActionPlay.java deleted file mode 100644 index 99724219ab2..00000000000 --- a/src/main/java/forge/game/GameActionPlay.java +++ /dev/null @@ -1,75 +0,0 @@ -package forge.game; - -import java.util.List; -import forge.Card; -import forge.FThreads; -import forge.card.ability.ApiType; -import forge.card.ability.effects.CharmEffect; -import forge.card.cost.CostPayment; -import forge.card.mana.ManaCostShard; -import forge.card.spellability.SpellAbility; -import forge.card.spellability.HumanPlaySpellAbility; -import forge.game.player.Player; - -/** - * TODO: Write javadoc for this type. - * - */ -public class GameActionPlay { - - private final GameState game; - - - public GameActionPlay(final GameState game0) { - game = game0; - } - - public final void playCardWithoutManaCost(final Card c, Player player) { - final List choices = c.getBasicSpells(); - // TODO add Buyback, Kicker, ... , spells here - - SpellAbility sa = player.getController().getAbilityToPlay(choices); - - if (sa == null) { - return; - } - - sa.setActivatingPlayer(player); - this.playSpellAbilityWithoutPayingManaCost(sa); - } - - /** - *

- * playSpellAbilityForFree. - *

- * - * @param sa - * a {@link forge.card.spellability.SpellAbility} object. - */ - public final void playSpellAbilityWithoutPayingManaCost(final SpellAbility sa) { - FThreads.checkEDT("GameActionPlay.playSpellAbilityWithoutPayingManaCost", false); - final Card source = sa.getSourceCard(); - - source.setSplitStateToPlayAbility(sa); - - if (sa.getPayCosts() != null) { - if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - CharmEffect.makeChoices(sa); - } - final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); - - final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); - req.fillRequirements(false, true, false); - } else { - if (sa.isSpell()) { - final Card c = sa.getSourceCard(); - if (!c.isCopiedSpell()) { - sa.setSourceCard(game.getAction().moveToStack(c)); - } - } - boolean x = sa.getSourceCard().getManaCost().getShardCount(ManaCostShard.X) > 0; - - game.getStack().add(sa, x); - } - } -} diff --git a/src/main/java/forge/game/GameActionUtil.java b/src/main/java/forge/game/GameActionUtil.java index d117414a1b5..73fb9ba5595 100644 --- a/src/main/java/forge/game/GameActionUtil.java +++ b/src/main/java/forge/game/GameActionUtil.java @@ -66,6 +66,7 @@ import forge.control.input.InputSelectCardsFromList; import forge.game.event.CardDamagedEvent; import forge.game.event.LifeLossEvent; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; @@ -274,7 +275,7 @@ public final class GameActionUtil { if (p.isHuman()) { if (GuiDialog.confirm(rippledCards[i], "Cast " + rippledCards[i].getName() + "?")) { - game.getActionPlay().playCardWithoutManaCost(rippledCards[i], p); + ((HumanPlayer)p).playCardWithoutManaCost(rippledCards[i]); revealed.remove(rippledCards[i]); } } else { diff --git a/src/main/java/forge/game/GameState.java b/src/main/java/forge/game/GameState.java index 395469436ae..95ab8dcae38 100644 --- a/src/main/java/forge/game/GameState.java +++ b/src/main/java/forge/game/GameState.java @@ -88,7 +88,6 @@ public class GameState { private long timestamp = 0; public final GameAction action; - public final GameActionPlay actionPlay; private final MatchController match; /** @@ -109,7 +108,6 @@ public class GameState { allPlayers = Collections.unmodifiableList(players); roIngamePlayers = Collections.unmodifiableList(ingamePlayers); action = new GameAction(this); - actionPlay = new GameActionPlay(this); stack = new MagicStack(this); phaseHandler = new PhaseHandler(this); @@ -663,15 +661,6 @@ public class GameState { } } - /** - * TODO: Write javadoc for this method. - * @return - */ - public GameActionPlay getActionPlay() { - // TODO Auto-generated method stub - return actionPlay; - } - public boolean mulliganned = false; public boolean hasMulliganned(){ return mulliganned; } public void setMulliganned(boolean value) { mulliganned = value; } diff --git a/src/main/java/forge/game/player/HumanPlayer.java b/src/main/java/forge/game/player/HumanPlayer.java index 1e329bd33b6..11a0651574b 100644 --- a/src/main/java/forge/game/player/HumanPlayer.java +++ b/src/main/java/forge/game/player/HumanPlayer.java @@ -28,6 +28,7 @@ import forge.card.ability.effects.CharmEffect; import forge.card.cost.Cost; import forge.card.cost.CostPayment; import forge.card.mana.ManaCostBeingPaid; +import forge.card.mana.ManaCostShard; import forge.card.spellability.Ability; import forge.card.spellability.HumanPlaySpellAbility; import forge.card.spellability.SpellAbility; @@ -229,5 +230,52 @@ public class HumanPlayer extends Player { } + public final void playCardWithoutManaCost(final Card c) { + final List choices = c.getBasicSpells(); + // TODO add Buyback, Kicker, ... , spells here + SpellAbility sa = controller.getAbilityToPlay(choices); + + if (sa == null) { + return; + } + + sa.setActivatingPlayer(this); + this.playSpellAbilityWithoutPayingManaCost(sa); + } + + /** + *

+ * playSpellAbilityForFree. + *

+ * + * @param sa + * a {@link forge.card.spellability.SpellAbility} object. + */ + public final void playSpellAbilityWithoutPayingManaCost(final SpellAbility sa) { + FThreads.checkEDT("GameActionPlay.playSpellAbilityWithoutPayingManaCost", false); + final Card source = sa.getSourceCard(); + + source.setSplitStateToPlayAbility(sa); + + if (sa.getPayCosts() != null) { + if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { + CharmEffect.makeChoices(sa); + } + final CostPayment payment = new CostPayment(sa.getPayCosts(), sa); + + final HumanPlaySpellAbility req = new HumanPlaySpellAbility(sa, payment); + req.fillRequirements(false, true, false); + } else { + if (sa.isSpell()) { + final Card c = sa.getSourceCard(); + if (!c.isCopiedSpell()) { + sa.setSourceCard(game.getAction().moveToStack(c)); + } + } + boolean x = sa.getSourceCard().getManaCost().getShardCount(ManaCostShard.X) > 0; + + game.getStack().add(sa, x); + } + } } // end HumanPlayer class diff --git a/src/main/java/forge/game/player/PlayerControllerAi.java b/src/main/java/forge/game/player/PlayerControllerAi.java index 5760322bc4f..5af7fda17f2 100644 --- a/src/main/java/forge/game/player/PlayerControllerAi.java +++ b/src/main/java/forge/game/player/PlayerControllerAi.java @@ -68,7 +68,7 @@ public class PlayerControllerAi extends PlayerController { } else if (abilities.size() == 1) { return abilities.get(0); } else { - return GuiChoose.oneOrNone("Choose", abilities); // some day network interaction will be here + return GuiChoose.oneOrNone("Choose ability for AI to play", abilities); // some day network interaction will be here } } diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index 54b951f445c..10ecb1cd17f 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -104,7 +104,7 @@ public class PlayerControllerHuman extends PlayerController { */ public void playFromSuspend(Card c) { c.setSuspendCast(true); - game.getActionPlay().playCardWithoutManaCost(c, c.getOwner()); + player.playCardWithoutManaCost(c); } /* (non-Javadoc) @@ -122,7 +122,7 @@ public class PlayerControllerHuman extends PlayerController { boolean result = GuiDialog.confirm(cascadedCard, question.toString()); if ( result ) - game.getActionPlay().playCardWithoutManaCost(cascadedCard, getPlayer()); + player.playCardWithoutManaCost(cascadedCard); return result; } @@ -131,7 +131,7 @@ public class PlayerControllerHuman extends PlayerController { */ @Override public void playSpellAbilityForFree(SpellAbility copySA) { - game.getActionPlay().playSpellAbilityWithoutPayingManaCost(copySA); + player.playSpellAbilityWithoutPayingManaCost(copySA); } /** diff --git a/src/main/java/forge/gui/GuiDisplayUtil.java b/src/main/java/forge/gui/GuiDisplayUtil.java index 543e5506373..b723be45366 100644 --- a/src/main/java/forge/gui/GuiDisplayUtil.java +++ b/src/main/java/forge/gui/GuiDisplayUtil.java @@ -632,9 +632,10 @@ public final class GuiDisplayUtil { FThreads.invokeInNewThread(new Runnable() { @Override public void run() { + game.getAction().moveToHand(forgeCard); // this is really needed (for rollbacks at least) + // Human player is choosing targets for an ability controlled by chosen player. sa.setActivatingPlayer(p); - game.getAction().moveToHand(forgeCard); // this is really needed - game.getActionPlay().playSpellAbilityWithoutPayingManaCost(sa); + Singletons.getControl().getPlayer().playSpellAbilityWithoutPayingManaCost(sa); } }); } From 8810a67f7a32cad20c646b7844e5a33a026c3c65 Mon Sep 17 00:00:00 2001 From: swordshine Date: Wed, 3 Apr 2013 07:48:44 +0000 Subject: [PATCH 082/123] - vanguard: Added Sliver Queen Avatar --- .gitattributes | 1 + res/cardsfolder/s/sliver_queen_avatar.txt | 13 +++++++++++++ res/cardsfolder/s/stonehewer_giant_avatar.txt | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 res/cardsfolder/s/sliver_queen_avatar.txt diff --git a/.gitattributes b/.gitattributes index 3f6b8f4316a..0c16bf27fe9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9794,6 +9794,7 @@ res/cardsfolder/s/slithery_stalker.txt svneol=native#text/plain res/cardsfolder/s/sliver_legion.txt svneol=native#text/plain res/cardsfolder/s/sliver_overlord.txt svneol=native#text/plain res/cardsfolder/s/sliver_queen.txt svneol=native#text/plain +res/cardsfolder/s/sliver_queen_avatar.txt -text res/cardsfolder/s/sliver_queen_brood_mother.txt -text res/cardsfolder/s/sliversmith.txt svneol=native#text/plain res/cardsfolder/s/slobad_goblin_tinkerer.txt svneol=native#text/plain diff --git a/res/cardsfolder/s/sliver_queen_avatar.txt b/res/cardsfolder/s/sliver_queen_avatar.txt new file mode 100644 index 00000000000..acd9342d67b --- /dev/null +++ b/res/cardsfolder/s/sliver_queen_avatar.txt @@ -0,0 +1,13 @@ +Name:Sliver Queen Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+0/+7 +T:Mode$ SpellCast | ValidCard$ Creature.nonSliver | ValidActivatingPlayer$ You | TriggerZones$ Command | Execute$ TrigCopy | TriggerDescription$ Whenever you cast a non-Sliver creature spell, exile that spell. If you do, put a token onto the battlefield that's a copy of a random non-Shapeshifter Sliver creature card with the same converted mana cost as that spell. +SVar:TrigCopy:AB$ ChangeZone | Cost$ 0 | Defined$ TriggeredCard | Origin$ Stack | Destination$ Exile | Fizzle$ True | RememberChanged$ True | SubAbility$ DBCopy +SVar:DBCopy:DB$ CopyPermanent | NumCopies$ 1 | ValidSupportedCopy$ Creature.Sliver+nonShapeshifter+cmcEQX | RandomCopied$ True | RandomNum$ 1 | ConditionCheckSVar$ RememberedSize | ConditionSVarCompare$ GE1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:TriggeredCard$CardManaCost +SVar:RememberedSize:Remembered$Amount +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/sliver_queen_avatar.jpg +Oracle:Hand +0, life +7\nWhenever you cast a non-Sliver creature spell, exile that spell. If you do, put a token onto the battlefield that's a copy of a random non-Shapeshifter Sliver creature card with the same converted mana cost as that spell. +SetInfo:VAN Special \ No newline at end of file diff --git a/res/cardsfolder/s/stonehewer_giant_avatar.txt b/res/cardsfolder/s/stonehewer_giant_avatar.txt index fa1328a2d4a..656eb7b6b60 100644 --- a/res/cardsfolder/s/stonehewer_giant_avatar.txt +++ b/res/cardsfolder/s/stonehewer_giant_avatar.txt @@ -3,7 +3,7 @@ ManaCost:no cost Types:Vanguard HandLifeModifier:+1/-5 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | TriggerZones$ Command | Execute$ TrigCopyEquip | TriggerDescription$ Whenever a creature enters the battlefield under your control, put a token onto the battlefield that's a copy of a random Equipment card with converted mana cost less than or equal to that creature's converted mana cost. Attach that Equipment to that creature. -SVar:TrigCopyEquip:AB$ CopyPermanent | Cost$ 0 | Defined$ TriggeredCard | NumCopies$ 1 | ValidSupportedCopy$ Equipment.cmcLEX | References$ X | RandomCopied$ True | RandomNum$ 1 | RememberCopied$ True | AttachedTo$ TriggeredCard +SVar:TrigCopyEquip:AB$ CopyPermanent | Cost$ 0 | Defined$ TriggeredCard | NumCopies$ 1 | ValidSupportedCopy$ Equipment.cmcLEX | References$ X | RandomCopied$ True | RandomNum$ 1 | AttachedTo$ TriggeredCard SVar:X:TriggeredCard$CardManaCost SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/stonehewer_giant_avatar.jpg Oracle:Hand +1, life -5\nWhenever a creature enters the battlefield under your control, put a token onto the battlefield that's a copy of a random Equipment card with converted mana cost less than or equal to that creature's converted mana cost. Attach that Equipment to that creature. From 25388ecda88ee0b80ecbb612c59456c0fc3e037c Mon Sep 17 00:00:00 2001 From: swordshine Date: Wed, 3 Apr 2013 08:43:51 +0000 Subject: [PATCH 083/123] - Vanguard: Added Jhoira of the Ghitu Avatar --- .gitattributes | 1 + .../j/jhoira_of_the_ghitu_avatar.txt | 9 ++++ .../card/ability/effects/PlayEffect.java | 44 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 res/cardsfolder/j/jhoira_of_the_ghitu_avatar.txt diff --git a/.gitattributes b/.gitattributes index 0c16bf27fe9..1f990f4c354 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5443,6 +5443,7 @@ res/cardsfolder/j/jhessian_infiltrator.txt svneol=native#text/plain res/cardsfolder/j/jhessian_lookout.txt svneol=native#text/plain res/cardsfolder/j/jhessian_zombies.txt svneol=native#text/plain res/cardsfolder/j/jhoira_of_the_ghitu.txt svneol=native#text/plain +res/cardsfolder/j/jhoira_of_the_ghitu_avatar.txt -text res/cardsfolder/j/jhoiras_timebug.txt -text res/cardsfolder/j/jhoiras_toolbox.txt svneol=native#text/plain res/cardsfolder/j/jhovall_queen.txt svneol=native#text/plain diff --git a/res/cardsfolder/j/jhoira_of_the_ghitu_avatar.txt b/res/cardsfolder/j/jhoira_of_the_ghitu_avatar.txt new file mode 100644 index 00000000000..ea5b76077e2 --- /dev/null +++ b/res/cardsfolder/j/jhoira_of_the_ghitu_avatar.txt @@ -0,0 +1,9 @@ +Name:Jhoira of the Ghitu Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+1/+0 +A:AB$ Play | Cost$ 3 Discard<1/Card> | ActivationZone$ Command | AnySupportedCard$ Instant | RandomCopied$ True | RandomNum$ 3 | ChoiceNum$ 1 | CopyCard$ True | WithoutManaCost$ True | SpellDescription$ Copy three instant cards chosen at random. You may cast one of the copies without paying its mana cost. +A:AB$ Play | Cost$ 3 Discard<1/Card> | ActivationZone$ Command | AnySupportedCard$ Sorcery | RandomCopied$ True | RandomNum$ 3 | ChoiceNum$ 1 | CopyCard$ True | WithoutManaCost$ True | SorcerySpeed$ True | SpellDescription$ Copy three sorcery cards chosen at random. You may cast one of the copies without paying its mana cost. Activate this ability only any time you could cast a sorcery. +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/jhoira_of_the_ghitu_avatar.jpg +Oracle:Hand +1, life +0\n{3}, Discard a card: Copy three instant cards chosen at random. You may cast one of the copies without paying its mana cost.\n{3}, Discard a card: Copy three sorcery cards chosen at random. You may cast one of the copies without paying its mana cost. Activate this ability only any time you could cast a sorcery. +SetInfo:VAN Special \ No newline at end of file diff --git a/src/main/java/forge/card/ability/effects/PlayEffect.java b/src/main/java/forge/card/ability/effects/PlayEffect.java index 462dc538810..b4e5471ab17 100644 --- a/src/main/java/forge/card/ability/effects/PlayEffect.java +++ b/src/main/java/forge/card/ability/effects/PlayEffect.java @@ -6,11 +6,15 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import forge.Card; import forge.CardCharacteristicName; import forge.CardLists; import forge.Singletons; +import forge.card.CardRulesPredicates; import forge.card.ability.AbilityUtils; import forge.card.ability.SpellAbilityEffect; import forge.card.spellability.Spell; @@ -26,6 +30,9 @@ import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.GuiDialog; import forge.item.CardDb; +import forge.item.CardPrinted; +import forge.util.Aggregates; +import forge.util.PredicateString.StringOp; public class PlayEffect extends SpellAbilityEffect { @Override @@ -82,6 +89,43 @@ public class PlayEffect extends SpellAbilityEffect { tgtCards.add(encodedCards.get(encodedIndex)); useEncoded = true; } + else if (sa.hasParam("AnySupportedCard")) { + List cards = Lists.newArrayList(CardDb.instance().getUniqueCards()); + String valid = sa.getParam("AnySupportedCard"); + if (StringUtils.containsIgnoreCase(valid, "sorcery")) { + Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_SORCERY, CardPrinted.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + } + if (StringUtils.containsIgnoreCase(valid, "instant")) { + Predicate cpp = Predicates.compose(CardRulesPredicates.Presets.IS_INSTANT, CardPrinted.FN_GET_RULES); + cards = Lists.newArrayList(Iterables.filter(cards, cpp)); + } + if (sa.hasParam("RandomCopied")) { + List copysource = new ArrayList(cards); + List choice = new ArrayList(); + final String num = sa.hasParam("RandomNum") ? sa.getParam("RandomNum") : "1"; + int ncopied = AbilityUtils.calculateAmount(source, num, sa); + while(ncopied > 0) { + final CardPrinted cp = Aggregates.random(copysource); + if (cp.getMatchingForgeCard().isValid(valid, source.getController(), source)) { + choice.add(cp.getMatchingForgeCard()); + copysource.remove(cp); + ncopied -= 1; + } + } + if (sa.hasParam("ChoiceNum")) { + final int choicenum = AbilityUtils.calculateAmount(source, sa.getParam("ChoiceNum"), sa); + List afterchoice = new ArrayList(); + for (int i = 0; i < choicenum; i++) { + afterchoice.add(GuiChoose.oneOrNone(source + "- Choose a Card", choice)); + } + tgtCards = afterchoice; + } else { + tgtCards = choice; + } + + } + } else { tgtCards = getTargetCards(sa); } From 8be07d95f25baa8476c8e8945a57ad2c59d2cf72 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 09:35:09 +0000 Subject: [PATCH 084/123] cleanup of AbilityUtils.calculateAmount - if amount is pure integer, it's returned right away (that means no svars may be named after valid numbers) former parseMath renamed and no longer returns an array of one string (just return the string) --- .../java/forge/card/ability/AbilityUtils.java | 499 +++++++++--------- .../card/cardfactory/CardFactoryUtil.java | 71 +-- src/main/java/forge/card/cost/CostPart.java | 9 +- .../java/forge/card/cost/CostPartMana.java | 11 +- .../spellability/HumanPlaySpellAbility.java | 4 +- .../forge/card/spellability/SpellAbility.java | 17 + .../java/forge/card/spellability/Target.java | 2 +- 7 files changed, 295 insertions(+), 318 deletions(-) diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index eecefd1e829..774226d6520 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -290,250 +290,18 @@ public class AbilityUtils { * @return a int. */ public static int calculateAmount(final Card card, String amount, final SpellAbility ability) { - // amount can be anything, not just 'X' as long as sVar exists + // return empty strings and constants + if (StringUtils.isBlank(amount)) return 0; + if (StringUtils.isNumeric(amount)) return Integer.parseInt(amount); - if (amount == null || amount.isEmpty()) { - return 0; - } - - // If Amount is -X, strip the minus sign before looking for an SVar of - // that kind - int multiplier = 1; - if (amount.startsWith("-")) { - multiplier = -1; + // Strip and save sign for calculations + boolean startsWithPlus = amount.charAt(0) == '+'; + boolean startsWithMinus = amount.charAt(0) == '-'; + int multiplier = startsWithMinus ? -1 : 1; + if(startsWithMinus || startsWithPlus ) amount = amount.substring(1); - } else if (amount.startsWith("+")) { - amount = amount.substring(1); - } - String svarval; - if (ability != null) { - - svarval = ability.getSVar(amount); - if (svarval.equals("")) { - try { - Integer.parseInt(amount); - } - catch (NumberFormatException ignored) { - //If this is reached, amount wasn't an integer - //Print a warning to console to help debug if an ability is not stolen properly. - StringBuilder sb = new StringBuilder("WARNING:SVar fallback to Card ("); - sb.append(card.getName()).append(") and Ability(").append(ability.toString()).append(")"); - System.out.println(sb.toString()); - svarval = card.getSVar(amount); - } - } - } else { - svarval = card.getSVar(amount); - } - - if (!svarval.equals("")) { - final String[] calcX = svarval.split("\\$"); - if ((calcX.length == 1) || calcX[1].equals("none")) { - return 0; - } - - if (calcX[0].startsWith("Count")) { - return AbilityUtils.xCount(card, calcX[1], ability) * multiplier; - } else if (calcX[0].startsWith("Number")) { - return CardFactoryUtil.xCount(card, svarval) * multiplier; - } else if (calcX[0].startsWith("SVar")) { - final String[] l = calcX[1].split("/"); - final String[] m = CardFactoryUtil.parseMath(l); - return CardFactoryUtil.doXMath(AbilityUtils.calculateAmount(card, l[0], ability), m, card) - * multiplier; - } else if (calcX[0].startsWith("PlayerCount")) { - final String hType = calcX[0].substring(11); - final ArrayList players = new ArrayList(); - if (hType.equals("Players") || hType.equals("")) { - players.addAll(Singletons.getModel().getGame().getPlayers()); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } else if (hType.equals("Opponents")) { - players.addAll(card.getController().getOpponents()); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } else if (hType.equals("Other")) { - players.addAll(card.getController().getAllOtherPlayers()); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } else if (hType.equals("Remembered")) { - for (final Object o : card.getRemembered()) { - if (o instanceof Player) { - players.add((Player) o); - } - } - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } else if (hType.equals("NonActive")) { - players.addAll(Singletons.getModel().getGame().getPlayers()); - players.remove(Singletons.getModel().getGame().getPhaseHandler().getPlayerTurn()); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - } else if (calcX[0].startsWith("Remembered")) { - // Add whole Remembered list to handlePaid - final List list = new ArrayList(); - if (card.getRemembered().isEmpty()) { - final Card newCard = Singletons.getModel().getGame().getCardState(card); - for (final Object o : newCard.getRemembered()) { - if (o instanceof Card) { - list.add(Singletons.getModel().getGame().getCardState((Card) o)); - } - } - } - - if (calcX[0].endsWith("LKI")) { // last known information - for (final Object o : card.getRemembered()) { - if (o instanceof Card) { - list.add((Card) o); - } - } - } else { - for (final Object o : card.getRemembered()) { - if (o instanceof Card) { - list.add(Singletons.getModel().getGame().getCardState((Card) o)); - } - } - } - - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; - } else if (calcX[0].startsWith("Imprinted")) { - // Add whole Imprinted list to handlePaid - final List list = new ArrayList(); - for (final Card c : card.getImprinted()) { - list.add(Singletons.getModel().getGame().getCardState(c)); - } - - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; - } else if (calcX[0].matches("Enchanted")) { - // Add whole Enchanted list to handlePaid - final List list = new ArrayList(); - if (card.isEnchanting()) { - Object o = card.getEnchanting(); - if (o instanceof Card) { - list.add(Singletons.getModel().getGame().getCardState((Card) o)); - } - } - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; - } else if (ability != null) { - // Player attribute counting - if (calcX[0].startsWith("TargetedPlayer")) { - final ArrayList players = new ArrayList(); - final SpellAbility saTargeting = ability.getSATargetingPlayer(); - if (null != saTargeting) { - players.addAll(saTargeting.getTarget().getTargetPlayers()); - } - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TargetedObjects")) { - final ArrayList objects = new ArrayList(); - // Make list of all targeted objects starting with the root SpellAbility - SpellAbility loopSA = ability.getRootAbility(); - while (loopSA != null) { - if (loopSA.getTarget() != null) { - objects.addAll(loopSA.getTarget().getTargets()); - } - loopSA = loopSA.getSubAbility(); - } - return CardFactoryUtil.objectXCount(objects, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TargetedController")) { - final ArrayList players = new ArrayList(); - final List list = getDefinedCards(card, "Targeted", ability); - final List sas = AbilityUtils.getDefinedSpellAbilities(card, "Targeted", - ability); - - for (final Card c : list) { - final Player p = c.getController(); - if (!players.contains(p)) { - players.add(p); - } - } - for (final SpellAbility s : sas) { - final Player p = s.getSourceCard().getController(); - if (!players.contains(p)) { - players.add(p); - } - } - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TargetedByTarget")) { - final List tgtList = new ArrayList(); - final List saList = getDefinedSpellAbilities(card, "Targeted", ability); - - for (final SpellAbility s : saList) { - tgtList.addAll(getDefinedCards(s.getSourceCard(), "Targeted", s)); - } - return CardFactoryUtil.handlePaid(tgtList, calcX[1], card) * multiplier; - } - if (calcX[0].startsWith("TriggeredPlayer") || calcX[0].startsWith("TriggeredTarget")) { - final SpellAbility root = ability.getRootAbility(); - Object o = root.getTriggeringObject(calcX[0].substring(9)); - final List players = new ArrayList(); - if (o instanceof Player) { - players.add((Player) o); - } - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - // Added on 9/30/12 (ArsenalNut) - Ended up not using but might be useful in future - /* - if (calcX[0].startsWith("EnchantedController")) { - final ArrayList players = new ArrayList(); - players.addAll(AbilityFactory.getDefinedPlayers(card, "EnchantedController", ability)); - return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; - } - */ - - List list = new ArrayList(); - if (calcX[0].startsWith("Sacrificed")) { - list = ability.getRootAbility().getPaidList("Sacrificed"); - } else if (calcX[0].startsWith("Discarded")) { - final SpellAbility root = ability.getRootAbility(); - list = root.getPaidList("Discarded"); - if ((null == list) && root.isTrigger()) { - list = root.getSourceCard().getSpellPermanent().getPaidList("Discarded"); - } - } else if (calcX[0].startsWith("Exiled")) { - list = ability.getRootAbility().getPaidList("Exiled"); - } else if (calcX[0].startsWith("Tapped")) { - list = ability.getRootAbility().getPaidList("Tapped"); - } else if (calcX[0].startsWith("Revealed")) { - list = ability.getRootAbility().getPaidList("Revealed"); - } else if (calcX[0].startsWith("Targeted")) { - list = ability.findTargetedCards(); - } else if (calcX[0].startsWith("Triggered")) { - final SpellAbility root = ability.getRootAbility(); - list = new ArrayList(); - list.add((Card) root.getTriggeringObject(calcX[0].substring(9))); - } else if (calcX[0].startsWith("TriggerCount")) { - // TriggerCount is similar to a regular Count, but just - // pulls Integer Values from Trigger objects - final SpellAbility root = ability.getRootAbility(); - final String[] l = calcX[1].split("/"); - final String[] m = CardFactoryUtil.parseMath(l); - final int count = (Integer) root.getTriggeringObject(l[0]); - - return CardFactoryUtil.doXMath(count, m, card) * multiplier; - } else if (calcX[0].startsWith("Replaced")) { - final SpellAbility root = ability.getRootAbility(); - list = new ArrayList(); - list.add((Card) root.getReplacingObject(calcX[0].substring(8))); - } else if (calcX[0].startsWith("ReplaceCount")) { - // ReplaceCount is similar to a regular Count, but just - // pulls Integer Values from Replacement objects - final SpellAbility root = ability.getRootAbility(); - final String[] l = calcX[1].split("/"); - final String[] m = CardFactoryUtil.parseMath(l); - final int count = (Integer) root.getReplacingObject(l[0]); - - return CardFactoryUtil.doXMath(count, m, card) * multiplier; - } else { - - return 0; - } - - return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; - - } else { - return 0; - } - } + // These are some special cases - who is implementing them? if (amount.equals("ChosenX") || amount.equals("ChosenY")) { // isn't made yet return 0; @@ -543,7 +311,244 @@ public class AbilityUtils { return 0; } - return Integer.parseInt(amount) * multiplier; + // Try to fetch variable, try ability first, then card. + String svarval = null; + if (ability != null) { + svarval = ability.getSVar(amount); + } + if (StringUtils.isBlank(svarval)) { + if( ability != null) { + System.err.printf("SVar '%s' not found in ability, fallback to Card (%s). Ability is (%s)%n", amount, card.getName(), ability); + } + svarval = card.getSVar(amount); + } + + // Nothing to do here if value is missing or blank + if (StringUtils.isBlank(svarval)) { + System.err.printf("SVar '%s' not defined in Card (%s)%n", amount, card.getName()); + return 0; + } + + // Handle numeric constant coming in svar value + if( StringUtils.isNumeric(svarval) ) + return multiplier * Integer.parseInt(svarval); + + // Parse Object$Property string + final String[] calcX = svarval.split("\\$"); + + // Incorrect parses mean zero. + if ((calcX.length == 1) || calcX[1].equals("none")) + return 0; + + if (calcX[0].startsWith("Count")) + return AbilityUtils.xCount(card, calcX[1], ability) * multiplier; + + if (calcX[0].startsWith("Number")) + return CardFactoryUtil.xCount(card, svarval) * multiplier; + + if (calcX[0].startsWith("SVar")) { + final String[] l = calcX[1].split("/"); + final String m = CardFactoryUtil.extractOperators(calcX[1]); + return CardFactoryUtil.doXMath(AbilityUtils.calculateAmount(card, l[0], ability), m, card) * multiplier; + } + + if (calcX[0].startsWith("PlayerCount")) { + final String hType = calcX[0].substring(11); + final ArrayList players = new ArrayList(); + if (hType.equals("Players") || hType.equals("")) { + players.addAll(Singletons.getModel().getGame().getPlayers()); + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } else if (hType.equals("Opponents")) { + players.addAll(card.getController().getOpponents()); + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } else if (hType.equals("Other")) { + players.addAll(card.getController().getAllOtherPlayers()); + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } else if (hType.equals("Remembered")) { + for (final Object o : card.getRemembered()) { + if (o instanceof Player) { + players.add((Player) o); + } + } + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } else if (hType.equals("NonActive")) { + players.addAll(Singletons.getModel().getGame().getPlayers()); + players.remove(Singletons.getModel().getGame().getPhaseHandler().getPlayerTurn()); + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } + return 0; + } + + if (calcX[0].startsWith("Remembered")) { + // Add whole Remembered list to handlePaid + final List list = new ArrayList(); + if (card.getRemembered().isEmpty()) { + final Card newCard = Singletons.getModel().getGame().getCardState(card); + for (final Object o : newCard.getRemembered()) { + if (o instanceof Card) { + list.add(Singletons.getModel().getGame().getCardState((Card) o)); + } + } + } + + if (calcX[0].endsWith("LKI")) { // last known information + for (final Object o : card.getRemembered()) { + if (o instanceof Card) { + list.add((Card) o); + } + } + } else { + for (final Object o : card.getRemembered()) { + if (o instanceof Card) { + list.add(Singletons.getModel().getGame().getCardState((Card) o)); + } + } + } + + return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; + } + + if (calcX[0].startsWith("Imprinted")) { + // Add whole Imprinted list to handlePaid + final List list = new ArrayList(); + for (final Card c : card.getImprinted()) { + list.add(Singletons.getModel().getGame().getCardState(c)); + } + + return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; + } + + if (calcX[0].matches("Enchanted")) { + // Add whole Enchanted list to handlePaid + final List list = new ArrayList(); + if (card.isEnchanting()) { + Object o = card.getEnchanting(); + if (o instanceof Card) { + list.add(Singletons.getModel().getGame().getCardState((Card) o)); + } + } + return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; + } + + if (ability == null) + return 0; + + // Player attribute counting + if (calcX[0].startsWith("TargetedPlayer")) { + final ArrayList players = new ArrayList(); + final SpellAbility saTargeting = ability.getSATargetingPlayer(); + if (null != saTargeting) { + players.addAll(saTargeting.getTarget().getTargetPlayers()); + } + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } + if (calcX[0].startsWith("TargetedObjects")) { + final ArrayList objects = new ArrayList(); + // Make list of all targeted objects starting with the root SpellAbility + SpellAbility loopSA = ability.getRootAbility(); + while (loopSA != null) { + if (loopSA.getTarget() != null) { + objects.addAll(loopSA.getTarget().getTargets()); + } + loopSA = loopSA.getSubAbility(); + } + return CardFactoryUtil.objectXCount(objects, calcX[1], card) * multiplier; + } + if (calcX[0].startsWith("TargetedController")) { + final ArrayList players = new ArrayList(); + final List list = getDefinedCards(card, "Targeted", ability); + final List sas = AbilityUtils.getDefinedSpellAbilities(card, "Targeted", + ability); + + for (final Card c : list) { + final Player p = c.getController(); + if (!players.contains(p)) { + players.add(p); + } + } + for (final SpellAbility s : sas) { + final Player p = s.getSourceCard().getController(); + if (!players.contains(p)) { + players.add(p); + } + } + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } + if (calcX[0].startsWith("TargetedByTarget")) { + final List tgtList = new ArrayList(); + final List saList = getDefinedSpellAbilities(card, "Targeted", ability); + + for (final SpellAbility s : saList) { + tgtList.addAll(getDefinedCards(s.getSourceCard(), "Targeted", s)); + } + return CardFactoryUtil.handlePaid(tgtList, calcX[1], card) * multiplier; + } + if (calcX[0].startsWith("TriggeredPlayer") || calcX[0].startsWith("TriggeredTarget")) { + final SpellAbility root = ability.getRootAbility(); + Object o = root.getTriggeringObject(calcX[0].substring(9)); + final List players = new ArrayList(); + if (o instanceof Player) { + players.add((Player) o); + } + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } + // Added on 9/30/12 (ArsenalNut) - Ended up not using but might be useful in future + /* + if (calcX[0].startsWith("EnchantedController")) { + final ArrayList players = new ArrayList(); + players.addAll(AbilityFactory.getDefinedPlayers(card, "EnchantedController", ability)); + return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; + } + */ + + List list = new ArrayList(); + if (calcX[0].startsWith("Sacrificed")) { + list = ability.getRootAbility().getPaidList("Sacrificed"); + } else if (calcX[0].startsWith("Discarded")) { + final SpellAbility root = ability.getRootAbility(); + list = root.getPaidList("Discarded"); + if ((null == list) && root.isTrigger()) { + list = root.getSourceCard().getSpellPermanent().getPaidList("Discarded"); + } + } else if (calcX[0].startsWith("Exiled")) { + list = ability.getRootAbility().getPaidList("Exiled"); + } else if (calcX[0].startsWith("Tapped")) { + list = ability.getRootAbility().getPaidList("Tapped"); + } else if (calcX[0].startsWith("Revealed")) { + list = ability.getRootAbility().getPaidList("Revealed"); + } else if (calcX[0].startsWith("Targeted")) { + list = ability.findTargetedCards(); + } else if (calcX[0].startsWith("Triggered")) { + final SpellAbility root = ability.getRootAbility(); + list = new ArrayList(); + list.add((Card) root.getTriggeringObject(calcX[0].substring(9))); + } else if (calcX[0].startsWith("TriggerCount")) { + // TriggerCount is similar to a regular Count, but just + // pulls Integer Values from Trigger objects + final SpellAbility root = ability.getRootAbility(); + final String[] l = calcX[1].split("/"); + final String m = CardFactoryUtil.extractOperators(calcX[1]); + final int count = (Integer) root.getTriggeringObject(l[0]); + + return CardFactoryUtil.doXMath(count, m, card) * multiplier; + } else if (calcX[0].startsWith("Replaced")) { + final SpellAbility root = ability.getRootAbility(); + list = new ArrayList(); + list.add((Card) root.getReplacingObject(calcX[0].substring(8))); + } else if (calcX[0].startsWith("ReplaceCount")) { + // ReplaceCount is similar to a regular Count, but just + // pulls Integer Values from Replacement objects + final SpellAbility root = ability.getRootAbility(); + final String[] l = calcX[1].split("/"); + final String m = CardFactoryUtil.extractOperators(calcX[1]); + final int count = (Integer) root.getReplacingObject(l[0]); + + return CardFactoryUtil.doXMath(count, m, card) * multiplier; + } else { + return 0; + } + + return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; } /** @@ -1231,7 +1236,7 @@ public class AbilityUtils { public static int xCount(final Card c, final String s, final SpellAbility sa) { final String[] l = s.split("/"); - final String[] m = CardFactoryUtil.parseMath(l); + final String expr = CardFactoryUtil.extractOperators(s); final String[] sq; sq = l[0].split("\\."); @@ -1240,9 +1245,9 @@ public class AbilityUtils { // Count$Kicked.. if (sq[0].startsWith("Kicked")) { if (sa.isKicked()) { - return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), m, c); // Kicked + return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), expr, c); // Kicked } else { - return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), m, c); // not Kicked + return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), expr, c); // not Kicked } } @@ -1252,9 +1257,9 @@ public class AbilityUtils { final int lhs = calculateAmount(c, compString[1], sa); final int rhs = calculateAmount(c, compString[2].substring(2), sa); if (Expressions.compare(lhs, compString[2], rhs)) { - return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), m, c); + return CardFactoryUtil.doXMath(Integer.parseInt(sq[1]), expr, c); } else { - return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), m, c); + return CardFactoryUtil.doXMath(Integer.parseInt(sq[2]), expr, c); } } } diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index 4fd6836501e..98595d32235 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -866,13 +866,9 @@ public class CardFactoryUtil { * an array of {@link java.lang.String} objects. * @return an array of {@link java.lang.String} objects. */ - public static String[] parseMath(final String[] l) { - final String[] m = { "none" }; - if (l.length > 1) { - m[0] = l[1]; - } - - return m; + public static String extractOperators(final String expression) { + String[] l = expression.split("/"); + return l.length > 1 ? l[1] : null; } /** @@ -889,20 +885,12 @@ public class CardFactoryUtil { * @return a int. */ public static int objectXCount(final ArrayList objects, final String s, final Card source) { - if (objects.size() == 0) { + if (objects.isEmpty()) { return 0; } - final String[] l = s.split("/"); - final String[] m = CardFactoryUtil.parseMath(l); - - int n = 0; - - if (s.startsWith("Amount")) { - n = objects.size(); - } - - return CardFactoryUtil.doXMath(n, m, source); + int n = s.startsWith("Amount") ? objects.size() : 0; + return CardFactoryUtil.doXMath(n, CardFactoryUtil.extractOperators(s), source); } /** @@ -924,7 +912,7 @@ public class CardFactoryUtil { } final String[] l = s.split("/"); - final String[] m = CardFactoryUtil.parseMath(l); + final String m = CardFactoryUtil.extractOperators(s); int n = 0; @@ -1115,19 +1103,19 @@ public class CardFactoryUtil { * * @param c * a {@link forge.Card} object. - * @param s + * @param expression * a {@link java.lang.String} object. * @return a int. */ - public static int xCount(final Card c, final String s) { + public static int xCount(final Card c, final String expression) { int n = 0; final Player cardController = c.getController(); final Player oppController = cardController.getOpponent(); final Player activePlayer = Singletons.getModel().getGame().getPhaseHandler().getPlayerTurn(); - final String[] l = s.split("/"); - final String[] m = CardFactoryUtil.parseMath(l); + final String[] l = expression.split("/"); + final String m = CardFactoryUtil.extractOperators(expression); // accept straight numbers if (l[0].startsWith("Number$")) { @@ -2028,12 +2016,12 @@ public class CardFactoryUtil { return CardFactoryUtil.doXMath(n, m, c); } - private static int doXMath(final int num, final String m, final Card c) { - if (m.equals("none")) { + public static int doXMath(final int num, final String operators, final Card c) { + if (operators == null || operators.equals("none")) { return num; } - final String[] s = m.split("\\."); + final String[] s = operators.split("\\."); int secondaryNum = 0; try { @@ -2092,27 +2080,6 @@ public class CardFactoryUtil { } } - /** - *

- * doXMath. - *

- * - * @param num - * a int. - * @param m - * an array of {@link java.lang.String} objects. - * @param c - * a {@link forge.Card} object. - * @return a int. - */ - public static int doXMath(final int num, final String[] m, final Card c) { - if (m.length == 0) { - return num; - } - - return CardFactoryUtil.doXMath(num, m[0], c); - } - /** *

* handlePaid. @@ -2145,17 +2112,9 @@ public class CardFactoryUtil { } if (string.startsWith("Valid")) { - final String[] m = { "none" }; - String valid = string.substring(6); - final String[] l; - l = valid.split("/"); // separate the specification from any math - valid = l[0]; - if (l.length > 1) { - m[0] = l[1]; - } final List list = CardLists.getValidCards(paidList, valid, source.getController(), source); - return CardFactoryUtil.doXMath(list.size(), m, source); + return CardFactoryUtil.doXMath(list.size(), CardFactoryUtil.extractOperators(valid), source); } int tot = 0; diff --git a/src/main/java/forge/card/cost/CostPart.java b/src/main/java/forge/card/cost/CostPart.java index 703c0e0d648..a8c0db3ff8b 100644 --- a/src/main/java/forge/card/cost/CostPart.java +++ b/src/main/java/forge/card/cost/CostPart.java @@ -18,6 +18,8 @@ package forge.card.cost; +import org.apache.commons.lang3.StringUtils; + import forge.Card; import forge.card.spellability.SpellAbility; import forge.game.GameState; @@ -120,12 +122,7 @@ public abstract class CostPart { * @return the integer */ public final Integer convertAmount() { - Integer i = null; - try { - i = Integer.parseInt(this.getAmount()); - } catch (final NumberFormatException e) { - } - return i; + return StringUtils.isNumeric(amount) ? Integer.parseInt(amount) : null; } /** diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index 30314241d91..d962268e0fe 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -209,10 +209,10 @@ public class CostPartMana extends CostPart { // if X cost is a defined value, other than xPaid if (!ability.getSVar("X").equals("Count$xPaid")) { // this currently only works for things about Targeted object - manaToAdd = AbilityUtils.calculateAmount(source, "X", ability) * this.getAmountOfX(); + manaToAdd += AbilityUtils.calculateAmount(source, "X", ability) * this.getAmountOfX(); } } - + if (!"0".equals(this.getManaToPay()) || manaToAdd > 0) { InputPayment inpPayment = new InputPayManaOfCostPayment(game, this, ability, manaToAdd); @@ -221,16 +221,15 @@ public class CostPartMana extends CostPart { return false; } if (this.getAmountOfX() > 0) { - if( !"X".equals(ability.getParam("Announce")) ) { + if( !ability.isAnnouncing("X") ) { source.setXManaCostPaid(0); InputPayment inpPayment = new InputPayManaX(ability, this.getAmountOfX(), this.canXbe0()); FThreads.setInputAndWait(inpPayment); if(!inpPayment.isPaid()) return false; } else { - String xVar = ability.getSVar("X"); - String xVal = xVar.split("\\$")[1]; - source.setXManaCostPaid(Integer.parseInt(xVal)); + int x = AbilityUtils.calculateAmount(source, ability.getSVar("X"), ability); + source.setXManaCostPaid(x); } } return true; diff --git a/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java index 171ce0d12ce..17791afea16 100644 --- a/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java +++ b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java @@ -158,8 +158,8 @@ public class HumanPlaySpellAbility { Integer value = ability.getActivatingPlayer().getController().announceRequirements(ability, aVar, ability.getPayCosts().getCostMana().canXbe0()); if ( null == value ) return false; - ability.setSVar(aVar, "Number$" + value); - ability.getSourceCard().setSVar(aVar, "Number$" + value); + ability.setSVar(aVar, value.toString()); + ability.getSourceCard().setSVar(aVar, value.toString()); } } return true; diff --git a/src/main/java/forge/card/spellability/SpellAbility.java b/src/main/java/forge/card/spellability/SpellAbility.java index 8fcd9d76429..d3e8468d0f2 100644 --- a/src/main/java/forge/card/spellability/SpellAbility.java +++ b/src/main/java/forge/card/spellability/SpellAbility.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.commons.lang3.StringUtils; + import forge.Card; import forge.GameEntity; import forge.Singletons; @@ -35,6 +37,7 @@ import forge.card.mana.Mana; import forge.card.mana.ManaCost; import forge.game.player.AIPlayer; import forge.game.player.Player; +import forge.util.TextUtil; //only SpellAbility can go on the stack //override any methods as needed @@ -1739,6 +1742,20 @@ public abstract class SpellAbility implements ISpellAbility { } } + return false; + } + + /** + * Returns whether variable was present in the announce list. + */ + public boolean isAnnouncing(String variable) { + String announce = getParam("Announce"); + if (StringUtils.isBlank(announce)) return false; + String[] announcedOnes = TextUtil.split(announce, ','); + for(String a : announcedOnes) { + if( a.trim().equalsIgnoreCase(variable)) + return true; + } return false; } } diff --git a/src/main/java/forge/card/spellability/Target.java b/src/main/java/forge/card/spellability/Target.java index 99dc9117bbf..e5300e48c41 100644 --- a/src/main/java/forge/card/spellability/Target.java +++ b/src/main/java/forge/card/spellability/Target.java @@ -281,7 +281,7 @@ public class Target { * * @return the max targets */ - public final String getMaxTargets() { + private final String getMaxTargets() { return this.maxTargets; } From d69dd49bb1090402a98ac0336ccb52ad028a62f4 Mon Sep 17 00:00:00 2001 From: swordshine Date: Wed, 3 Apr 2013 11:01:59 +0000 Subject: [PATCH 085/123] - Simplified some codes and and stack descriptions --- res/cardsfolder/e/eight_and_a_half_tails_avatar.txt | 2 +- src/main/java/forge/card/ability/effects/PlayEffect.java | 1 - src/main/java/forge/card/ability/effects/PumpEffect.java | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/res/cardsfolder/e/eight_and_a_half_tails_avatar.txt b/res/cardsfolder/e/eight_and_a_half_tails_avatar.txt index 5ad0a177728..bc50216bca7 100644 --- a/res/cardsfolder/e/eight_and_a_half_tails_avatar.txt +++ b/res/cardsfolder/e/eight_and_a_half_tails_avatar.txt @@ -2,7 +2,7 @@ Name:Eight-and-a-Half-Tails Avatar ManaCost:no cost Types:Vanguard HandLifeModifier:+2/-3 -A:AB$ Pump | Cost$ 1 | ActivationZone$ Command | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | KW$ Protection from red & Protection from blue & Protection from black & Protection from white & Protection from green | RandomKeyword$ True | NoRepetition$ True | SpellDescription$ Until end of turn, target permanent you control gains protection from a color chosen at random from colors it doesn't have protection from. +A:AB$ Pump | Cost$ 1 | ActivationZone$ Command | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | KW$ Protection from red & Protection from blue & Protection from black & Protection from white & Protection from green | RandomKeyword$ True | NoRepetition$ True | StackDescription$ SpellDescription | SpellDescription$ Until end of turn, target permanent you control gains protection from a color chosen at random from colors it doesn't have protection from. SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/eight_and_a_half_tails_avatar.jpg Oracle:Hand +2, life -3\n{1}: Until end of turn, target permanent you control gains protection from a color chosen at random from colors it doesn't have protection from. SetInfo:VAN Special \ No newline at end of file diff --git a/src/main/java/forge/card/ability/effects/PlayEffect.java b/src/main/java/forge/card/ability/effects/PlayEffect.java index b4e5471ab17..5c401427f06 100644 --- a/src/main/java/forge/card/ability/effects/PlayEffect.java +++ b/src/main/java/forge/card/ability/effects/PlayEffect.java @@ -32,7 +32,6 @@ import forge.gui.GuiDialog; import forge.item.CardDb; import forge.item.CardPrinted; import forge.util.Aggregates; -import forge.util.PredicateString.StringOp; public class PlayEffect extends SpellAbilityEffect { @Override diff --git a/src/main/java/forge/card/ability/effects/PumpEffect.java b/src/main/java/forge/card/ability/effects/PumpEffect.java index ee4ec1ec289..66c5b0756b0 100644 --- a/src/main/java/forge/card/ability/effects/PumpEffect.java +++ b/src/main/java/forge/card/ability/effects/PumpEffect.java @@ -203,9 +203,9 @@ public class PumpEffect extends SpellAbilityEffect { List total = new ArrayList(keywords); if (sa.hasParam("NoRepetition")) { final List tgtCardskws = tgtCards.get(0).getKeyword(); - for (int i = 0; i < tgtCardskws.size(); i++) { - if (total.contains(tgtCardskws.get(i))) { - total.remove(tgtCardskws.get(i)); + for (String kws : tgtCardskws) { + if (total.contains(kws)) { + total.remove(kws); } } } From e1bc7e1ba09a47759d6fbe47a6f142aae4326f12 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 3 Apr 2013 11:09:56 +0000 Subject: [PATCH 086/123] - Added new card names to changes.txt. --- CHANGES.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 7203139aa3c..848fefa6ebd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -78,6 +78,7 @@ Swift Silence Cut the Tethers Delay Runed Halo +Heroism ---------- @@ -87,6 +88,18 @@ New Planes Sea of Sand +-------------------- +New Vanguard Avatars +-------------------- + +Akroma, Angel of Wrath Avatar +Eight-and-a-Half-Tails Avatar +Peacekeeper Avatar +Stonehewer Giant Avatar +Sliver Queen Avatar +Jhoira of the Ghitu Avatar + + ------------ Known Issues ------------ From 8586768dcd40b4328b5229d93d073692af22f83c Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 15:13:31 +0000 Subject: [PATCH 087/123] fixed a bug with incorrect recognition of numbers written as +N (titanic growth was affected) --- src/main/java/forge/card/ability/AbilityUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index 774226d6520..9633d320b5f 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -292,13 +292,15 @@ public class AbilityUtils { public static int calculateAmount(final Card card, String amount, final SpellAbility ability) { // return empty strings and constants if (StringUtils.isBlank(amount)) return 0; + final boolean startsWithPlus = amount.charAt(0) == '+'; + if(startsWithPlus) amount = amount.substring(1); + if (StringUtils.isNumeric(amount)) return Integer.parseInt(amount); // Strip and save sign for calculations - boolean startsWithPlus = amount.charAt(0) == '+'; boolean startsWithMinus = amount.charAt(0) == '-'; int multiplier = startsWithMinus ? -1 : 1; - if(startsWithMinus || startsWithPlus ) + if(startsWithPlus ) amount = amount.substring(1); // These are some special cases - who is implementing them? From 0d00496afe67d833965f45cb6da0133d9bc13bee Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 15:14:00 +0000 Subject: [PATCH 088/123] same as previous --- src/main/java/forge/card/ability/AbilityUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index 9633d320b5f..8733fd7042e 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -300,7 +300,7 @@ public class AbilityUtils { // Strip and save sign for calculations boolean startsWithMinus = amount.charAt(0) == '-'; int multiplier = startsWithMinus ? -1 : 1; - if(startsWithPlus ) + if(startsWithMinus) amount = amount.substring(1); // These are some special cases - who is implementing them? From e7b1a07742e5364dda5c2c8d46d1f13c9dd6235c Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 15:18:52 +0000 Subject: [PATCH 089/123] had to move setActivatingPlayer to an earlier moment because player was needed yet at the point of costs check --- src/main/java/forge/game/ai/ComputerUtil.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index b0dd3b6b420..d11420407fc 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -278,6 +278,7 @@ public class ComputerUtil { * a {@link forge.card.spellability.SpellAbility} object. */ public static final void playNoStack(final AIPlayer ai, final SpellAbility sa, final GameState game) { + sa.setActivatingPlayer(ai); // TODO: We should really restrict what doesn't use the Stack if (ComputerUtilCost.canPayCost(sa, ai)) { final Card source = sa.getSourceCard(); @@ -285,8 +286,6 @@ public class ComputerUtil { sa.setSourceCard(game.getAction().moveToStack(source)); } - sa.setActivatingPlayer(ai); - final Cost cost = sa.getPayCosts(); if (cost == null) { ComputerUtilMana.payManaCost(ai, sa); From 4a5d500f97885b7a7c1b99e747a0680f5770a8d7 Mon Sep 17 00:00:00 2001 From: moomarc Date: Wed, 3 Apr 2013 17:09:39 +0000 Subject: [PATCH 090/123] - Added Blazing Effigy --- .gitattributes | 1 + res/cardsfolder/b/blazing_effigy.txt | 17 +++++++++++++++++ .../card/ability/effects/StoreSVarEffect.java | 6 ++++++ 3 files changed, 24 insertions(+) create mode 100644 res/cardsfolder/b/blazing_effigy.txt diff --git a/.gitattributes b/.gitattributes index 1f990f4c354..e95c148e8ff 100644 --- a/.gitattributes +++ b/.gitattributes @@ -979,6 +979,7 @@ res/cardsfolder/b/blaze.txt svneol=native#text/plain res/cardsfolder/b/blazethorn_scarecrow.txt svneol=native#text/plain res/cardsfolder/b/blazing_archon.txt svneol=native#text/plain res/cardsfolder/b/blazing_blade_askari.txt svneol=native#text/plain +res/cardsfolder/b/blazing_effigy.txt -text res/cardsfolder/b/blazing_salvo.txt -text res/cardsfolder/b/blazing_shoal.txt svneol=native#text/plain res/cardsfolder/b/blazing_specter.txt svneol=native#text/plain diff --git a/res/cardsfolder/b/blazing_effigy.txt b/res/cardsfolder/b/blazing_effigy.txt new file mode 100644 index 00000000000..35e9602f6c2 --- /dev/null +++ b/res/cardsfolder/b/blazing_effigy.txt @@ -0,0 +1,17 @@ +Name:Blazing Effigy +ManaCost:1 R +Types:Creature Elemental +PT:0/3 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ BlazeDmg | TriggerDescription$ When CARDNAME dies, it deals X damage to target creature, where X is 3 plus the amount of damage dealt to CARDNAME this turn by other sources named Blazing Effigy. +SVar:BlazeDmg:DB$ DealDamage | ValidTgts$ Creature | TgtPrompt$ Select target creature to deal damage to | NumDmg$ BlazeSize | References$ BlazeSize,Contributions | SubAbility$ TrigReset +T:Mode$ DamageDone | ValidSource$ Card.Other+namedBlazing Effigy | ValidTarget$ Card.Self | Execute$ StoreContribution | Static$ True +SVar:StoreContribution:DB$ StoreSVar | SVar$ Contributions | Type$ CountSVar | Expression$ Contributions/Plus.Blazed +T:Mode$ Phase | Phase$ Cleanup | Execute$ TrigReset | Static$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Ante,Library,Hand,Exile | ValidCard$ Card.Self | Execute$ TrigReset | Static$ True +SVar:TrigReset:DB$ StoreSVar | SVar$ Contributions | Type$ Number | Expression$ 0 +SVar:BlazeSize:SVar$Contributions/Plus.3 +SVar:Contributions:Number$0 +SVar:Blazed:TriggerCount$DamageAmount +SVar:Picture:http://www.wizards.com/global/images/magic/general/blazing_effigy.jpg +Oracle:When Blazing Effigy dies, it deals X damage to target creature, where X is 3 plus the amount of damage dealt to Blazing Effigy this turn by other sources named Blazing Effigy. +SetInfo:LEG Common \ No newline at end of file diff --git a/src/main/java/forge/card/ability/effects/StoreSVarEffect.java b/src/main/java/forge/card/ability/effects/StoreSVarEffect.java index 8a4f832bf3d..bedc3520e03 100644 --- a/src/main/java/forge/card/ability/effects/StoreSVarEffect.java +++ b/src/main/java/forge/card/ability/effects/StoreSVarEffect.java @@ -1,6 +1,7 @@ package forge.card.ability.effects; import forge.Card; +import forge.card.ability.AbilityUtils; import forge.card.ability.SpellAbilityEffect; import forge.card.cardfactory.CardFactoryUtil; import forge.card.spellability.SpellAbility; @@ -42,6 +43,11 @@ public class StoreSVarEffect extends SpellAbilityEffect { value = Integer.valueOf(expr); } else if (type.equals("CountSVar")) { + if (expr.contains("/")) { + final String exprMathVar = expr.split("\\/")[1].split("\\.")[1]; + int exprMath = AbilityUtils.calculateAmount(source, exprMathVar, sa); + expr = expr.replace(exprMathVar, Integer.toString(exprMath)); + } value = CardFactoryUtil.xCount(source, "SVar$" + expr); } else if (type.equals("Targeted")) { From 7d3ee5635a185f9135c526e057dd501b9b64521e Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 17:57:01 +0000 Subject: [PATCH 091/123] CostPartMana now contains a ManaCost instance, no more strings --- src/main/java/forge/card/cost/Cost.java | 16 ++-- .../java/forge/card/cost/CostPartMana.java | 89 +++++-------------- .../java/forge/card/cost/CostPayment.java | 3 +- src/main/java/forge/card/cost/CostUtil.java | 32 +++---- .../forge/card/mana/ManaCostBeingPaid.java | 65 +++++++++++--- 5 files changed, 95 insertions(+), 110 deletions(-) diff --git a/src/main/java/forge/card/cost/Cost.java b/src/main/java/forge/card/cost/Cost.java index 016ea4e9982..66ce7a4caa9 100644 --- a/src/main/java/forge/card/cost/Cost.java +++ b/src/main/java/forge/card/cost/Cost.java @@ -140,8 +140,7 @@ public class Cost { this.name = card != null ? card.getName() : ""; boolean xCantBe0 = false; - int amountX = 0; - + StringBuilder manaParts = new StringBuilder(); String[] parts = TextUtil.splitWithParenthesis(parse, ' ', '<', '>'); @@ -156,8 +155,6 @@ public class Cost { for(String part : parts) { if( "XCantBe0".equals(part) ) xCantBe0 = true; - else if ( "X".equals(part) ) - amountX++; else { CostPart cp = parseCostPart(part, tapCost, untapCost); if ( null != cp ) @@ -168,11 +165,10 @@ public class Cost { } - if ((amountX > 0) || manaParts.length() > 0) { - this.costParts.add(0, new CostPartMana(manaParts.toString(), amountX, xCantBe0)); + if (manaParts.length() > 0) { + this.costParts.add(0, new CostPartMana(new ManaCost(new ManaCostParser(manaParts.toString())), xCantBe0)); } - - + // inspect parts to set Sac, {T} and {Q} flags for (int iCp = 0; iCp < costParts.size(); iCp++) { CostPart cp = costParts.get(iCp); @@ -368,7 +364,7 @@ public class Cost { final ManaCostBeingPaid changedCost = new ManaCostBeingPaid(mana); changedCost.applySpellCostChange(sa); - ((CostPartMana)part).setAdjustedMana(changedCost.toString(false)); + ((CostPartMana)part).setAdjustedMana(changedCost.toManaCost()); costChanged = true; } } @@ -376,7 +372,7 @@ public class Cost { // Spells with a cost of 0 should be affected too final ManaCostBeingPaid changedCost = new ManaCostBeingPaid("0"); changedCost.applySpellCostChange(sa); - this.costParts.add(new CostPartMana(changedCost.toString(), 0, false)); + this.costParts.add(new CostPartMana(changedCost.toManaCost(), false)); } } diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index d962268e0fe..63bde67ed62 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -17,11 +17,11 @@ */ package forge.card.cost; -import com.google.common.base.Strings; - import forge.Card; import forge.FThreads; import forge.card.ability.AbilityUtils; +import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostShard; import forge.card.spellability.SpellAbility; import forge.control.input.InputPayManaOfCostPayment; import forge.control.input.InputPayManaX; @@ -35,9 +35,8 @@ import forge.game.player.AIPlayer; */ public class CostPartMana extends CostPart { // "Leftover" - private String mana = ""; - private int amountX = 0; - private String adjustedMana = ""; + private final ManaCost cost; + private ManaCost adjustedCost; private boolean xCantBe0 = false; /** @@ -45,19 +44,9 @@ public class CostPartMana extends CostPart { * * @return the mana */ - public final String getMana() { + public final ManaCost getMana() { // Only used for Human to pay for non-X cost first - return this.mana; - } - - /** - * Sets the mana. - * - * @param sCost - * the new mana - */ - public final void setMana(final String sCost) { - this.mana = sCost; + return this.cost; } /** @@ -66,7 +55,7 @@ public class CostPartMana extends CostPart { * @return true, if successful */ public final boolean hasNoXManaCost() { - return this.amountX == 0; + return getAmountOfX() == 0; } /** @@ -75,36 +64,7 @@ public class CostPartMana extends CostPart { * @return the x mana */ public final int getAmountOfX() { - return this.amountX; - } - - /** - * Sets the x mana. - * - * @param xCost - * the new x mana - */ - public final void setAmountOfX(final int xCost) { - this.amountX = xCost; - } - - /** - * Gets the adjusted mana. - * - * @return the adjusted mana - */ - public final String getAdjustedMana() { - return this.adjustedMana; - } - - /** - * Sets the adjusted mana. - * - * @param adjustedMana - * the new adjusted mana - */ - public final void setAdjustedMana(final String adjustedMana) { - this.adjustedMana = adjustedMana; + return this.cost.getShardCount(ManaCostShard.X); } /** @@ -126,13 +86,13 @@ public class CostPartMana extends CostPart { * * @return the mana to pay */ - public final String getManaToPay() { + public final ManaCost getManaToPay() { // Only used for Human to pay for non-X cost first - if (!this.adjustedMana.equals("")) { - return this.adjustedMana; + if (this.adjustedCost != null ) { + return this.adjustedCost; } - return this.mana; + return this.cost; } @Override @@ -150,9 +110,8 @@ public class CostPartMana extends CostPart { * the amount * @param xCantBe0 TODO */ - public CostPartMana(final String mana, final int amount, boolean xCantBe0) { - this.mana = mana.trim(); - this.amountX = amount; + public CostPartMana(final ManaCost cost, boolean xCantBe0) { + this.cost = cost; this.setxCantBe0(xCantBe0); } @@ -163,13 +122,7 @@ public class CostPartMana extends CostPart { */ @Override public final String toString() { - final StringBuilder sb = new StringBuilder(); - - sb.append(Strings.repeat("X ", this.amountX)); - if ( sb.length() == 0 || mana != "0" ) - sb.append(this.mana); - - return sb.toString().trim(); + return cost.toString(); } /* @@ -228,7 +181,7 @@ public class CostPartMana extends CostPart { if(!inpPayment.isPaid()) return false; } else { - int x = AbilityUtils.calculateAmount(source, ability.getSVar("X"), ability); + int x = AbilityUtils.calculateAmount(source, "X", ability); source.setXManaCostPaid(x); } } @@ -244,6 +197,12 @@ public class CostPartMana extends CostPart { return new PaymentDecision(0); } - // Inputs - + /** + * TODO: Write javadoc for this method. + * @param manaCost + */ + public void setAdjustedMana(ManaCost manaCost) { + // this is set when static effects of LodeStone Golems or Thalias are applied + adjustedCost = manaCost; + } } diff --git a/src/main/java/forge/card/cost/CostPayment.java b/src/main/java/forge/card/cost/CostPayment.java index d48047ff435..706440f03e4 100644 --- a/src/main/java/forge/card/cost/CostPayment.java +++ b/src/main/java/forge/card/cost/CostPayment.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import forge.Card; +import forge.card.mana.ManaCost; import forge.card.spellability.SpellAbility; import forge.game.GameState; import forge.game.player.AIPlayer; @@ -179,7 +180,7 @@ public class CostPayment { final List parts = this.cost.getCostParts(); if (this.getCost().getCostMana() == null) { - parts.add(new CostPartMana("0", 0, false)); + parts.add(new CostPartMana(ManaCost.ZERO, false)); } Map, PaymentDecision> decisions = new HashMap, PaymentDecision>(); diff --git a/src/main/java/forge/card/cost/CostUtil.java b/src/main/java/forge/card/cost/CostUtil.java index e71ed8799ac..ae8ee6f676e 100644 --- a/src/main/java/forge/card/cost/CostUtil.java +++ b/src/main/java/forge/card/cost/CostUtil.java @@ -126,31 +126,19 @@ public class CostUtil { } public static Cost combineCosts(Cost cost1, Cost cost2) { - if (cost1 == null) { - if (cost2 == null) { - return null; - } else { - return cost2; - } - } - - if (cost2 == null) { - return cost1; - } + if (cost1 == null) return cost2; + if (cost2 == null) return cost1; + CostPartMana costPart2 = cost2.getCostMana(); for (final CostPart part : cost1.getCostParts()) { - if (!(part instanceof CostPartMana)) { + if (part instanceof CostPartMana && costPart2 != null) { + ManaCostBeingPaid oldManaCost = new ManaCostBeingPaid(((CostPartMana) part).getMana()); + boolean xCanBe0 = ((CostPartMana) part).canXbe0() && costPart2.canXbe0(); + oldManaCost.combineManaCost(costPart2.getMana()); + + cost2.getCostParts().add(0, new CostPartMana(oldManaCost.toManaCost(), !xCanBe0)); + } else { cost2.getCostParts().add(part); - } else { - CostPartMana newCostMana = cost2.getCostMana(); - if (newCostMana != null) { - ManaCostBeingPaid oldManaCost = new ManaCostBeingPaid(part.toString()); - newCostMana.setAmountOfX(oldManaCost.getXcounter() + newCostMana.getAmountOfX()); - oldManaCost.combineManaCost(newCostMana.toString()); - newCostMana.setMana(oldManaCost.toString(false)); - } else { - cost2.getCostParts().add(0, part); - } } } return cost2; diff --git a/src/main/java/forge/card/mana/ManaCostBeingPaid.java b/src/main/java/forge/card/mana/ManaCostBeingPaid.java index dd4ebeb4803..839d1eadc4a 100644 --- a/src/main/java/forge/card/mana/ManaCostBeingPaid.java +++ b/src/main/java/forge/card/mana/ManaCostBeingPaid.java @@ -19,6 +19,7 @@ package forge.card.mana; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map.Entry; @@ -47,6 +48,47 @@ import forge.util.MyRandom; * @version $Id$ */ public class ManaCostBeingPaid { + private class ManaCostBeingPaidIterator implements IParserManaCost { + private Iterator mch; + private ManaCostShard nextShard; + private int remainingShards = 0; + + public ManaCostBeingPaidIterator() { + mch = unpaidShards.keySet().iterator(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + @Override + public ManaCostShard next() { + if (remainingShards == 0) + throw new UnsupportedOperationException("All shards were depleted, call hasNext()"); + remainingShards--; + return nextShard; + } + + @Override + public boolean hasNext() { + if ( remainingShards > 0 ) return true; + if ( !mch.hasNext() ) return false; + + nextShard = mch.next(); + if ( nextShard == ManaCostShard.COLORLESS ) + return this.hasNext(); // skip colorless + remainingShards = unpaidShards.get(nextShard); + + return true; + } + + @Override + public int getTotalColorlessCost() { + return unpaidShards.get(ManaCostShard.COLORLESS); + } + } + // holds Mana_Part objects // ManaPartColor is stored before ManaPartColorless private final HashMap unpaidShards = new HashMap(); @@ -528,24 +570,19 @@ public class ManaCostBeingPaid { return true; } - /** - *

- * combineManaCost. - *

- * - * @param extra - * a {@link java.lang.String} object. - */ - public final void combineManaCost(final String extra) { - final ManaCost manaCost = new ManaCost(new ManaCostParser(extra)); - for (ManaCostShard shard : manaCost.getShards()) { + public final void combineManaCost(final ManaCost extra) { + for (ManaCostShard shard : extra.getShards()) { if (shard == ManaCostShard.X) { cntX++; } else { increaseShard(shard, 1); } } - increaseColorlessMana(manaCost.getGenericCost()); + increaseColorlessMana(extra.getGenericCost()); + } + + public final void combineManaCost(final String extra) { + combineManaCost(new ManaCost(new ManaCostParser(extra))); } /** @@ -609,6 +646,10 @@ public class ManaCostBeingPaid { } return cmc; } + + public ManaCost toManaCost() { + return new ManaCost(new ManaCostBeingPaidIterator()); + } /** *

From a787b9a0758abef081709dbe3481647646e6548c Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 18:16:18 +0000 Subject: [PATCH 092/123] CostPartMana - rearrange and cleanup, InputPayManaOfCost now takes ManaCostBeingPaid as parameter AbilityUtils - workaroud about StringUtils.isNumeric that returned false for negative numbers --- .../java/forge/card/ability/AbilityUtils.java | 5 +- .../java/forge/card/cost/CostPartMana.java | 120 ++++++------------ .../forge/card/mana/ManaCostBeingPaid.java | 7 - .../forge/control/input/InputPayManaBase.java | 2 +- .../input/InputPayManaOfCostPayment.java | 6 +- 5 files changed, 46 insertions(+), 94 deletions(-) diff --git a/src/main/java/forge/card/ability/AbilityUtils.java b/src/main/java/forge/card/ability/AbilityUtils.java index 8733fd7042e..95df7a8d2d3 100644 --- a/src/main/java/forge/card/ability/AbilityUtils.java +++ b/src/main/java/forge/card/ability/AbilityUtils.java @@ -294,8 +294,6 @@ public class AbilityUtils { if (StringUtils.isBlank(amount)) return 0; final boolean startsWithPlus = amount.charAt(0) == '+'; if(startsWithPlus) amount = amount.substring(1); - - if (StringUtils.isNumeric(amount)) return Integer.parseInt(amount); // Strip and save sign for calculations boolean startsWithMinus = amount.charAt(0) == '-'; @@ -303,6 +301,9 @@ public class AbilityUtils { if(startsWithMinus) amount = amount.substring(1); + // return result soon for plain numbers + if (StringUtils.isNumeric(amount)) return Integer.parseInt(amount) * multiplier; + // These are some special cases - who is implementing them? if (amount.equals("ChosenX") || amount.equals("ChosenY")) { // isn't made yet diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index 63bde67ed62..b90fa4696e6 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -21,6 +21,7 @@ import forge.Card; import forge.FThreads; import forge.card.ability.AbilityUtils; import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostBeingPaid; import forge.card.mana.ManaCostShard; import forge.card.spellability.SpellAbility; import forge.control.input.InputPayManaOfCostPayment; @@ -39,6 +40,20 @@ public class CostPartMana extends CostPart { private ManaCost adjustedCost; private boolean xCantBe0 = false; + /** + * Instantiates a new cost mana. + * + * @param mana + * the mana + * @param amount + * the amount + * @param xCantBe0 TODO + */ + public CostPartMana(final ManaCost cost, boolean xCantBe0) { + this.cost = cost; + this.xCantBe0 = xCantBe0; // TODO: Add 0 to parameter's name. + } + /** * Gets the mana. * @@ -49,20 +64,6 @@ public class CostPartMana extends CostPart { return this.cost; } - /** - * Checks for no x mana cost. - * - * @return true, if successful - */ - public final boolean hasNoXManaCost() { - return getAmountOfX() == 0; - } - - /** - * Gets the x mana. - * - * @return the x mana - */ public final int getAmountOfX() { return this.cost.getShardCount(ManaCostShard.X); } @@ -75,10 +76,11 @@ public class CostPartMana extends CostPart { } /** - * @param xCantBe00 the xCantBe0 to set + * Used to set mana cost after applying static effects that change costs. */ - public void setxCantBe0(boolean xCantBe0) { - this.xCantBe0 = xCantBe0; // TODO: Add 0 to parameter's name. + public void setAdjustedMana(ManaCost manaCost) { + // this is set when static effects of LodeStone Golems or Thalias are applied + adjustedCost = manaCost; } /** @@ -87,12 +89,7 @@ public class CostPartMana extends CostPart { * @return the mana to pay */ public final ManaCost getManaToPay() { - // Only used for Human to pay for non-X cost first - if (this.adjustedCost != null ) { - return this.adjustedCost; - } - - return this.cost; + return adjustedCost == null ? cost : adjustedCost; } @Override @@ -101,37 +98,13 @@ public class CostPartMana extends CostPart { @Override public boolean isUndoable() { return true; } - /** - * Instantiates a new cost mana. - * - * @param mana - * the mana - * @param amount - * the amount - * @param xCantBe0 TODO - */ - public CostPartMana(final ManaCost cost, boolean xCantBe0) { - this.cost = cost; - this.setxCantBe0(xCantBe0); - } - /* - * (non-Javadoc) - * - * @see forge.card.cost.CostPart#toString() - */ @Override public final String toString() { return cost.toString(); } - /* - * (non-Javadoc) - * - * @see - * forge.card.cost.CostPart#canPay(forge.card.spellability.SpellAbility, - * forge.Card, forge.Player, forge.card.cost.Cost) - */ + @Override public final boolean canPay(final SpellAbility ability) { // For now, this will always return true. But this should probably be @@ -139,36 +112,23 @@ public class CostPartMana extends CostPart { return true; } - /* (non-Javadoc) - * @see forge.card.cost.CostPart#payAI(forge.card.cost.PaymentDecision, forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card) - */ - @Override - public void payAI(PaymentDecision decision, AIPlayer ai, SpellAbility ability, Card source) { - ComputerUtilMana.payManaCost(ai, ability); - } - - /* - * (non-Javadoc) - * - * @see - * forge.card.cost.CostPart#payHuman(forge.card.spellability.SpellAbility, - * forge.Card, forge.card.cost.Cost_Payment) - */ @Override public final boolean payHuman(final SpellAbility ability, final GameState game) { final Card source = ability.getSourceCard(); - int manaToAdd = 0; - if (!this.hasNoXManaCost()) { + ManaCostBeingPaid toPay = new ManaCostBeingPaid(getManaToPay()); + + if (this.getAmountOfX() > 0) { // if X cost is a defined value, other than xPaid if (!ability.getSVar("X").equals("Count$xPaid")) { // this currently only works for things about Targeted object - manaToAdd += AbilityUtils.calculateAmount(source, "X", ability) * this.getAmountOfX(); + int xCost = AbilityUtils.calculateAmount(source, "X", ability) * this.getAmountOfX(); + toPay.increaseColorlessMana(xCost); } } - - - if (!"0".equals(this.getManaToPay()) || manaToAdd > 0) { - InputPayment inpPayment = new InputPayManaOfCostPayment(game, this, ability, manaToAdd); + + + if (!toPay.isPaid()) { + InputPayment inpPayment = new InputPayManaOfCostPayment(game, toPay, ability); FThreads.setInputAndWait(inpPayment); if(!inpPayment.isPaid()) return false; @@ -186,9 +146,18 @@ public class CostPartMana extends CostPart { } } return true; - + } + /* (non-Javadoc) + * @see forge.card.cost.CostPart#payAI(forge.card.cost.PaymentDecision, forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card) + */ + @Override + public void payAI(PaymentDecision decision, AIPlayer ai, SpellAbility ability, Card source) { + ComputerUtilMana.payManaCost(ai, ability); + } + + /* (non-Javadoc) * @see forge.card.cost.CostPart#decideAIPayment(forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card) */ @@ -196,13 +165,4 @@ public class CostPartMana extends CostPart { public PaymentDecision decideAIPayment(AIPlayer ai, SpellAbility ability, Card source) { return new PaymentDecision(0); } - - /** - * TODO: Write javadoc for this method. - * @param manaCost - */ - public void setAdjustedMana(ManaCost manaCost) { - // this is set when static effects of LodeStone Golems or Thalias are applied - adjustedCost = manaCost; - } } diff --git a/src/main/java/forge/card/mana/ManaCostBeingPaid.java b/src/main/java/forge/card/mana/ManaCostBeingPaid.java index 839d1eadc4a..cf68569afcd 100644 --- a/src/main/java/forge/card/mana/ManaCostBeingPaid.java +++ b/src/main/java/forge/card/mana/ManaCostBeingPaid.java @@ -97,8 +97,6 @@ public class ManaCostBeingPaid { private final ArrayList manaNeededToAvoidNegativeEffect = new ArrayList(); private final ArrayList manaPaidToAvoidNegativeEffect = new ArrayList(); - private final ManaCost originalCost; - // manaCost can be like "0", "3", "G", "GW", "10", "3 GW", "10 GW" // or "split hybrid mana" like "2/G 2/G", "2/B 2/B 2/B" // "GW" can be paid with either G or W @@ -116,7 +114,6 @@ public class ManaCostBeingPaid { } public ManaCostBeingPaid(ManaCost manaCost) { - originalCost = manaCost; if ( null == manaCost ) return; @@ -703,10 +700,6 @@ public class ManaCostBeingPaid { return this.manaPaidToAvoidNegativeEffect; } - public ManaCost getStartingCost() { - return originalCost; - } - public final void applySpellCostChange(final SpellAbility sa) { final GameState game = sa.getActivatingPlayer().getGame(); // Beached diff --git a/src/main/java/forge/control/input/InputPayManaBase.java b/src/main/java/forge/control/input/InputPayManaBase.java index 1e633147e52..f70fe3a52b8 100644 --- a/src/main/java/forge/control/input/InputPayManaBase.java +++ b/src/main/java/forge/control/input/InputPayManaBase.java @@ -320,7 +320,7 @@ public abstract class InputPayManaBase extends InputSyncronizedBase implements I @Override public String toString() { - return String.format("PayManaBase %s (out of %s)", manaCost.toString(), manaCost.getStartingCost() ); + return String.format("PayManaBase %s left", manaCost.toString() ); } protected void handleConvokedCards(boolean isCancelled) { diff --git a/src/main/java/forge/control/input/InputPayManaOfCostPayment.java b/src/main/java/forge/control/input/InputPayManaOfCostPayment.java index 8812c1f3cf6..b3f8cbaddc7 100644 --- a/src/main/java/forge/control/input/InputPayManaOfCostPayment.java +++ b/src/main/java/forge/control/input/InputPayManaOfCostPayment.java @@ -2,7 +2,6 @@ package forge.control.input; import forge.Card; import forge.Singletons; -import forge.card.cost.CostPartMana; import forge.card.mana.ManaCostBeingPaid; import forge.card.spellability.SpellAbility; import forge.game.GameState; @@ -11,10 +10,9 @@ import forge.view.ButtonUtil; public class InputPayManaOfCostPayment extends InputPayManaBase { - public InputPayManaOfCostPayment(final GameState game, CostPartMana costMana, SpellAbility spellAbility, int toAdd) { + public InputPayManaOfCostPayment(final GameState game, ManaCostBeingPaid cost, SpellAbility spellAbility) { super(spellAbility); - manaCost = new ManaCostBeingPaid(costMana.getManaToPay()); - manaCost.increaseColorlessMana(toAdd); + manaCost = cost; } private static final long serialVersionUID = 3467312982164195091L; From e7365a8068e7a3ca8df0b87fe7168e0d7330a424 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 18:54:11 +0000 Subject: [PATCH 093/123] bugfix, CostPartMana tried to take color of X into account --- src/main/java/forge/card/cost/CostPartMana.java | 13 +++++++------ src/main/java/forge/card/mana/ManaCost.java | 6 +----- .../java/forge/card/mana/ManaCostBeingPaid.java | 3 ++- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index b90fa4696e6..e6d5717ee8d 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -19,6 +19,7 @@ package forge.card.cost; import forge.Card; import forge.FThreads; +import forge.card.MagicColor; import forge.card.ability.AbilityUtils; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostBeingPaid; @@ -117,13 +118,13 @@ public class CostPartMana extends CostPart { final Card source = ability.getSourceCard(); ManaCostBeingPaid toPay = new ManaCostBeingPaid(getManaToPay()); - if (this.getAmountOfX() > 0) { + if (this.getAmountOfX() > 0 && !ability.getSVar("X").equals("Count$xPaid")) { // if X cost is a defined value, other than xPaid - if (!ability.getSVar("X").equals("Count$xPaid")) { - // this currently only works for things about Targeted object - int xCost = AbilityUtils.calculateAmount(source, "X", ability) * this.getAmountOfX(); - toPay.increaseColorlessMana(xCost); - } + + // this currently only works for things about Targeted object + int xCost = AbilityUtils.calculateAmount(source, "X", ability) * this.getAmountOfX(); + byte xColor = MagicColor.fromName(ability.hasParam("XColor") ? ability.getParam("XColor") : "1"); + toPay.increaseShard(ManaCostShard.valueOf(xColor), xCost); } diff --git a/src/main/java/forge/card/mana/ManaCost.java b/src/main/java/forge/card/mana/ManaCost.java index cf3f7d7e695..d591db9b9f4 100644 --- a/src/main/java/forge/card/mana/ManaCost.java +++ b/src/main/java/forge/card/mana/ManaCost.java @@ -79,9 +79,6 @@ public final class ManaCost implements Comparable { * the parser */ public ManaCost(final IParserManaCost parser) { - if (!parser.hasNext()) { - throw new RuntimeException("Empty manacost passed to parser (this should have been handled before)"); - } final List shardsTemp = new ArrayList(); this.hasNoCost = false; while (parser.hasNext()) { @@ -90,8 +87,7 @@ public final class ManaCost implements Comparable { shardsTemp.add(shard); } // null is OK - that was generic mana } - this.genericCost = parser.getTotalColorlessCost(); // collect generic - // mana + this.genericCost = parser.getTotalColorlessCost(); // collect generic mana // here sealClass(shardsTemp); } diff --git a/src/main/java/forge/card/mana/ManaCostBeingPaid.java b/src/main/java/forge/card/mana/ManaCostBeingPaid.java index cf68569afcd..2c809203e5d 100644 --- a/src/main/java/forge/card/mana/ManaCostBeingPaid.java +++ b/src/main/java/forge/card/mana/ManaCostBeingPaid.java @@ -85,7 +85,8 @@ public class ManaCostBeingPaid { @Override public int getTotalColorlessCost() { - return unpaidShards.get(ManaCostShard.COLORLESS); + Integer c = unpaidShards.get(ManaCostShard.COLORLESS); + return c == null ? 0 : c.intValue(); } } From 6c292fd06718c6146f90cc3468d833951eefbfdd Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 19:04:09 +0000 Subject: [PATCH 094/123] bugfix for announced X --- src/main/java/forge/card/mana/ManaCostShard.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/forge/card/mana/ManaCostShard.java b/src/main/java/forge/card/mana/ManaCostShard.java index acccd8915d4..e12aa9ca0bb 100644 --- a/src/main/java/forge/card/mana/ManaCostShard.java +++ b/src/main/java/forge/card/mana/ManaCostShard.java @@ -282,6 +282,7 @@ public class ManaCostShard { * @return the card mana cost shard */ public static ManaCostShard valueOf(final int atoms) { + if ( atoms == 0 ) return ManaCostShard.COLORLESS; for (final ManaCostShard element : ManaCostShard.ALL_POSSIBLE) { if (element.shard == atoms) { return element; From 7ffd8008d5738920774340a5d7b5caa796c93721 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 19:07:42 +0000 Subject: [PATCH 095/123] fix2 --- src/main/java/forge/card/mana/ManaCost.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/forge/card/mana/ManaCost.java b/src/main/java/forge/card/mana/ManaCost.java index d591db9b9f4..c4bf7c422b2 100644 --- a/src/main/java/forge/card/mana/ManaCost.java +++ b/src/main/java/forge/card/mana/ManaCost.java @@ -83,7 +83,7 @@ public final class ManaCost implements Comparable { this.hasNoCost = false; while (parser.hasNext()) { final ManaCostShard shard = parser.next(); - if (shard != null) { + if (shard != null && shard != ManaCostShard.COLORLESS) { shardsTemp.add(shard); } // null is OK - that was generic mana } From d4d19f4f652d7ffcc85a0f0bbdf1ca777950a88a Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 19:12:55 +0000 Subject: [PATCH 096/123] Tested consume_spirit.txt with Announce with X that must be colored - works! --- res/cardsfolder/c/consume_spirit.txt | 2 +- src/main/java/forge/card/cost/CostPartMana.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/res/cardsfolder/c/consume_spirit.txt b/res/cardsfolder/c/consume_spirit.txt index a6b7b2cfc31..1aba348946c 100644 --- a/res/cardsfolder/c/consume_spirit.txt +++ b/res/cardsfolder/c/consume_spirit.txt @@ -1,7 +1,7 @@ Name:Consume Spirit ManaCost:X 1 B Types:Sorcery -A:SP$DealDamage | Cost$ X 1 B | XColor$ B | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | NumDmg$ X | SubAbility$ DBGainLife | References$ X | SpellDescription$ Spend only black mana on X. Consume Spirit deals X damage to target creature or player and you gain X life. +A:SP$DealDamage | Cost$ X 1 B | XColor$ B | Announce$ X | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | NumDmg$ X | SubAbility$ DBGainLife | References$ X | SpellDescription$ Spend only black mana on X. Consume Spirit deals X damage to target creature or player and you gain X life. SVar:DBGainLife:DB$GainLife | Defined$ You | LifeAmount$ X | References$ X SVar:X:Count$xPaid SVar:RemAIDeck:True diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index e6d5717ee8d..f4422e3487c 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -118,9 +118,7 @@ public class CostPartMana extends CostPart { final Card source = ability.getSourceCard(); ManaCostBeingPaid toPay = new ManaCostBeingPaid(getManaToPay()); - if (this.getAmountOfX() > 0 && !ability.getSVar("X").equals("Count$xPaid")) { - // if X cost is a defined value, other than xPaid - + if (this.getAmountOfX() > 0 && !ability.getSVar("X").equals("Count$xPaid")) { // announce X will overwrite whatever was in card script // this currently only works for things about Targeted object int xCost = AbilityUtils.calculateAmount(source, "X", ability) * this.getAmountOfX(); byte xColor = MagicColor.fromName(ability.hasParam("XColor") ? ability.getParam("XColor") : "1"); From e3eb4dd972957d494c717ef2b6c7fad53dad4274 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 3 Apr 2013 20:59:31 +0000 Subject: [PATCH 097/123] simultaneous announces for X and multikicker work now. Comet storm scripted, Strength of Tajuru scriptable. possible side effect: AI can may become unable to cast spells with X in their cost. (see ComputerUtilMana.java:242) --- .gitattributes | 1 + res/cardsfolder/c/comet_storm.txt | 11 +++ src/main/java/forge/Card.java | 16 ---- .../cardfactory/CardFactoryCreatures.java | 1 - .../card/cardfactory/CardFactoryUtil.java | 7 -- .../java/forge/card/cost/CostPartMana.java | 6 ++ .../spellability/HumanPlaySpellAbility.java | 15 +++- .../forge/card/spellability/SpellAbility.java | 42 ++-------- .../forge/card/trigger/WrappedAbility.java | 5 -- .../control/input/InputSelectTargets.java | 5 ++ .../java/forge/game/ai/ComputerUtilMana.java | 20 ++++- src/main/java/forge/game/zone/MagicStack.java | 77 +++++-------------- 12 files changed, 78 insertions(+), 128 deletions(-) create mode 100644 res/cardsfolder/c/comet_storm.txt diff --git a/.gitattributes b/.gitattributes index e95c148e8ff..0e91d634b07 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1907,6 +1907,7 @@ res/cardsfolder/c/colossus_of_sardia.txt svneol=native#text/plain res/cardsfolder/c/coma_veil.txt svneol=native#text/plain res/cardsfolder/c/combat_medic.txt svneol=native#text/plain res/cardsfolder/c/combust.txt svneol=native#text/plain +res/cardsfolder/c/comet_storm.txt -text res/cardsfolder/c/command_of_unsummoning.txt svneol=native#text/plain res/cardsfolder/c/commander_eesha.txt svneol=native#text/plain res/cardsfolder/c/commander_greven_il_vec.txt svneol=native#text/plain diff --git a/res/cardsfolder/c/comet_storm.txt b/res/cardsfolder/c/comet_storm.txt new file mode 100644 index 00000000000..9596ffd8525 --- /dev/null +++ b/res/cardsfolder/c/comet_storm.txt @@ -0,0 +1,11 @@ +Name:Comet Storm +ManaCost:X R R +Types:Instant +A:SP$ DealDamage | Cost$ X R R | Announce$ Multikicker,X | ValidTgts$ Creature,Player | TgtPrompt$ Select target creature or player | NumDmg$ X | TargetMin$ TargetsNum | TargetMax$ TargetsNum | References$ X,TargetsNum | SpellDescription$ CARDNAME deals X damage to each target creature and/or player. +K:Multikicker 1 +SVar:TargetsNum:Count$TimesKicked/Plus.1 +SVar:Picture:http://www.wizards.com/global/images/magic/general/comet_storm.jpg +SVar:X:Count$xPaid +Oracle:Multikicker {1} (You may pay an additional {1} any number of times as you cast this spell.)\nChoose target creature or player, then choose another target creature or player for each time Comet Storm was kicked. Comet Storm deals X damage to each of them. +SetInfo:WWK Mythic +SetInfo:CMD Mythic diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index 351303af20a..168a1890016 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -4470,26 +4470,10 @@ public class Card extends GameEntity implements Comparable { public final void addMultiKickerMagnitude(final int n) { this.multiKickerMagnitude += n; } - - /** - *

- * Setter for the field multiKickerMagnitude. - *

- * - * @param n - * a int. - */ public final void setMultiKickerMagnitude(final int n) { this.multiKickerMagnitude = n; } - /** - *

- * Getter for the field multiKickerMagnitude. - *

- * - * @return a int. - */ public final int getMultiKickerMagnitude() { return this.multiKickerMagnitude; } diff --git a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java index eef6eaafcfe..86158d76789 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java @@ -316,7 +316,6 @@ public class CardFactoryCreatures { c.addCounter(CounterType.P1P1, xCounters, true); } }; - spell.setXManaCost(1); // Do not remove SpellAbilities created by AbilityFactory or // Keywords. card.clearFirstSpell(); diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index 98595d32235..891725d07a4 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -47,7 +47,6 @@ import forge.card.ability.ApiType; import forge.card.cost.Cost; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostParser; -import forge.card.mana.ManaCostShard; import forge.card.replacement.ReplacementEffect; import forge.card.replacement.ReplacementHandler; import forge.card.replacement.ReplacementLayer; @@ -2509,12 +2508,6 @@ public class CardFactoryUtil { } } // Suspend - int xCount = card.getManaCost().getShardCount(ManaCostShard.X); - if (xCount > 0) { - final SpellAbility sa = card.getSpellAbility()[0]; - sa.setXManaCost(xCount); - } // X - if (CardFactoryUtil.hasKeyword(card, "Fading") != -1) { final int n = CardFactoryUtil.hasKeyword(card, "Fading"); if (n != -1) { diff --git a/src/main/java/forge/card/cost/CostPartMana.java b/src/main/java/forge/card/cost/CostPartMana.java index f4422e3487c..05327d51dd9 100644 --- a/src/main/java/forge/card/cost/CostPartMana.java +++ b/src/main/java/forge/card/cost/CostPartMana.java @@ -124,6 +124,12 @@ public class CostPartMana extends CostPart { byte xColor = MagicColor.fromName(ability.hasParam("XColor") ? ability.getParam("XColor") : "1"); toPay.increaseShard(ManaCostShard.valueOf(xColor), xCost); } + int timesMultikicked = ability.getSourceCard().getMultiKickerMagnitude(); + if ( timesMultikicked > 0 && ability.isAnnouncing("Multikicker")) { + ManaCost mkCost = ability.getMultiKickerManaCost(); + for(int i = 0; i < timesMultikicked; i++) + toPay.combineManaCost(mkCost); + } if (!toPay.isPaid()) { diff --git a/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java index 17791afea16..9742dc39b39 100644 --- a/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java +++ b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java @@ -155,11 +155,20 @@ public class HumanPlaySpellAbility { String announce = ability.getParam("Announce"); if (announce != null) { for(String aVar : announce.split(",")) { - Integer value = ability.getActivatingPlayer().getController().announceRequirements(ability, aVar, ability.getPayCosts().getCostMana().canXbe0()); + String varName = aVar.trim(); + + boolean allowZero = !("X".equalsIgnoreCase(varName)) || ability.getPayCosts().getCostMana().canXbe0(); + + Integer value = ability.getActivatingPlayer().getController().announceRequirements(ability, varName, allowZero); if ( null == value ) return false; - ability.setSVar(aVar, value.toString()); - ability.getSourceCard().setSVar(aVar, value.toString()); + + ability.setSVar(varName, value.toString()); + if( "Multikicker".equals(varName) ) { + ability.getSourceCard().setMultiKickerMagnitude(value); + } else { + ability.getSourceCard().setSVar(varName, value.toString()); + } } } return true; diff --git a/src/main/java/forge/card/spellability/SpellAbility.java b/src/main/java/forge/card/spellability/SpellAbility.java index d3e8468d0f2..6e653839a50 100644 --- a/src/main/java/forge/card/spellability/SpellAbility.java +++ b/src/main/java/forge/card/spellability/SpellAbility.java @@ -57,7 +57,6 @@ public abstract class SpellAbility implements ISpellAbility { private ManaCost manaCost = null; private ManaCost multiKickerManaCost = null; private ManaCost replicateManaCost = null; - private int xManaCost = 0; private Player activatingPlayer = null; private String type = "Intrinsic"; // set to Intrinsic by default @@ -290,29 +289,6 @@ public abstract class SpellAbility implements ISpellAbility { this.replicateManaCost = spellManaCost; } - /** - *

- * Getter for the field xManaCost. - *

- * - * @return a {@link java.lang.String} object. - */ - public int getXManaCost() { - return this.xManaCost; - } - - /** - *

- * Setter for the field xManaCost. - *

- * - * @param cost - * a {@link java.lang.String} object. - */ - public final void setXManaCost(final int cost) { - this.xManaCost = cost; - } - /** *

* Getter for the field activatingPlayer. @@ -396,7 +372,7 @@ public abstract class SpellAbility implements ISpellAbility { * @return a boolean. */ public boolean isMultiKicker() { - return this.multiKickerManaCost != null; + return this.multiKickerManaCost != null && !this.isAnnouncing("Multikicker"); } /** @@ -423,17 +399,6 @@ public abstract class SpellAbility implements ISpellAbility { } - /** - *

- * isXCost. - *

- * - * @return a boolean. - */ - public boolean isXCost() { - return getXManaCost() > 0; - } - /** *

* setIsCycling. @@ -1757,5 +1722,10 @@ public abstract class SpellAbility implements ISpellAbility { return true; } return false; + } + + public boolean isXCost() { + CostPartMana cm = payCosts != null ? getPayCosts().getCostMana() : null; + return cm != null && cm.getAmountOfX() > 0; } } diff --git a/src/main/java/forge/card/trigger/WrappedAbility.java b/src/main/java/forge/card/trigger/WrappedAbility.java index 30770fc53ef..0722ab7054f 100644 --- a/src/main/java/forge/card/trigger/WrappedAbility.java +++ b/src/main/java/forge/card/trigger/WrappedAbility.java @@ -219,11 +219,6 @@ public class WrappedAbility extends Ability implements ISpellAbility { return sa.getTargetPlayer(); } - @Override - public int getXManaCost() { - return sa.getXManaCost(); - } - @Override public boolean isAbility() { return sa.isAbility(); diff --git a/src/main/java/forge/control/input/InputSelectTargets.java b/src/main/java/forge/control/input/InputSelectTargets.java index 70bfe4d5710..629c1cc95cd 100644 --- a/src/main/java/forge/control/input/InputSelectTargets.java +++ b/src/main/java/forge/control/input/InputSelectTargets.java @@ -62,6 +62,11 @@ public final class InputSelectTargets extends InputSyncronizedBase { } //sb.append(tgt.getTargetedString()).append("\n"); sb.append(tgt.getVTSelection()); + + int maxTargets = tgt.getMaxTargets(sa.getSourceCard(), sa); + int targeted = tgt.getNumTargeted(); + if(maxTargets > 1) + sb.append("\n(").append(maxTargets - targeted).append(" more can be targeted)"); showMessage(sb.toString()); diff --git a/src/main/java/forge/game/ai/ComputerUtilMana.java b/src/main/java/forge/game/ai/ComputerUtilMana.java index 343242388d2..bf8effe3422 100644 --- a/src/main/java/forge/game/ai/ComputerUtilMana.java +++ b/src/main/java/forge/game/ai/ComputerUtilMana.java @@ -16,12 +16,14 @@ import forge.Constant; import forge.card.MagicColor; import forge.card.ability.AbilityUtils; import forge.card.ability.ApiType; +import forge.card.cardfactory.CardFactoryUtil; import forge.card.cost.Cost; import forge.card.cost.CostPayment; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostBeingPaid; import forge.card.mana.ManaCostShard; import forge.card.mana.ManaPool; +import forge.card.spellability.Ability; import forge.card.spellability.AbilityManaPart; import forge.card.spellability.AbilitySub; import forge.card.spellability.SpellAbility; @@ -65,7 +67,7 @@ public class ComputerUtilMana { manapool.clearManaPaid(sa, test); return true; } - + // get map of mana abilities final Map> manaAbilityMap = ComputerUtilMana.mapManaSources(ai, checkPlayable); // initialize ArrayList list for mana needed @@ -237,6 +239,22 @@ public class ComputerUtilMana { } // payManaCost() + // TODO: this code is disconnected now, it was moved here from MagicStack, where X cost is not processed any more + public static void computerPayX(final SpellAbility sa, AIPlayer player, int xCost) { + final int neededDamage = CardFactoryUtil.getNeededXDamage(sa); + final Ability ability = new Ability(sa.getSourceCard(), ManaCost.get(xCost)) { + @Override + public void resolve() { + sa.getSourceCard().addXManaCostPaid(1); + } + }; + + while (ComputerUtilCost.canPayCost(ability, player) && (neededDamage != sa.getSourceCard().getXManaCostPaid())) { + ComputerUtil.playNoStack(player, ability, player.getGame()); + } + + } + /** *

* payManaCost. diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index e14a936792a..c188cb9433f 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -45,10 +45,8 @@ import forge.card.spellability.TargetChoices; import forge.card.trigger.Trigger; import forge.card.trigger.TriggerType; import forge.control.input.InputPayManaExecuteCommands; -import forge.control.input.InputPayManaX; import forge.control.input.InputSelectCards; import forge.control.input.InputSelectCardsFromList; -import forge.control.input.InputSynchronized; import forge.game.GameActionUtil; import forge.game.GameState; import forge.game.ai.ComputerUtil; @@ -381,76 +379,37 @@ public class MagicStack extends MyObservable { } if (sp.getSourceCard().isCopiedSpell()) { this.push(sp); - } else if (!sp.isMultiKicker() && !sp.isReplicate() && !sp.isXCost()) { + } else if (!sp.isMultiKicker() && !sp.isReplicate()) { this.push(sp); - } else if ((sp.getPayCosts() != null) && !sp.isMultiKicker() && !sp.isReplicate()) { - this.push(sp); - } else if (sp.isXCost()) { - // TODO: convert any X costs to use abCost so it happens earlier - final SpellAbility sa = sp; - final int xCost = sa.getXManaCost(); - Player player = sp.getSourceCard().getController(); - if (player.isHuman()) { - InputSynchronized inp = new InputPayManaX(sa, xCost, true); - FThreads.setInputAndWait(inp); - MagicStack.this.push(sa); - } else { - // computer - final int neededDamage = CardFactoryUtil.getNeededXDamage(sa); - final Ability ability = new Ability(sp.getSourceCard(), ManaCost.get(xCost)) { - @Override - public void resolve() { - final Card crd = this.getSourceCard(); - crd.addXManaCostPaid(1); - } - }; - - while (ComputerUtilCost.canPayCost(ability, player) && (neededDamage != sa.getSourceCard().getXManaCostPaid())) { - ComputerUtil.playNoStack((AIPlayer)player, ability, game); - } - this.push(sa); - } } else if (sp.isMultiKicker()) { - // TODO: convert multikicker support in abCost so this doesn't - // happen here - // both X and multi is not supported yet - final SpellAbility sa = sp; - final Ability abilityIncreaseMultikicker = new Ability(sp.getSourceCard(), sp.getMultiKickerManaCost()) { - @Override - public void resolve() { - this.getSourceCard().addMultiKickerMagnitude(1); - } - }; - final Player activating = sp.getActivatingPlayer(); if (activating.isHuman()) { - sa.getSourceCard().addMultiKickerMagnitude(-1); - final Runnable paidCommand = new Runnable() { - @Override - public void run() { - abilityIncreaseMultikicker.resolve(); - int mkMagnitude = sa.getSourceCard().getMultiKickerMagnitude(); - String prompt = String.format("Multikicker for %s\r\nTimes Kicked: %d\r\n", sa.getSourceCard(), mkMagnitude ); - InputPayManaExecuteCommands toSet = new InputPayManaExecuteCommands(activating, prompt, sp.getMultiKickerManaCost()); - FThreads.setInputAndWait(toSet); - if ( toSet.isPaid() ) { - this.run(); - } else - MagicStack.this.push(sa); - } - }; - paidCommand.run(); + while(true) { + int mkMagnitude = sa.getSourceCard().getMultiKickerMagnitude(); + String prompt = String.format("Multikicker for %s\r\nTimes Kicked: %d\r\n", sa.getSourceCard(), mkMagnitude ); + InputPayManaExecuteCommands toSet = new InputPayManaExecuteCommands(activating, prompt, sp.getMultiKickerManaCost()); + FThreads.setInputAndWait(toSet); + if ( !toSet.isPaid() ) + break; + + sa.getSourceCard().addMultiKickerMagnitude(1); + } } else { // computer + final Ability abilityIncreaseMultikicker = new Ability(sp.getSourceCard(), sp.getMultiKickerManaCost()) { + @Override + public void resolve() { + this.getSourceCard().addMultiKickerMagnitude(1); + } + }; while (ComputerUtilCost.canPayCost(abilityIncreaseMultikicker, activating)) { ComputerUtil.playNoStack((AIPlayer)activating, abilityIncreaseMultikicker, game); } - - this.push(sa); } + this.push(sa); } else if (sp.isReplicate()) { // TODO: convert multikicker/replicate support in abCost so this // doesn't happen here From 1970863c1b2b2035e1307d07f369d9c3aa7a4392 Mon Sep 17 00:00:00 2001 From: Sloth Date: Wed, 3 Apr 2013 21:24:21 +0000 Subject: [PATCH 098/123] - Added the hard quest deck Preacher 3 by Nordos. --- .gitattributes | 1 + res/quest/duels/Preacher 3.dck | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 res/quest/duels/Preacher 3.dck diff --git a/.gitattributes b/.gitattributes index 0e91d634b07..8586b70dac2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12820,6 +12820,7 @@ res/quest/duels/Picard[!!-~]3.dck -text res/quest/duels/Pinky[!!-~]and[!!-~]the[!!-~]Brain[!!-~]2.dck -text res/quest/duels/Piper[!!-~]2.dck -text res/quest/duels/Pointy[!!-~]Haired[!!-~]Boss[!!-~]3.dck -text +res/quest/duels/Preacher[!!-~]3.dck -text res/quest/duels/Princess[!!-~]Selenia[!!-~]1.dck -text res/quest/duels/Professor[!!-~]X[!!-~]2.dck -text res/quest/duels/Professor[!!-~]X[!!-~]3.dck -text diff --git a/res/quest/duels/Preacher 3.dck b/res/quest/duels/Preacher 3.dck new file mode 100644 index 00000000000..c903041bc4b --- /dev/null +++ b/res/quest/duels/Preacher 3.dck @@ -0,0 +1,32 @@ +[duel] +[metadata] +Name=Preacher 3 +Title=Preacher +Difficulty=hard +Description=WB Cleric deck +Icon=Preacher.jpg +Deck Type=constructed +[main] +1 Akroma's Devoted +3 Ancestor's Prophet +1 Battlefield Medic +3 Battletide Alchemist +2 Cathedral Sanctifier +2 Celestial Gatekeeper +4 Doubtless One +4 Edgewalker +4 Hedron-Field Purists +2 High Priest of Penance +1 Mox Jet +1 Mox Pearl +1 Order of Whiteclay +12 Plains +1 Profane Prayers +3 Rotlung Reanimator +4 Scrubland +1 Skirsdag High Priest +5 Swamp +1 Urborg, Tomb of Yawgmoth +2 Vile Deacon +2 Weathered Wayfarer +[sideboard] From e5b20fbb8a6abdc41f4c10330cdbaed03e21cb9d Mon Sep 17 00:00:00 2001 From: Sloth Date: Wed, 3 Apr 2013 22:05:42 +0000 Subject: [PATCH 099/123] - runWaitingTriggers will now call checkStaticAbilities before triggers are checked (experimental). --- src/main/java/forge/card/trigger/TriggerHandler.java | 10 ++++++---- src/main/java/forge/game/GameAction.java | 4 ++-- src/main/java/forge/game/zone/MagicStack.java | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/forge/card/trigger/TriggerHandler.java b/src/main/java/forge/card/trigger/TriggerHandler.java index 6a0c5c18db1..bf37f7b5691 100644 --- a/src/main/java/forge/card/trigger/TriggerHandler.java +++ b/src/main/java/forge/card/trigger/TriggerHandler.java @@ -169,27 +169,29 @@ public class TriggerHandler { if (game.getStack().isFrozen() || holdTrigger) { waitingTriggers.add(new TriggerWaiting(mode, runParams)); } else { - runWaitingTrigger(new TriggerWaiting(mode, runParams), true); + runWaitingTrigger(new TriggerWaiting(mode, runParams)); } // Tell auto stop to stop } - public final boolean runWaitingTriggers(boolean runStaticEffects) { + public final boolean runWaitingTriggers() { ArrayList waiting = new ArrayList(waitingTriggers); waitingTriggers.clear(); if (waiting.isEmpty()) { return false; } + + Singletons.getModel().getGame().getAction().checkStaticAbilities(); boolean haveWaiting = false; for (TriggerWaiting wt : waiting) { - haveWaiting |= runWaitingTrigger(wt, runStaticEffects); + haveWaiting |= runWaitingTrigger(wt); } return haveWaiting; } - public final boolean runWaitingTrigger(TriggerWaiting wt, boolean runStaticEffects) { + public final boolean runWaitingTrigger(TriggerWaiting wt) { final TriggerType mode = wt.getMode(); final Map runParams = wt.getParams(); final GameState game = Singletons.getModel().getGame(); diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index 23c08a68205..bd2fe50342d 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -848,7 +848,7 @@ public class GameAction { } /** */ - private final void checkStaticAbilities() { + public final void checkStaticAbilities() { // remove old effects game.getStaticEffects().clearStaticEffects(); @@ -1051,7 +1051,7 @@ public class GameAction { } } - if (game.getTriggerHandler().runWaitingTriggers(true)) { + if (game.getTriggerHandler().runWaitingTriggers()) { checkAgain = true; // Place triggers on stack } diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index c188cb9433f..b469895b9be 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -201,7 +201,7 @@ public class MagicStack extends MyObservable { this.add(sa); } // Add all waiting triggers onto the stack - checkState |= Singletons.getModel().getGame().getTriggerHandler().runWaitingTriggers(false); + checkState |= Singletons.getModel().getGame().getTriggerHandler().runWaitingTriggers(); if (checkState) { this.chooseOrderOfSimultaneousStackEntryAll(); game.getAction().checkStateEffects(); From 4eb77330ee3f025d848910b53516d31bd6cd1808 Mon Sep 17 00:00:00 2001 From: Sloth Date: Wed, 3 Apr 2013 22:20:26 +0000 Subject: [PATCH 100/123] - Updated the Preacher 3 decklist (as posted in the forums). --- res/quest/duels/Preacher 3.dck | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/res/quest/duels/Preacher 3.dck b/res/quest/duels/Preacher 3.dck index c903041bc4b..158d2111621 100644 --- a/res/quest/duels/Preacher 3.dck +++ b/res/quest/duels/Preacher 3.dck @@ -22,10 +22,12 @@ Deck Type=constructed 1 Order of Whiteclay 12 Plains 1 Profane Prayers -3 Rotlung Reanimator +1 Rotlung Reanimator 4 Scrubland 1 Skirsdag High Priest 5 Swamp +1 Syndic of Tithes +1 Temple Acolyte 1 Urborg, Tomb of Yawgmoth 2 Vile Deacon 2 Weathered Wayfarer From 609f99a2b687f6155f93d9dc7dea443a7976119c Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 04:24:17 +0000 Subject: [PATCH 101/123] BugReporter will print crashed thread ID --- src/main/java/forge/card/spellability/Target.java | 2 +- src/main/java/forge/error/BugReporter.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/forge/card/spellability/Target.java b/src/main/java/forge/card/spellability/Target.java index e5300e48c41..87c6cdd80d7 100644 --- a/src/main/java/forge/card/spellability/Target.java +++ b/src/main/java/forge/card/spellability/Target.java @@ -272,7 +272,7 @@ public class Target { * * @return the min targets */ - public final String getMinTargets() { + private final String getMinTargets() { return this.minTargets; } diff --git a/src/main/java/forge/error/BugReporter.java b/src/main/java/forge/error/BugReporter.java index 5b8048fea65..3b01511196f 100644 --- a/src/main/java/forge/error/BugReporter.java +++ b/src/main/java/forge/error/BugReporter.java @@ -50,6 +50,7 @@ import net.miginfocom.swing.MigLayout; import org.apache.commons.lang3.StringUtils; +import forge.FThreads; import forge.gui.WrapLayout; import forge.gui.toolbox.FHyperlink; import forge.gui.toolbox.FLabel; @@ -76,7 +77,7 @@ public class BugReporter { return; } if (message != null) { - System.err.println(message); + System.err.printf("%s > %s%n", FThreads.debugGetCurrThreadId(), message); } ex.printStackTrace(); @@ -85,8 +86,7 @@ public class BugReporter { _buildSpoilerHeader(sb, ex.getClass().getSimpleName()); sb.append("\n\n"); if (null != message && !message.isEmpty()) { - sb.append(message); - sb.append("\n"); + sb.append(FThreads.debugGetCurrThreadId()).append(" > ").append(message).append("\n"); } StringWriter sw = new StringWriter(); From 43553e7b3b9eee8aa4ce624e67723a23c849b72d Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 04:25:30 +0000 Subject: [PATCH 102/123] Sacrifice effect will try to include SacValid$ into message for easier understanding of what input wants you to do --- src/main/java/forge/card/ability/effects/SacrificeEffect.java | 2 +- src/main/java/forge/game/phase/CombatUtil.java | 2 +- src/main/java/forge/game/player/PlayerController.java | 3 +-- src/main/java/forge/game/player/PlayerControllerAi.java | 2 +- src/main/java/forge/game/player/PlayerControllerHuman.java | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/forge/card/ability/effects/SacrificeEffect.java b/src/main/java/forge/card/ability/effects/SacrificeEffect.java index a875e5f701d..d7c53e35181 100644 --- a/src/main/java/forge/card/ability/effects/SacrificeEffect.java +++ b/src/main/java/forge/card/ability/effects/SacrificeEffect.java @@ -61,7 +61,7 @@ public class SacrificeEffect extends SpellAbilityEffect { choosenToSacrifice = Aggregates.random(validTargets, Math.min(amount, validTargets.size())); } else { boolean isOptional = sa.hasParam("Optional"); - choosenToSacrifice = p.getController().choosePermanentsToSacrifice(validTargets, amount, sa, destroy, isOptional); + choosenToSacrifice = p.getController().choosePermanentsToSacrifice(validTargets, valid, amount, sa, destroy, isOptional); } for(Card sac : choosenToSacrifice) { diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index 9d7c0ae95b2..ef107e4a8a9 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -1207,7 +1207,7 @@ public class CombatUtil { final Player opponent = Singletons.getModel().getGame().getCombat().getDefendingPlayerRelatedTo(c).get(0); //List list = AbilityUtils.filterListByType(opponent.getCardsIn(ZoneType.Battlefield), "Permanent", this); final List list = opponent.getCardsIn(ZoneType.Battlefield); - List toSac = opponent.getController().choosePermanentsToSacrifice(list, a, this, false, false); + List toSac = opponent.getController().choosePermanentsToSacrifice(list, "Card", a, this, false, false); for(Card sacd : toSac) { final GameState game = Singletons.getModel().getGame(); diff --git a/src/main/java/forge/game/player/PlayerController.java b/src/main/java/forge/game/player/PlayerController.java index 093a5b82b97..993699bda94 100644 --- a/src/main/java/forge/game/player/PlayerController.java +++ b/src/main/java/forge/game/player/PlayerController.java @@ -95,8 +95,7 @@ public abstract class PlayerController { public abstract Map assignCombatDamage(Card attacker, List blockers, int damageDealt, GameEntity defender); public abstract Integer announceRequirements(SpellAbility ability, String announce, boolean allowZero); - - public abstract List choosePermanentsToSacrifice(List validTargets, int amount, SpellAbility sa, boolean destroy, boolean isOptional); + public abstract List choosePermanentsToSacrifice(List validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional); public Card chooseSingleCardForEffect(List sourceList, SpellAbility sa, String title) { return chooseSingleCardForEffect(sourceList, sa, title, false); } public abstract Card chooseSingleCardForEffect(List sourceList, SpellAbility sa, String title, boolean isOptional); diff --git a/src/main/java/forge/game/player/PlayerControllerAi.java b/src/main/java/forge/game/player/PlayerControllerAi.java index 5af7fda17f2..47806016f05 100644 --- a/src/main/java/forge/game/player/PlayerControllerAi.java +++ b/src/main/java/forge/game/player/PlayerControllerAi.java @@ -155,7 +155,7 @@ public class PlayerControllerAi extends PlayerController { } @Override - public List choosePermanentsToSacrifice(List validTargets, int amount, SpellAbility sa, boolean destroy, boolean isOptional) { + public List choosePermanentsToSacrifice(List validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional) { return ComputerUtil.choosePermanentsToSacrifice(player, validTargets, amount, sa, destroy, isOptional); } diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index 10ecb1cd17f..d9b674a5604 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -240,14 +240,14 @@ public class PlayerControllerHuman extends PlayerController { * @see forge.game.player.PlayerController#choosePermanentsToSacrifice(java.util.List, int, forge.card.spellability.SpellAbility, boolean, boolean) */ @Override - public List choosePermanentsToSacrifice(List validTargets, int amount, SpellAbility sa, boolean destroy, boolean isOptional) { + public List choosePermanentsToSacrifice(List validTargets, String validMessage, int amount, SpellAbility sa, boolean destroy, boolean isOptional) { int max = Math.min(amount, validTargets.size()); if (max == 0) return new ArrayList(); InputSelectCards inp = new InputSelectCardsFromList(isOptional ? 0 : amount, max, validTargets); // TODO: Either compose a message here, or pass it as parameter from caller. - inp.setMessage("Select %d card(s) to sacrifice"); + inp.setMessage("Select %d " + validMessage + "(s) to sacrifice"); FThreads.setInputAndWait(inp); if( inp.hasCancelled() ) From 3b88eae06390c7716dab8352630977582204f593 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 04:28:04 +0000 Subject: [PATCH 103/123] InputControl.java - getActualInput: moved stack extraction to top. so that mulligan input is returned only with empty stack. --- .../forge/control/input/InputControl.java | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/main/java/forge/control/input/InputControl.java b/src/main/java/forge/control/input/InputControl.java index 5a92aa81191..f049ed5706d 100644 --- a/src/main/java/forge/control/input/InputControl.java +++ b/src/main/java/forge/control/input/InputControl.java @@ -22,8 +22,10 @@ import java.util.Stack; import forge.Singletons; import forge.game.GameState; import forge.game.GameType; +import forge.game.MatchController; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.player.PlayerController; import forge.game.zone.MagicStack; @@ -116,16 +118,14 @@ public class InputControl extends MyObservable implements java.io.Serializable { * @return a {@link forge.control.input.InputBase} object. */ public final Input getActualInput(GameState game) { - if ( !game.hasMulliganned() ) - { - if(game.getType() == GameType.Commander) - { - return new InputPartialParisMulligan(Singletons.getModel().getMatch(), Singletons.getControl().getPlayer()); - } - else - { - return new InputMulligan(Singletons.getModel().getMatch(), Singletons.getControl().getPlayer()); - } + if (!this.inputStack.isEmpty()) { // incoming input to Control + return this.inputStack.peek(); + } + + if ( !game.hasMulliganned() ) { + HumanPlayer human = Singletons.getControl().getPlayer(); + MatchController match = Singletons.getModel().getMatch(); + return game.getType() == GameType.Commander ? new InputPartialParisMulligan(match, human) : new InputMulligan(match, human); } final PhaseHandler handler = game.getPhaseHandler(); final PhaseType phase = handler.getPhase(); @@ -135,13 +135,6 @@ public class InputControl extends MyObservable implements java.io.Serializable { throw new RuntimeException("No player has priority!"); PlayerController pc = priority.getController(); - - - if (!this.inputStack.isEmpty()) { // incoming input to Control - return this.inputStack.peek(); - } - - // If the Phase we're in doesn't allow for Priority, move to next phase if (!handler.isPlayerPriorityAllowed()) { From 16414982a8730325ae8612a1c749511dbf2c5ae2 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 05:16:46 +0000 Subject: [PATCH 104/123] the 'state' of GameState added - named it GameAge this should prevent wrong inputs before mulligan (problem reported by sol) --- .gitattributes | 1 + .../forge/control/input/InputControl.java | 27 +++++--- src/main/java/forge/game/GameAction.java | 55 +++++++++++++++ src/main/java/forge/game/GameAge.java | 8 +++ src/main/java/forge/game/GameNew.java | 2 +- src/main/java/forge/game/GameState.java | 23 ++++--- src/main/java/forge/game/MatchController.java | 67 ++----------------- 7 files changed, 100 insertions(+), 83 deletions(-) create mode 100644 src/main/java/forge/game/GameAge.java diff --git a/.gitattributes b/.gitattributes index 8586b70dac2..f3014576662 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13897,6 +13897,7 @@ src/main/java/forge/error/ExceptionHandler.java svneol=native#text/plain src/main/java/forge/error/package-info.java svneol=native#text/plain src/main/java/forge/game/GameAction.java svneol=native#text/plain src/main/java/forge/game/GameActionUtil.java svneol=native#text/plain +src/main/java/forge/game/GameAge.java -text src/main/java/forge/game/GameEndReason.java -text src/main/java/forge/game/GameFormat.java -text src/main/java/forge/game/GameLossReason.java -text diff --git a/src/main/java/forge/control/input/InputControl.java b/src/main/java/forge/control/input/InputControl.java index f049ed5706d..d9b579d0eab 100644 --- a/src/main/java/forge/control/input/InputControl.java +++ b/src/main/java/forge/control/input/InputControl.java @@ -20,6 +20,7 @@ package forge.control.input; import java.util.Stack; import forge.Singletons; +import forge.game.GameAge; import forge.game.GameState; import forge.game.GameType; import forge.game.MatchController; @@ -45,6 +46,11 @@ public class InputControl extends MyObservable implements java.io.Serializable { private final Stack inputStack = new Stack(); + private final MatchController match; + public InputControl(MatchController matchController) { + match = matchController; + } + /** *

* Setter for the field input. @@ -118,15 +124,20 @@ public class InputControl extends MyObservable implements java.io.Serializable { * @return a {@link forge.control.input.InputBase} object. */ public final Input getActualInput(GameState game) { + GameAge age = game.getAge(); + + if ( age == GameAge.BeforeMulligan || age == GameAge.GameOver) + return inputLock; + + if ( age == GameAge.Mulligan ) { + HumanPlayer human = Singletons.getControl().getPlayer(); + return game.getType() == GameType.Commander ? new InputPartialParisMulligan(match, human) : new InputMulligan(match, human); + } + if (!this.inputStack.isEmpty()) { // incoming input to Control return this.inputStack.peek(); } - - if ( !game.hasMulliganned() ) { - HumanPlayer human = Singletons.getControl().getPlayer(); - MatchController match = Singletons.getModel().getMatch(); - return game.getType() == GameType.Commander ? new InputPartialParisMulligan(match, human) : new InputMulligan(match, human); - } + final PhaseHandler handler = game.getPhaseHandler(); final PhaseType phase = handler.getPhase(); final Player playerTurn = handler.getPlayerTurn(); @@ -186,9 +197,7 @@ public class InputControl extends MyObservable implements java.io.Serializable { return pc.getDefaultInput(); } // getInput() - /** - * TODO: Write javadoc for this method. - */ + private final static InputLockUI inputLock = new InputLockUI(); public void lock() { setInput(inputLock); diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index bd2fe50342d..50282765780 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -38,6 +38,7 @@ import forge.CounterType; import forge.GameEntity; import forge.card.CardType; import forge.card.TriggerReplacementBase; +import forge.card.ability.AbilityFactory; import forge.card.ability.effects.AttachEffect; import forge.card.cardfactory.CardFactory; import forge.card.cost.Cost; @@ -57,6 +58,7 @@ import forge.game.event.CardDestroyedEvent; import forge.game.event.CardRegeneratedEvent; import forge.game.event.CardSacrificedEvent; import forge.game.player.AIPlayer; +import forge.game.player.HumanPlayer; import forge.game.player.Player; import forge.game.player.PlayerType; import forge.game.zone.PlayerZone; @@ -64,6 +66,7 @@ import forge.game.zone.PlayerZoneBattlefield; import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; +import forge.gui.GuiDialog; /** * Methods for common actions performed during a game. @@ -1402,6 +1405,58 @@ public class GameAction { } } + void handleLeylinesAndChancellors() { + for (Player p : game.getPlayers()) { + final List openingHand = new ArrayList(p.getCardsIn(ZoneType.Hand)); + + for (final Card c : openingHand) { + if (p.isHuman()) { + for (String kw : c.getKeyword()) { + if (kw.startsWith("MayEffectFromOpeningHand")) { + final String effName = kw.split(":")[1]; + + final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); + if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { + // If we ever let the AI memorize cards in the players + // hand, this would be a place to do so. + ((HumanPlayer)p).playSpellAbilityNoStack(effect); + } + } + } + if (c.getName().startsWith("Leyline of")) { + if (GuiDialog.confirm(c, "Use " + c + "'s ability?")) { + game.getAction().moveToPlay(c); + } + } + } else { // Computer Leylines & Chancellors + if (!c.getName().startsWith("Leyline of")) { + for (String kw : c.getKeyword()) { + if (kw.startsWith("MayEffectFromOpeningHand")) { + final String effName = kw.split(":")[1]; + + final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); + + // Is there a better way for the AI to decide this? + if (effect.doTrigger(false, (AIPlayer)p)) { + GuiDialog.message("Computer reveals " + c.getName() + "(" + c.getUniqueNumber() + ")."); + ComputerUtil.playNoStack((AIPlayer)p, effect, game); + } + } + } + } + if (c.getName().startsWith("Leyline of") + && !(c.getName().startsWith("Leyline of Singularity") + && (Iterables.any(game.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Leyline of Singularity"))))) { + game.getAction().moveToPlay(c); + //ga.checkStateEffects(); + } + } + } + } + + game.getAction().checkStateEffects(); + } + /** *

* playCardWithoutManaCost. diff --git a/src/main/java/forge/game/GameAge.java b/src/main/java/forge/game/GameAge.java new file mode 100644 index 00000000000..c31770d935f --- /dev/null +++ b/src/main/java/forge/game/GameAge.java @@ -0,0 +1,8 @@ +package forge.game; + +public enum GameAge { + BeforeMulligan, + Mulligan, + Play, + GameOver +} \ No newline at end of file diff --git a/src/main/java/forge/game/GameNew.java b/src/main/java/forge/game/GameNew.java index c10f43516cf..fb0be418347 100644 --- a/src/main/java/forge/game/GameNew.java +++ b/src/main/java/forge/game/GameNew.java @@ -220,7 +220,7 @@ public class GameNew { playersConditions.put(p, players.get(p.getLobbyPlayer())); } - game.setMulliganned(false); + game.setAge(GameAge.Mulligan); match.getInput().clearInput(); //Card.resetUniqueNumber(); diff --git a/src/main/java/forge/game/GameState.java b/src/main/java/forge/game/GameState.java index 95ab8dcae38..8cc12add158 100644 --- a/src/main/java/forge/game/GameState.java +++ b/src/main/java/forge/game/GameState.java @@ -18,6 +18,7 @@ package forge.game; import java.util.ArrayList; + import java.util.Collections; import java.util.List; import java.util.Map; @@ -54,9 +55,8 @@ import forge.game.zone.PlayerZone; import forge.game.zone.Zone; import forge.game.zone.ZoneType; -/** - * Represents the state of a single game and is - * "cleaned up" at each new game. + /** + * Represents the state of a single game, a new instance is created for each game. */ public class GameState { private final GameType type; @@ -82,13 +82,12 @@ public class GameState { private final GameLog gameLog = new GameLog(); private final ColorChanger colorChanger = new ColorChanger(); - private boolean gameOver = false; - private final Zone stackZone = new Zone(ZoneType.Stack); private long timestamp = 0; public final GameAction action; private final MatchController match; + private GameAge age = GameAge.BeforeMulligan; /** * Constructor. @@ -291,7 +290,7 @@ public class GameState { * @return the gameOver */ public synchronized boolean isGameOver() { - return gameOver; + return age == GameAge.GameOver; } /** @@ -299,7 +298,7 @@ public class GameState { * @param go the gameOver to set */ public synchronized void setGameOver(GameEndReason reason) { - this.gameOver = true; + this.age = GameAge.GameOver; for (Player p : roIngamePlayers) { p.onGameOver(); } @@ -661,7 +660,11 @@ public class GameState { } } - public boolean mulliganned = false; - public boolean hasMulliganned(){ return mulliganned; } - public void setMulliganned(boolean value) { mulliganned = value; } + public GameAge getAge() { + return age; + } + + void setAge(GameAge value) { + age = value; + } } diff --git a/src/main/java/forge/game/MatchController.java b/src/main/java/forge/game/MatchController.java index edf8d8c174c..eae422cd7fa 100644 --- a/src/main/java/forge/game/MatchController.java +++ b/src/main/java/forge/game/MatchController.java @@ -7,20 +7,14 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import com.google.common.collect.Iterables; import forge.Constant.Preferences; -import forge.Card; -import forge.CardPredicates; import forge.Singletons; -import forge.card.ability.AbilityFactory; -import forge.card.spellability.SpellAbility; import forge.control.FControl; import forge.control.input.InputControl; import forge.deck.Deck; import forge.error.BugReporter; import forge.game.ai.AiProfileUtil; -import forge.game.ai.ComputerUtil; import forge.game.event.DuelOutcomeEvent; import forge.game.player.AIPlayer; import forge.game.player.HumanPlayer; @@ -29,7 +23,6 @@ import forge.game.player.Player; import forge.game.player.PlayerStatistics; import forge.game.player.PlayerType; import forge.game.zone.ZoneType; -import forge.gui.GuiDialog; import forge.gui.InputProxy; import forge.gui.framework.EDocID; import forge.gui.framework.SDisplayUtil; @@ -137,8 +130,7 @@ public class MatchController { */ public void startRound() { - // Deal with circular dependencies here - input = new InputControl(); + input = new InputControl(this); currentGame = Singletons.getModel().newGame(players.keySet(),gameType, this); Map startConditions = new HashMap(); @@ -186,6 +178,7 @@ public class MatchController { final boolean canRandomFoil = Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_RANDOM_FOIL) && gameType == GameType.Constructed; GameNew.newGame(this, startConditions, currentGame, canRandomFoil); + currentGame.setAge(GameAge.Mulligan); getInput().clearInput(); //getInput().setNewInput(currentGame); @@ -348,62 +341,10 @@ public class MatchController { return 10; } - private void handleLeylinesAndChancellors() { - for (Player p : currentGame.getPlayers()) { - final List openingHand = new ArrayList(p.getCardsIn(ZoneType.Hand)); - - for (final Card c : openingHand) { - if (p.isHuman()) { - for (String kw : c.getKeyword()) { - if (kw.startsWith("MayEffectFromOpeningHand")) { - final String effName = kw.split(":")[1]; - - final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); - if (GuiDialog.confirm(c, "Use " + c +"'s ability?")) { - // If we ever let the AI memorize cards in the players - // hand, this would be a place to do so. - ((HumanPlayer)p).playSpellAbilityNoStack(effect); - } - } - } - if (c.getName().startsWith("Leyline of")) { - if (GuiDialog.confirm(c, "Use " + c + "'s ability?")) { - currentGame.getAction().moveToPlay(c); - } - } - } else { // Computer Leylines & Chancellors - if (!c.getName().startsWith("Leyline of")) { - for (String kw : c.getKeyword()) { - if (kw.startsWith("MayEffectFromOpeningHand")) { - final String effName = kw.split(":")[1]; - - final SpellAbility effect = AbilityFactory.getAbility(c.getSVar(effName), c); - - // Is there a better way for the AI to decide this? - if (effect.doTrigger(false, (AIPlayer)p)) { - GuiDialog.message("Computer reveals " + c.getName() + "(" + c.getUniqueNumber() + ")."); - ComputerUtil.playNoStack((AIPlayer)p, effect, currentGame); - } - } - } - } - if (c.getName().startsWith("Leyline of") - && !(c.getName().startsWith("Leyline of Singularity") - && (Iterables.any(currentGame.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Leyline of Singularity"))))) { - currentGame.getAction().moveToPlay(c); - //ga.checkStateEffects(); - } - } - } - } - - currentGame.getAction().checkStateEffects(); - } - public void afterMulligans() { - handleLeylinesAndChancellors(); - currentGame.setMulliganned(true); + currentGame.getAction().handleLeylinesAndChancellors(); + currentGame.setAge(GameAge.Play); getInput().clearInput(); } } From 628260b1fb8a652b126210de23609ad2328ecd7b Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 05:26:32 +0000 Subject: [PATCH 105/123] minor rearrange of statements --- src/main/java/forge/control/input/InputControl.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/forge/control/input/InputControl.java b/src/main/java/forge/control/input/InputControl.java index d9b579d0eab..b4b48c19020 100644 --- a/src/main/java/forge/control/input/InputControl.java +++ b/src/main/java/forge/control/input/InputControl.java @@ -125,15 +125,14 @@ public class InputControl extends MyObservable implements java.io.Serializable { */ public final Input getActualInput(GameState game) { GameAge age = game.getAge(); - - if ( age == GameAge.BeforeMulligan || age == GameAge.GameOver) - return inputLock; - if ( age == GameAge.Mulligan ) { HumanPlayer human = Singletons.getControl().getPlayer(); return game.getType() == GameType.Commander ? new InputPartialParisMulligan(match, human) : new InputMulligan(match, human); } + if ( age != GameAge.Play ) + return inputLock; + if (!this.inputStack.isEmpty()) { // incoming input to Control return this.inputStack.peek(); } From e1d1bb45d6d7f260f6d14c195a030fbaa07ded6a Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 05:43:14 +0000 Subject: [PATCH 106/123] removed language routines from GameState, moved to land library. --- .gitattributes | 1 + .../forge/control/input/InputMulligan.java | 14 +++++----- .../input/InputPartialParisMulligan.java | 10 ++++--- src/main/java/forge/game/GameState.java | 13 ++-------- src/main/java/forge/util/Lang.java | 26 +++++++++++++++++++ 5 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 src/main/java/forge/util/Lang.java diff --git a/.gitattributes b/.gitattributes index f3014576662..1ef95e0ce67 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14286,6 +14286,7 @@ src/main/java/forge/util/HttpUtil.java svneol=native#text/plain src/main/java/forge/util/IItemReader.java -text src/main/java/forge/util/IItemSerializer.java -text src/main/java/forge/util/IgnoringXStream.java -text +src/main/java/forge/util/Lang.java -text src/main/java/forge/util/LineReader.java -text src/main/java/forge/util/MultiplexOutputStream.java svneol=native#text/plain src/main/java/forge/util/MyObservable.java svneol=native#text/plain diff --git a/src/main/java/forge/control/input/InputMulligan.java b/src/main/java/forge/control/input/InputMulligan.java index 81eaab21545..f6283df6c3e 100644 --- a/src/main/java/forge/control/input/InputMulligan.java +++ b/src/main/java/forge/control/input/InputMulligan.java @@ -35,6 +35,7 @@ import forge.gui.framework.SDisplayUtil; import forge.gui.match.CMatchUI; import forge.gui.match.nonsingleton.VField; import forge.gui.match.views.VMessage; +import forge.util.Lang; import forge.view.ButtonUtil; /** *

@@ -58,20 +59,21 @@ public class InputMulligan extends InputBase { /** {@inheritDoc} */ @Override public final void showMessage() { - ButtonUtil.setButtonText("No", "Yes"); + ButtonUtil.setButtonText("Keep", "Mulligan"); ButtonUtil.enableAllFocusOk(); GameState game = match.getCurrentGame(); Player startingPlayer = game.getPhaseHandler().getPlayerTurn(); StringBuilder sb = new StringBuilder(); - sb.append(startingPlayer.getName()).append(" is going first. "); - - if (!startingPlayer.equals(player)) { - sb.append("You are going ").append(game.getOrdinalPosition(player, startingPlayer)).append(". "); + if( startingPlayer == player ) { + sb.append("You are going first.\n"); + } else { + sb.append(startingPlayer.getName()).append(" is going first. "); + sb.append("You are going ").append(Lang.getOrdinal(game.getPosition(player, startingPlayer))).append(".\n"); } - sb.append("Do you want to Mulligan?"); + sb.append("Do you want to keep your hand?"); showMessage(sb.toString()); } diff --git a/src/main/java/forge/control/input/InputPartialParisMulligan.java b/src/main/java/forge/control/input/InputPartialParisMulligan.java index 416086d358d..637fd292987 100644 --- a/src/main/java/forge/control/input/InputPartialParisMulligan.java +++ b/src/main/java/forge/control/input/InputPartialParisMulligan.java @@ -32,6 +32,7 @@ import forge.gui.GuiDialog; import forge.gui.framework.SDisplayUtil; import forge.gui.match.CMatchUI; import forge.gui.match.nonsingleton.VField; +import forge.util.Lang; import forge.view.ButtonUtil; /** *

@@ -65,10 +66,11 @@ public class InputPartialParisMulligan extends InputBase { Player startingPlayer = game.getPhaseHandler().getPlayerTurn(); StringBuilder sb = new StringBuilder(); - sb.append(startingPlayer.getName()).append(" is going first. "); - - if (!startingPlayer.equals(player)) { - sb.append("You are going ").append(game.getOrdinalPosition(player, startingPlayer)).append(". "); + if( startingPlayer == player ) { + sb.append("You are going first.\n"); + } else { + sb.append(startingPlayer.getName()).append(" is going first. "); + sb.append("You are going ").append(Lang.getOrdinal(game.getPosition(player, startingPlayer))).append(".\n"); } sb.append("Do you want to Mulligan?"); diff --git a/src/main/java/forge/game/GameState.java b/src/main/java/forge/game/GameState.java index 8cc12add158..d7d2ccc2348 100644 --- a/src/main/java/forge/game/GameState.java +++ b/src/main/java/forge/game/GameState.java @@ -481,19 +481,10 @@ public class GameState { } - public String getOrdinalPosition(Player player, Player startingPlayer) { + public int getPosition(Player player, Player startingPlayer) { int startPosition = roIngamePlayers.indexOf(startingPlayer); int position = (roIngamePlayers.indexOf(player) + startPosition) % roIngamePlayers.size() + 1; - String[] sufixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" }; - switch (position % 100) { - case 11: - case 12: - case 13: - return position + "th"; - default: - return position + sufixes[position % 10]; - - } + return position; } /** diff --git a/src/main/java/forge/util/Lang.java b/src/main/java/forge/util/Lang.java new file mode 100644 index 00000000000..6bbd798026f --- /dev/null +++ b/src/main/java/forge/util/Lang.java @@ -0,0 +1,26 @@ +package forge.util; + +/** + * TODO: Write javadoc for this type. + * + */ +public class Lang { + + /** + * TODO: Write javadoc for this method. + * @param position + * @return + */ + public static Object getOrdinal(int position) { + String[] sufixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" }; + switch (position % 100) { + case 11: + case 12: + case 13: + return position + "th"; + default: + return position + sufixes[position % 10]; + } + } + +} From f33353572140b826fdf795ca29019bd03ad89579 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 05:56:18 +0000 Subject: [PATCH 107/123] joinHomogenous --- .../ability/effects/ChangeZoneEffect.java | 8 ++----- src/main/java/forge/util/Lang.java | 23 +++++++++++++++++-- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java index e71c092672b..8ea33b4d38e 100644 --- a/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java +++ b/src/main/java/forge/card/ability/effects/ChangeZoneEffect.java @@ -29,6 +29,7 @@ import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.GuiDialog; import forge.util.Aggregates; +import forge.util.Lang; public class ChangeZoneEffect extends SpellAbilityEffect { @Override @@ -81,12 +82,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { fetchers.add(sa.getSourceCard().getController()); } - final StringBuilder fetcherSB = new StringBuilder(); - for (int i = 0; i < fetchers.size(); i++) { - fetcherSB.append(fetchers.get(i).getName()); - fetcherSB.append((i + 2) == fetchers.size() ? " and " : (i + 1) == fetchers.size() ? "" : ", "); - } - final String fetcherNames = fetcherSB.toString(); + final String fetcherNames = Lang.joinHomogenous(fetchers, Player.Accessors.FN_GET_NAME); // Player who chooses the cards to move List choosers = new ArrayList(); diff --git a/src/main/java/forge/util/Lang.java b/src/main/java/forge/util/Lang.java index 6bbd798026f..843c56a7d81 100644 --- a/src/main/java/forge/util/Lang.java +++ b/src/main/java/forge/util/Lang.java @@ -1,5 +1,9 @@ package forge.util; +import java.util.List; + +import com.google.common.base.Function; + /** * TODO: Write javadoc for this type. * @@ -11,7 +15,7 @@ public class Lang { * @param position * @return */ - public static Object getOrdinal(int position) { + public static String getOrdinal(int position) { String[] sufixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" }; switch (position % 100) { case 11: @@ -22,5 +26,20 @@ public class Lang { return position + sufixes[position % 10]; } } - + + public static String joinHomogenous(List objects) { return joinHomogenous(objects, null); } + public static String joinHomogenous(List objects, Function accessor) { + int remaining = objects.size(); + StringBuilder sb = new StringBuilder(); + for(T obj : objects) { + remaining--; + if( accessor != null ) + sb.append(accessor.apply(obj)); + else + sb.append(obj); + if( remaining > 1 ) sb.append(", "); + if( remaining == 1 ) sb.append(" and "); + } + return sb.toString(); + } } From 5b98f34a6dd2d5de98ab10cdd251bc5c9781c883 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 06:01:57 +0000 Subject: [PATCH 108/123] focus on OK after creature selection --- src/main/java/forge/control/input/InputAttack.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/forge/control/input/InputAttack.java b/src/main/java/forge/control/input/InputAttack.java index ae75e889a74..55367ce8f11 100644 --- a/src/main/java/forge/control/input/InputAttack.java +++ b/src/main/java/forge/control/input/InputAttack.java @@ -120,6 +120,7 @@ public class InputAttack extends InputBase { // just to make sure the attack symbol is marked human.getZone(ZoneType.Battlefield).updateObservers(); CombatUtil.showCombat(); + ButtonUtil.enableOnlyOk(); } else { SDisplayUtil.remind(VMessage.SINGLETON_INSTANCE); From e518027ff5dad1f259dad29d90b541042e762eab Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 06:12:33 +0000 Subject: [PATCH 109/123] combineCosts - bugfix --- src/main/java/forge/card/cost/CostUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/forge/card/cost/CostUtil.java b/src/main/java/forge/card/cost/CostUtil.java index ae8ee6f676e..c261d0def7a 100644 --- a/src/main/java/forge/card/cost/CostUtil.java +++ b/src/main/java/forge/card/cost/CostUtil.java @@ -136,6 +136,7 @@ public class CostUtil { boolean xCanBe0 = ((CostPartMana) part).canXbe0() && costPart2.canXbe0(); oldManaCost.combineManaCost(costPart2.getMana()); + cost2.getCostParts().remove(costPart2); cost2.getCostParts().add(0, new CostPartMana(oldManaCost.toManaCost(), !xCanBe0)); } else { cost2.getCostParts().add(part); From 81243788533bd6d08127c185f1b9bee05ba255dc Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 06:28:39 +0000 Subject: [PATCH 110/123] limited announce list to 9 due to harder selection of numbers above 10 --- src/main/java/forge/game/player/PlayerControllerHuman.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index d9b674a5604..39081958b49 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -213,7 +213,7 @@ public class PlayerControllerHuman extends PlayerController { @Override public Integer announceRequirements(SpellAbility ability, String announce, boolean canChooseZero) { List options = new ArrayList(); - for(int i = canChooseZero ? 0 : 1; i <= 20; i++) + for(int i = canChooseZero ? 0 : 1; i < 10; i++) options.add(Integer.valueOf(i)); options.add("Other amount"); From 27ae91a1cfeb1438ec31da244f937e282527e1be Mon Sep 17 00:00:00 2001 From: Sloth Date: Thu, 4 Apr 2013 06:59:03 +0000 Subject: [PATCH 111/123] - Cleanup of Combat class. - Added some infrastructure for "BecomesBlocked" effects. --- src/main/java/forge/game/phase/Combat.java | 35 ++++++++----------- .../java/forge/game/phase/CombatUtil.java | 16 --------- 2 files changed, 15 insertions(+), 36 deletions(-) diff --git a/src/main/java/forge/game/phase/Combat.java b/src/main/java/forge/game/phase/Combat.java index acc7d96ad0e..8bbede8cdad 100644 --- a/src/main/java/forge/game/phase/Combat.java +++ b/src/main/java/forge/game/phase/Combat.java @@ -51,7 +51,7 @@ public class Combat { private final Map> blockerMap = new TreeMap>(); private final Set blocked = new HashSet(); - private final HashMap> unblockedMap = new HashMap>(); + private final Set unblocked = new HashSet(); private final HashMap defendingDamageMap = new HashMap(); // Defenders are the Defending Player + Each controlled Planeswalker @@ -83,7 +83,7 @@ public class Combat { this.resetAttackers(); this.blocked.clear(); - this.unblockedMap.clear(); + this.unblocked.clear(); this.defendingDamageMap.clear(); this.attackingPlayer = null; @@ -453,6 +453,13 @@ public class Combat { return this.blocked.contains(attacker); } + public final void setBlocked(final Card attacker) { + if (!this.blocked.contains(attacker)) { + this.blocked.add(attacker); + this.unblocked.remove(attacker); + } + } + /** *

* addBlocker. @@ -771,8 +778,6 @@ public class Combat { public void dealAssignedDamage() { // This function handles both Regular and First Strike combat assignment - final boolean bFirstStrike = Singletons.getModel().getGame().getPhaseHandler().is(PhaseType.COMBAT_FIRST_STRIKE_DAMAGE); - final HashMap defMap = this.getDefendingDamageMap(); for (final Entry entry : defMap.entrySet()) { @@ -784,18 +789,6 @@ public class Combat { } } - final List unblocked = new ArrayList(bFirstStrike ? this.getUnblockedAttackers() : this.getUnblockedFirstStrikeAttackers()); - - for (int j = 0; j < unblocked.size(); j++) { - if (bFirstStrike) { - CombatUtil.checkUnblockedAttackers(unblocked.get(j)); - } else { - if (!unblocked.get(j).hasFirstStrike() && !unblocked.get(j).hasDoubleStrike()) { - CombatUtil.checkUnblockedAttackers(unblocked.get(j)); - } - } - } - // this can be much better below here... final List combatants = new ArrayList(); @@ -840,7 +833,7 @@ public class Combat { * @return a boolean. */ public final boolean isUnblocked(final Card att) { - return this.unblockedMap.containsKey(att); + return this.unblocked.contains(att); } /** @@ -852,7 +845,7 @@ public class Combat { */ public final List getUnblockedAttackers() { final List out = new ArrayList(); - for (Card c : this.unblockedMap.keySet()) { + for (Card c : this.unblocked) { if (!c.hasFirstStrike()) { out.add(c); } @@ -869,7 +862,7 @@ public class Combat { */ public final List getUnblockedFirstStrikeAttackers() { final List out = new ArrayList(); - for (Card c : this.unblockedMap.keySet()) { // only add creatures without firstStrike to this + for (Card c : this.unblocked) { // only add creatures without firstStrike to this if (c.hasFirstStrike() || c.hasDoubleStrike()) { out.add(c); } @@ -886,7 +879,9 @@ public class Combat { * a {@link forge.Card} object. */ public final void addUnblockedAttacker(final Card c) { - this.unblockedMap.put(c, new ArrayList()); + if (!this.unblocked.contains(c)) { + this.unblocked.add(c); + } } public boolean isPlayerAttacked(Player priority) { diff --git a/src/main/java/forge/game/phase/CombatUtil.java b/src/main/java/forge/game/phase/CombatUtil.java index ef107e4a8a9..f1e674fe80e 100644 --- a/src/main/java/forge/game/phase/CombatUtil.java +++ b/src/main/java/forge/game/phase/CombatUtil.java @@ -1293,22 +1293,6 @@ public class CombatUtil { c.getController().incrementAttackersDeclaredThisTurn(); } // checkDeclareAttackers - /** - *

- * checkUnblockedAttackers. - *

- * - * @param c - * a {@link forge.Card} object. - */ - public static void checkUnblockedAttackers(final Card c) { - - // Run triggers - final HashMap runParams = new HashMap(); - runParams.put("Card", c); - Singletons.getModel().getGame().getTriggerHandler().runTrigger(TriggerType.AttackerUnblocked, runParams, false); - } - /** *

* checkDeclareBlockers. From 0dc4bb0f1048bd8c7e375597f4455e2b3aa37bc8 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 07:10:56 +0000 Subject: [PATCH 112/123] stack descriptions for spells with X in cost fixed (X is displayed correctly) --- .../java/forge/card/cardfactory/CardFactoryUtil.java | 3 +++ .../forge/card/spellability/HumanPlaySpellAbility.java | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index 891725d07a4..fb3f882ed9c 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -1108,6 +1108,9 @@ public class CardFactoryUtil { */ public static int xCount(final Card c, final String expression) { int n = 0; + + if (StringUtils.isNumeric(expression)) + return Integer.parseInt(expression); final Player cardController = c.getController(); final Player oppController = cardController.getOpponent(); diff --git a/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java index 9742dc39b39..52d775b435f 100644 --- a/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java +++ b/src/main/java/forge/card/spellability/HumanPlaySpellAbility.java @@ -23,7 +23,6 @@ import org.apache.commons.lang3.StringUtils; import forge.Card; import forge.CardCharacteristicName; -import forge.Singletons; import forge.card.ability.AbilityUtils; import forge.card.cost.CostPayment; import forge.game.GameState; @@ -48,7 +47,7 @@ public class HumanPlaySpellAbility { public final void fillRequirements(boolean isAlreadyTargeted, boolean isFree, boolean skipStack) { - final GameState game = Singletons.getModel().getGame(); + final GameState game = ability.getActivatingPlayer().getGame(); // used to rollback Zone fromZone = null; @@ -128,7 +127,8 @@ public class HumanPlaySpellAbility { private void rollbackAbility(Zone fromZone, int zonePosition) { // cancel ability during target choosing - final Card c = this.ability.getSourceCard(); + final GameState game = ability.getActivatingPlayer().getGame(); + final Card c = ability.getSourceCard(); // split cards transform back to full form if targeting is canceled if (c.isSplitCard()) { @@ -137,14 +137,14 @@ public class HumanPlaySpellAbility { if (fromZone != null) { // and not a copy // add back to where it came from - Singletons.getModel().getGame().getAction().moveTo(fromZone, c, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); + game.getAction().moveTo(fromZone, c, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); } clearTargets(ability); this.ability.resetOnceResolved(); this.payment.refundPayment(); - Singletons.getModel().getGame().getStack().clearFrozen(); + game.getStack().clearFrozen(); // Singletons.getModel().getGame().getStack().removeFromFrozenStack(this.ability); } From 969ab15f534dabb7f4bf3309148ba5e23f3c9492 Mon Sep 17 00:00:00 2001 From: swordshine Date: Thu, 4 Apr 2013 10:29:20 +0000 Subject: [PATCH 113/123] - Added some planes from PC2 set --- .gitattributes | 7 +++++++ res/cardsfolder/a/astral_arena.txt | 10 ++++++++++ res/cardsfolder/b/bloodhill_bastion.txt | 11 +++++++++++ res/cardsfolder/g/gavony.txt | 9 +++++++++ res/cardsfolder/h/hedron_fields_of_agadeem.txt | 9 +++++++++ res/cardsfolder/k/kessig.txt | 10 ++++++++++ res/cardsfolder/n/nephalia.txt | 12 ++++++++++++ res/cardsfolder/t/the_zephyr_maze.txt | 10 ++++++++++ 8 files changed, 78 insertions(+) create mode 100644 res/cardsfolder/a/astral_arena.txt create mode 100644 res/cardsfolder/b/bloodhill_bastion.txt create mode 100644 res/cardsfolder/g/gavony.txt create mode 100644 res/cardsfolder/h/hedron_fields_of_agadeem.txt create mode 100644 res/cardsfolder/k/kessig.txt create mode 100644 res/cardsfolder/n/nephalia.txt create mode 100644 res/cardsfolder/t/the_zephyr_maze.txt diff --git a/.gitattributes b/.gitattributes index 1ef95e0ce67..b43c75ebcce 100644 --- a/.gitattributes +++ b/.gitattributes @@ -559,6 +559,7 @@ res/cardsfolder/a/assemble_the_legion.txt -text res/cardsfolder/a/assembly_hall.txt -text res/cardsfolder/a/assembly_worker.txt svneol=native#text/plain res/cardsfolder/a/assert_authority.txt svneol=native#text/plain +res/cardsfolder/a/astral_arena.txt -text res/cardsfolder/a/astral_slide.txt svneol=native#text/plain res/cardsfolder/a/astral_steel.txt svneol=native#text/plain res/cardsfolder/a/astrolabe.txt svneol=native#text/plain @@ -1078,6 +1079,7 @@ res/cardsfolder/b/bloodfray_giant.txt -text res/cardsfolder/b/bloodghast.txt svneol=native#text/plain res/cardsfolder/b/bloodgift_demon.txt -text res/cardsfolder/b/bloodhall_ooze.txt svneol=native#text/plain +res/cardsfolder/b/bloodhill_bastion.txt -text res/cardsfolder/b/bloodhunter_bat.txt -text res/cardsfolder/b/bloodhusk_ritualist.txt svneol=native#text/plain res/cardsfolder/b/bloodied_ghost.txt svneol=native#text/plain @@ -4024,6 +4026,7 @@ res/cardsfolder/g/gather_the_townsfolk.txt -text res/cardsfolder/g/gatherer_of_graces.txt svneol=native#text/plain res/cardsfolder/g/gatstaf_shepherd_gatstaf_howler.txt -text res/cardsfolder/g/gauntlet_of_might.txt svneol=native#text/plain +res/cardsfolder/g/gavony.txt -text res/cardsfolder/g/gavony_ironwright.txt -text res/cardsfolder/g/gavony_township.txt -text res/cardsfolder/g/gaze_of_adamaro.txt svneol=native#text/plain @@ -4813,6 +4816,7 @@ res/cardsfolder/h/heckling_fiends.txt -text res/cardsfolder/h/hedge_troll.txt svneol=native#text/plain res/cardsfolder/h/hedron_crab.txt svneol=native#text/plain res/cardsfolder/h/hedron_field_purists.txt -text +res/cardsfolder/h/hedron_fields_of_agadeem.txt -text res/cardsfolder/h/hedron_matrix.txt svneol=native#text/plain res/cardsfolder/h/hedron_rover.txt svneol=native#text/plain res/cardsfolder/h/hedron_scrabbler.txt svneol=native#text/plain @@ -5645,6 +5649,7 @@ res/cardsfolder/k/kemba_kha_regent.txt svneol=native#text/plain res/cardsfolder/k/kembas_legion.txt -text res/cardsfolder/k/kembas_skyguard.txt svneol=native#text/plain res/cardsfolder/k/kemuri_onna.txt svneol=native#text/plain +res/cardsfolder/k/kessig.txt -text res/cardsfolder/k/kessig_cagebreakers.txt -text res/cardsfolder/k/kessig_malcontents.txt -text res/cardsfolder/k/kessig_recluse.txt -text @@ -7144,6 +7149,7 @@ res/cardsfolder/n/nemata_grove_guardian.txt svneol=native#text/plain res/cardsfolder/n/nemesis_mask.txt svneol=native#text/plain res/cardsfolder/n/nemesis_of_reason.txt svneol=native#text/plain res/cardsfolder/n/nemesis_trap.txt -text +res/cardsfolder/n/nephalia.txt -text res/cardsfolder/n/nephalia_drownyard.txt -text res/cardsfolder/n/nephalia_seakite.txt -text res/cardsfolder/n/nephalia_smuggler.txt -text @@ -10880,6 +10886,7 @@ res/cardsfolder/t/the_tabernacle_at_pendrell_vale.txt svneol=native#text/plain res/cardsfolder/t/the_unspeakable.txt svneol=native#text/plain res/cardsfolder/t/the_very_soil_shall_shake.txt -text res/cardsfolder/t/the_wretched.txt -text svneol=unset#text/plain +res/cardsfolder/t/the_zephyr_maze.txt -text res/cardsfolder/t/theft_of_dreams.txt svneol=native#text/plain res/cardsfolder/t/thelon_of_havenwood.txt -text res/cardsfolder/t/thelonite_druid.txt -text diff --git a/res/cardsfolder/a/astral_arena.txt b/res/cardsfolder/a/astral_arena.txt new file mode 100644 index 00000000000..02feea43cb2 --- /dev/null +++ b/res/cardsfolder/a/astral_arena.txt @@ -0,0 +1,10 @@ +Name:Astral Arena +ManaCost:no cost +Types:Plane Kolbahan +S:Mode$ Continuous | EffectZone$ Command | GlobalRule$ No more than one creature can attack each combat. | Description$ No more than one creature can attack each combat. +S:Mode$ Continuous | EffectZone$ Command | GlobalRule$ No more than one creature can block each combat. | Description$ No more than one creature can block each combat. +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, CARDNAME deals 2 damage to each creature. +SVar:RolledChaos:AB$ DamageAll | Cost$ 0 | NumDmg$ 2 | ValidCards$ Creature | ValidDescription$ each creature. +SVar:Picture:http://www.wizards.com/global/images/magic/general/astral_arena.jpg +Oracle:No more than one creature can attack each combat.\nNo more than one creature can block each combat.\nWhenever you roll {C}, Astral Arena deals 2 damage to each creature. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/b/bloodhill_bastion.txt b/res/cardsfolder/b/bloodhill_bastion.txt new file mode 100644 index 00000000000..452fc00a54d --- /dev/null +++ b/res/cardsfolder/b/bloodhill_bastion.txt @@ -0,0 +1,11 @@ +Name:Bloodhill Bastion +ManaCost:no cost +Types:Plane Equilor +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | TriggerZones$ Command | ValidCard$ Creature | Execute$ TrigPump | TriggerDescription$ Whenever a creature enters the battlefield, it gains double strike and haste until end of turn. +SVar:TrigPump:AB$ Pump | Cost$ 0 | Defined$ TriggeredCard | KW$ Double Strike & Haste +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, exile target nontoken creature you control, then return it to the battlefield under your control. +SVar:RolledChaos:AB$ ChangeZone | Cost$ 0 | ValidTgts$ Creature.nonToken+YouCtrl | TgtPrompt$ Select target non-Token creature you control | Origin$ Battlefield | Destination$ Exile | RememberTargets$ True | ForgetOtherTargets$ True | SubAbility$ RestorationReturn +SVar:RestorationReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | GainControl$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/bloodhill_bastion.jpg +Oracle:Whenever a creature enters the battlefield, it gains double strike and haste until end of turn.\nWhenever you roll {C}, exile target nontoken creature you control, then return it to the battlefield under your control. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/g/gavony.txt b/res/cardsfolder/g/gavony.txt new file mode 100644 index 00000000000..084f42ebde9 --- /dev/null +++ b/res/cardsfolder/g/gavony.txt @@ -0,0 +1,9 @@ +Name:Gavony +ManaCost:no cost +Types:Plane Innistrad +S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature | AddKeyword$ Vigilance | Description$ All creatures have vigilance. +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, creatures you control are indestructible this turn. +SVar:RolledChaos:AB$ PumpAll | Cost$ 0 | ValidCards$ Creature.YouCtrl | KW$ HIDDEN Indestructible +SVar:Picture:http://www.wizards.com/global/images/magic/general/gavony.jpg +Oracle:All creatures have vigilance.\nWhenever you roll {C}, creatures you control are indestructible this turn. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/h/hedron_fields_of_agadeem.txt b/res/cardsfolder/h/hedron_fields_of_agadeem.txt new file mode 100644 index 00000000000..42e770b6a9e --- /dev/null +++ b/res/cardsfolder/h/hedron_fields_of_agadeem.txt @@ -0,0 +1,9 @@ +Name:Hedron Fields of Agadeem +ManaCost:no cost +Types:Plane Zendikar +S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.powerGE7 | AddHiddenKeyword$ CARDNAME can't attack or block. | Description$ Creatures with power 7 or greater can't attack or block. +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, put a 7/7 colorless Eldrazi creature token with annihilator 1 onto the battlefield. (Whenever it attacks, defending player sacrifices a permanent.) +SVar:RolledChaos:AB$ Token | Cost$ 0 | TokenAmount$ 1 | TokenName$ Eldrazi | TokenTypes$ Creature,Eldrazi | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 7 | TokenToughness$ 7 | TokenKeywords$ Annihilator 1 +SVar:Picture:http://www.wizards.com/global/images/magic/general/hedron_fields_of_agadeem.jpg +Oracle:Creatures with power 7 or greater can't attack or block.\nWhenever you roll {C}, put a 7/7 colorless Eldrazi creature token with annihilator 1 onto the battlefield. (Whenever it attacks, defending player sacrifices a permanent.) +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/k/kessig.txt b/res/cardsfolder/k/kessig.txt new file mode 100644 index 00000000000..8d5ef288522 --- /dev/null +++ b/res/cardsfolder/k/kessig.txt @@ -0,0 +1,10 @@ +Name:Kessig +ManaCost:no cost +Types:Plane Innistrad +S:Mode$ Continuous | Affected$ Creature.nonWerewolf | EffectZone$ Command | AddHiddenKeyword$ Prevent all combat damage that would be dealt by CARDNAME. | Description$ Prevent all combat damage that would be dealt by non-Werewolf creatures. +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, each creature you control gets +2/+2, gains trample, and becomes a Werewolf in addition to its other types until end of turn. +SVar:RolledChaos:AB$ AnimateAll | Cost$ 0 | ValidCards$ Creature.YouCtrl | Types$ Werewolf | SubAbility$ DBPump +SVar:DBPump:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Trample | NumAtt$ 2 | NumDef$ 2 +SVar:Picture:http://www.wizards.com/global/images/magic/general/kessig.jpg +Oracle:Prevent all combat damage that would be dealt by non-Werewolf creatures.\nWhenever you roll {C}, each creature you control gets +2/+2, gains trample, and becomes a Werewolf in addition to its other types until end of turn. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/n/nephalia.txt b/res/cardsfolder/n/nephalia.txt new file mode 100644 index 00000000000..4fdc48d2a96 --- /dev/null +++ b/res/cardsfolder/n/nephalia.txt @@ -0,0 +1,12 @@ +Name:Nephalia +ManaCost:no cost +Types:Plane Innistrad +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Command | Execute$ TrigMill | TriggerDescription$ At the beginning of your end step, put the top seven cards of your library into your graveyard. Then return a card at random from your graveyard to your hand. +SVar:TrigMill:AB$ Mill | Cost$ 0 | NumCards$ 7 | SubAbility$ DBRandom +SVar:DBRandom:DB$ ChooseCard | Choices$ Card.YouOwn | ChoiceZone$ Graveyard | AtRandom$ True | Amount$ 1 | SubAbility$ DBReturn +SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | Defined$ ChosenCard +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, return target card from your graveyard to your hand. +SVar:RolledChaos:AB$ ChangeZone | Cost$ 0 | Origin$ Graveyard | Destination$ Hand | TgtPrompt$ Select target card in your graveyard | ValidTgts$ Card.YouOwn +SVar:Picture:http://www.wizards.com/global/images/magic/general/nephalia.jpg +Oracle:At the beginning of your end step, put the top seven cards of your library into your graveyard. Then return a card at random from your graveyard to your hand.\nWhenever you roll {C}, return target card from your graveyard to your hand. +SetInfo:PC2 Common \ No newline at end of file diff --git a/res/cardsfolder/t/the_zephyr_maze.txt b/res/cardsfolder/t/the_zephyr_maze.txt new file mode 100644 index 00000000000..3b8edb3407a --- /dev/null +++ b/res/cardsfolder/t/the_zephyr_maze.txt @@ -0,0 +1,10 @@ +Name:The Zephyr Maze +ManaCost:no cost +Types:Plane Kyneth +S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.withFlying | AddPower$ 2 | Description$ Creatures with flying get +2/+0. +S:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.withoutFlying | AddPower$ -2 | Description$ Creatures without flying get -2/-0. +T:Mode$ PlanarDice | Result$ Chaos | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever you roll Chaos, target creature gains flying until end of turn. +SVar:RolledChaos:AB$ Pump | Cost$ 0 | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Flying +SVar:Picture:http://www.wizards.com/global/images/magic/general/the_zephyr_maze.jpg +Oracle:Creatures with flying get +2/+0.\nCreatures without flying get -2/-0.\nWhenever you roll {C}, target creature gains flying until end of turn. +SetInfo:PC2 Common \ No newline at end of file From 4e57d64659481ff31ab75be345c8406a29bd7a89 Mon Sep 17 00:00:00 2001 From: swordshine Date: Thu, 4 Apr 2013 10:31:03 +0000 Subject: [PATCH 114/123] - Converted Nebuchadnezzar and Brilliant Ultimatum --- res/cardsfolder/b/brilliant_ultimatum.txt | 8 +- res/cardsfolder/n/nebuchadnezzar.txt | 5 +- .../cardfactory/CardFactoryCreatures.java | 79 -------- .../cardfactory/CardFactorySorceries.java | 187 +----------------- 4 files changed, 12 insertions(+), 267 deletions(-) diff --git a/res/cardsfolder/b/brilliant_ultimatum.txt b/res/cardsfolder/b/brilliant_ultimatum.txt index ca4db3d50fa..da052cd2587 100644 --- a/res/cardsfolder/b/brilliant_ultimatum.txt +++ b/res/cardsfolder/b/brilliant_ultimatum.txt @@ -1,7 +1,13 @@ Name:Brilliant Ultimatum ManaCost:W W U U U B B Types:Sorcery -Text:Exile the top five cards of your library. An opponent separates those cards into two piles. You may play any number of cards from one of those piles without paying their mana costs. +A:SP$ Mill | Cost$ W W U U U B B | Defined$ You | NumCards$ 5 | Destination$ Exile | RememberMilled$ True | SubAbility$ DBTwoPiles | SpellDescription$ Exile the top five cards of your library. An opponent separates those cards into two piles. You may play any number of cards from one of those piles without paying their mana costs. +SVar:DBTwoPiles:DB$ TwoPiles | Defined$ You | DefinedCards$ Remembered | Separator$ Opponent | ChosenPile$ DBPlay | SubAbility$ DBCleanup +SVar:DBPlay:DB$ RepeatEach | UseImprinted$ True | RepeatCards$ Card.IsRemembered | ChooseOrder$ True | Zone$ Exile | RepeatSubAbility$ DBPlayCard +SVar:DBPlayCard:DB$ Play | Defined$ Imprinted | Controller$ You | WithoutManaCost$ True | Optional$ True +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Y:Count$InYourLibrary +SVar:NeedsToPlayVar:Y GE8 SVar:Picture:http://www.wizards.com/global/images/magic/general/brilliant_ultimatum.jpg Oracle:Exile the top five cards of your library. An opponent separates those cards into two piles. You may play any number of cards from one of those piles without paying their mana costs. SetInfo:ALA Rare \ No newline at end of file diff --git a/res/cardsfolder/n/nebuchadnezzar.txt b/res/cardsfolder/n/nebuchadnezzar.txt index ab711332369..d5a64f06c47 100644 --- a/res/cardsfolder/n/nebuchadnezzar.txt +++ b/res/cardsfolder/n/nebuchadnezzar.txt @@ -2,7 +2,10 @@ Name:Nebuchadnezzar ManaCost:3 U B Types:Legendary Creature Human Wizard PT:3/3 -#This is necessary for cost payment, but not used. +A:AB$ NameCard | Cost$ X T | Defined$ You | SubAbility$ DBReveal | PlayerTurn$ True | SpellDescription$ Name a card. Target opponent reveals X cards at random from his or her hand. Then that player discards all cards with that name revealed this way. Activate this ability only during your turn. +SVar:DBReveal:DB$ Reveal | ValidTgts$ Opponent | Random$ True | NumCards$ X | References$ X | RememberRevealed$ True | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | DefinedCards$ ValidHand Card.IsRemembered+NamedCard | Defined$ Targeted | Mode$ Defined | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$xPaid SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/nebuchadnezzar.jpg diff --git a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java index 86158d76789..99a31805572 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryCreatures.java @@ -528,83 +528,6 @@ public class CardFactoryCreatures { card.addTrigger(myTrigger); } - private static void getCard_Nebuchadnezzar(final Card card, final String cardName) { - /* - * X, T: Name a card. Target opponent reveals X cards at random from - * his or her hand. Then that player discards all cards with that - * name revealed this way. Activate this ability only during your - * turn. - */ - final Cost abCost = new Cost(card, "X T", true); - final Target target = new Target(card, "Select target opponent", "Opponent".split(",")); - class NebuchadnezzarAbility extends AbilityActivated { - public NebuchadnezzarAbility(final Card ca, final Cost co, final Target t) { - super(ca, co, t); - } - - @Override - public AbilityActivated getCopy() { - AbilityActivated discard = new NebuchadnezzarAbility(getSourceCard(), - getPayCosts(), new Target(getTarget())); - discard.getRestrictions().setPlayerTurn(true); - return discard; - } - - private static final long serialVersionUID = 4839778470534392198L; - - @Override - public void resolve() { - // name a card - final String choice = JOptionPane.showInputDialog(null, "Name a card", cardName, - JOptionPane.QUESTION_MESSAGE); - final List hand = new ArrayList(this.getTargetPlayer().getCardsIn(ZoneType.Hand)); - int numCards = card.getXManaCostPaid(); - numCards = Math.min(hand.size(), numCards); - - final List revealed = new ArrayList(); - for (int i = 0; i < numCards; i++) { - final Card random = Aggregates.random(hand); - revealed.add(random); - hand.remove(random); - } - if (!revealed.isEmpty()) { - GuiChoose.one("Revealed at random", revealed); - } else { - GuiChoose.one("Revealed at random", new String[] { "Nothing to reveal" }); - } - - for (final Card c : revealed) { - if (c.getName().equals(choice)) { - c.getController().discard(c, this); - } - } - } - - @Override - public boolean canPlayAI() { - return false; - } - - @Override - public String getDescription() { - final StringBuilder sbDesc = new StringBuilder(); - sbDesc.append(abCost).append("Name a card. "); - sbDesc.append("Target opponent reveals X cards at random from his or her hand. "); - sbDesc.append("Then that player discards all cards with that name revealed this way. "); - sbDesc.append("Activate this ability only during your turn."); - return sbDesc.toString(); - } - } - final AbilityActivated discard = new NebuchadnezzarAbility(card, abCost, target); - - discard.getRestrictions().setPlayerTurn(true); - - final StringBuilder sbStack = new StringBuilder(); - sbStack.append(cardName).append(" - name a card."); - discard.setStackDescription(sbStack.toString()); - - card.addSpellAbility(discard); - } // // This is a hardcoded card template @@ -628,8 +551,6 @@ public class CardFactoryCreatures { getCard_SurturedGhoul(card); } else if (cardName.equals("Phyrexian Dreadnought")) { getCard_PhyrexianDreadnought(card, cardName); - } else if (cardName.equals("Nebuchadnezzar")) { - getCard_Nebuchadnezzar(card, cardName); } // *************************************************** diff --git a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java index c7d2539eba0..e50a7545de9 100644 --- a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java +++ b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java @@ -61,190 +61,6 @@ import forge.util.Aggregates; */ public class CardFactorySorceries { - private static final SpellAbility getBrilliantUltimatum(final Card card) { - return new Spell(card) { - private static final long serialVersionUID = 1481112451519L; - - @Override - public void resolve() { - - Card choice = null; - - // check for no cards in hand on resolve - final List lib = card.getController().getCardsIn(ZoneType.Library); - final List cards = new ArrayList(); - final List exiled = new ArrayList(); - if (lib.size() == 0) { - JOptionPane.showMessageDialog(null, "No more cards in library.", "", - JOptionPane.INFORMATION_MESSAGE); - return; - } - int count = 5; - if (lib.size() < 5) { - count = lib.size(); - } - for (int i = 0; i < count; i++) { - cards.add(lib.get(i)); - } - for (int i = 0; i < count; i++) { - exiled.add(lib.get(i)); - Singletons.getModel().getGame().getAction().exile(lib.get(i)); - } - final List pile1 = new ArrayList(); - final List pile2 = new ArrayList(); - boolean stop = false; - int pile1CMC = 0; - int pile2CMC = 0; - - final StringBuilder msg = new StringBuilder(); - msg.append("Revealing top ").append(count).append(" cards of library: "); - GuiChoose.one(msg.toString(), cards); - // Human chooses - if (card.getController().isComputer()) { - for (int i = 0; i < count; i++) { - if (!stop) { - choice = GuiChoose.oneOrNone("Choose cards to put into the first pile: ", - cards); - if (choice != null) { - pile1.add(choice); - cards.remove(choice); - pile1CMC = pile1CMC + choice.getCMC(); - } else { - stop = true; - } - } - } - for (int i = 0; i < count; i++) { - if (!pile1.contains(exiled.get(i))) { - pile2.add(exiled.get(i)); - pile2CMC = pile2CMC + exiled.get(i).getCMC(); - } - } - final StringBuilder sb = new StringBuilder(); - sb.append("You have spilt the cards into the following piles"); - sb.append("\r\n").append("\r\n"); - sb.append("Pile 1: ").append("\r\n"); - for (int i = 0; i < pile1.size(); i++) { - sb.append(pile1.get(i).getName()).append("\r\n"); - } - sb.append("\r\n").append("Pile 2: ").append("\r\n"); - for (int i = 0; i < pile2.size(); i++) { - sb.append(pile2.get(i).getName()).append("\r\n"); - } - JOptionPane.showMessageDialog(null, sb, "", JOptionPane.INFORMATION_MESSAGE); - if (pile1CMC >= pile2CMC) { - JOptionPane.showMessageDialog(null, "Computer chooses the Pile 1", "", - JOptionPane.INFORMATION_MESSAGE); - for (int i = 0; i < pile1.size(); i++) { - final List choices = pile1.get(i).getBasicSpells(); - - for (final SpellAbility sa : choices) { - if (sa.canPlayAI()) { - ComputerUtil.playStackFree(sa.getActivatingPlayer(), sa); - if (pile1.get(i).isPermanent()) { - exiled.remove(pile1.get(i)); - } - break; - } - } - } - } else { - JOptionPane.showMessageDialog(null, "Computer chooses the Pile 2", "", - JOptionPane.INFORMATION_MESSAGE); - for (int i = 0; i < pile2.size(); i++) { - final List choices = pile2.get(i).getBasicSpells(); - - for (final SpellAbility sa : choices) { - if (sa.canPlayAI()) { - ComputerUtil.playStackFree(sa.getActivatingPlayer(), sa); - if (pile2.get(i).isPermanent()) { - exiled.remove(pile2.get(i)); - } - break; - } - } - } - } - - } else { // Computer chooses (It picks the highest converted - // mana cost card and 1 random card.) - Card biggest = exiled.get(0); - - for (final Card c : exiled) { - if (biggest.getManaCost().getCMC() < c.getManaCost().getCMC()) { - biggest = c; - } - } - - pile1.add(biggest); - cards.remove(biggest); - if (cards.size() > 2) { - final Card random = Aggregates.random(cards); - pile1.add(random); - } - for (int i = 0; i < count; i++) { - if (!pile1.contains(exiled.get(i))) { - pile2.add(exiled.get(i)); - } - } - final StringBuilder sb = new StringBuilder(); - sb.append("Choose a pile to add to your hand: "); - sb.append("\r\n").append("\r\n"); - sb.append("Pile 1: ").append("\r\n"); - for (int i = 0; i < pile1.size(); i++) { - sb.append(pile1.get(i).getName()).append("\r\n"); - } - sb.append("\r\n").append("Pile 2: ").append("\r\n"); - for (int i = 0; i < pile2.size(); i++) { - sb.append(pile2.get(i).getName()).append("\r\n"); - } - final Object[] possibleValues = { "Pile 1", "Pile 2" }; - final Object q = JOptionPane.showOptionDialog(null, sb, "Brilliant Ultimatum", - JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, possibleValues, - possibleValues[0]); - - List chosen; - if (q.equals(0)) { - chosen = pile1; - } else { - chosen = pile2; - } - - final int numChosen = chosen.size(); - for (int i = 0; i < numChosen; i++) { - final Card check = GuiChoose.oneOrNone("Select spells to play in reverse order: ", chosen); - if (check == null) { - break; - } - - final Card playing = check; - if (playing.isLand()) { - if (card.getController().canPlayLand(playing)) { - card.getController().playLand(playing); - } else { - JOptionPane.showMessageDialog(null, "You can't play any more lands this turn.", "", - JOptionPane.INFORMATION_MESSAGE); - } - } else { - ((HumanPlayer)card.getController()).playCardWithoutManaCost(playing); - } - chosen.remove(playing); - } - - } - pile1.clear(); - pile2.clear(); - } // resolve() - - @Override - public boolean canPlayAI() { - final List cards = getActivatingPlayer().getCardsIn(ZoneType.Library); - return cards.size() >= 8; - } - }; // SpellAbility - - } - private static final void balanceLands(Spell card) { List> lands = new ArrayList>(); @@ -506,8 +322,7 @@ public class CardFactorySorceries { public static void buildCard(final Card card, final String cardName) { - if (cardName.equals("Brilliant Ultimatum")) { card.addSpellAbility(getBrilliantUltimatum(card)); - } else if (cardName.equals("Balance")) { card.addSpellAbility(getBalance(card)); + if (cardName.equals("Balance")) { card.addSpellAbility(getBalance(card)); } else if (cardName.equals("Patriarch's Bidding")) { card.addSpellAbility(getPatriarchsBidding(card)); } else if (cardName.equals("Transmute Artifact")) { card.addSpellAbility(getTransmuteArtifact(card)); } From 4ab1ace4d3578c95a2cd7986a0c869b30b0285a9 Mon Sep 17 00:00:00 2001 From: swordshine Date: Thu, 4 Apr 2013 10:39:07 +0000 Subject: [PATCH 115/123] - vanguard: Added Kresh the Bloodbraided Avatar - Added Karmic Justice --- .gitattributes | 4 + res/cardsfolder/k/karmic_justice.txt | 6 ++ .../k/kresh_the_bloodbraided_avatar.txt | 10 +++ src/main/java/forge/Card.java | 2 +- .../ability/effects/ControlGainEffect.java | 4 +- .../card/ability/effects/CounterEffect.java | 2 +- .../ability/effects/DestroyAllEffect.java | 4 +- .../card/ability/effects/DestroyEffect.java | 8 +- .../card/ability/effects/SacrificeEffect.java | 2 +- .../card/cardfactory/CardFactoryUtil.java | 8 ++ .../forge/card/trigger/TriggerDestroyed.java | 85 +++++++++++++++++++ .../forge/card/trigger/TriggerDevoured.java | 79 +++++++++++++++++ .../java/forge/card/trigger/TriggerType.java | 2 + src/main/java/forge/game/GameAction.java | 15 ++-- src/main/java/forge/game/GameActionUtil.java | 4 +- src/main/java/forge/game/phase/EndOfTurn.java | 4 +- src/main/java/forge/game/phase/Upkeep.java | 12 +-- src/main/java/forge/gui/GuiDisplayUtil.java | 2 +- 18 files changed, 226 insertions(+), 27 deletions(-) create mode 100644 res/cardsfolder/k/karmic_justice.txt create mode 100644 res/cardsfolder/k/kresh_the_bloodbraided_avatar.txt create mode 100644 src/main/java/forge/card/trigger/TriggerDestroyed.java create mode 100644 src/main/java/forge/card/trigger/TriggerDevoured.java diff --git a/.gitattributes b/.gitattributes index b43c75ebcce..4b707c32b3c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5565,6 +5565,7 @@ res/cardsfolder/k/karakas.txt svneol=native#text/plain res/cardsfolder/k/kargan_dragonlord.txt svneol=native#text/plain res/cardsfolder/k/karma.txt svneol=native#text/plain res/cardsfolder/k/karmic_guide.txt svneol=native#text/plain +res/cardsfolder/k/karmic_justice.txt -text res/cardsfolder/k/karn.txt -text res/cardsfolder/k/karn_liberated.txt -text res/cardsfolder/k/karn_silver_golem.txt svneol=native#text/plain @@ -5814,6 +5815,7 @@ res/cardsfolder/k/krark_clan_stoker.txt svneol=native#text/plain res/cardsfolder/k/krenko_mob_boss.txt -text res/cardsfolder/k/krenkos_command.txt -text res/cardsfolder/k/kresh_the_bloodbraided.txt svneol=native#text/plain +res/cardsfolder/k/kresh_the_bloodbraided_avatar.txt -text res/cardsfolder/k/kris_mage.txt svneol=native#text/plain res/cardsfolder/k/krond_the_dawn_clad.txt -text res/cardsfolder/k/krosa.txt -text @@ -13817,6 +13819,8 @@ src/main/java/forge/card/trigger/TriggerCounterRemoved.java -text src/main/java/forge/card/trigger/TriggerCountered.java -text src/main/java/forge/card/trigger/TriggerCycled.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerDamageDone.java svneol=native#text/plain +src/main/java/forge/card/trigger/TriggerDestroyed.java -text +src/main/java/forge/card/trigger/TriggerDevoured.java -text src/main/java/forge/card/trigger/TriggerDiscarded.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerDrawn.java svneol=native#text/plain src/main/java/forge/card/trigger/TriggerHandler.java svneol=native#text/plain diff --git a/res/cardsfolder/k/karmic_justice.txt b/res/cardsfolder/k/karmic_justice.txt new file mode 100644 index 00000000000..64d72c14040 --- /dev/null +++ b/res/cardsfolder/k/karmic_justice.txt @@ -0,0 +1,6 @@ +Name:Karmic Justice +ManaCost:2 W +Types:Enchantment +T:Mode$ Destroyed | ValidCauser$ Player.Opponent | ValidCard$ Permanent.nonCreature+YouCtrl | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigDestroy | TriggerDescription$ Whenever a spell or ability an opponent controls destroys a noncreature permanent you control, you may destroy target permanent that opponent controls. +SVar:TrigDestroy:AB$ Destroy | Cost$ 0 | ValidTgts$ Permanent | TargetsWithDefinedController$ TriggeredCauser +SVar:Picture:http://www.wizards.com/global/images/magic/general/karmic_justice.jpg diff --git a/res/cardsfolder/k/kresh_the_bloodbraided_avatar.txt b/res/cardsfolder/k/kresh_the_bloodbraided_avatar.txt new file mode 100644 index 00000000000..b058c1437b6 --- /dev/null +++ b/res/cardsfolder/k/kresh_the_bloodbraided_avatar.txt @@ -0,0 +1,10 @@ +Name:Kresh the Bloodbraided Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:+1/-3 +T:Mode$ Devoured | ValidDevoured$ Creature.YouCtrl | TriggerZones$ Command | Execute$ TrigToken | TriggerDescription$ Whenever a creature you control is devoured, put an X/X green Ooze creature token onto the battlefield, where X is the devoured creature's power. +SVar:TrigToken:AB$ Token | Cost$ 0 | TokenImage$ G X X Ooze | TokenAmount$ 1 | TokenName$ Ooze | TokenTypes$ Creature,Ooze | TokenOwner$ You | TokenColors$ Green | TokenPower$ X | TokenToughness$ X | References$ X | TokenImage$ g x x ooze rtr | SpellDescription$ Put an X/X green Ooze creature token onto the battlefield. +SVar:X:TriggeredDevoured$CardPower +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/kresh_the_bloodbraided_avatar.jpg +Oracle:Hand +1, life -3\nWhenever a creature you control is devoured, put an X/X green Ooze creature token onto the battlefield, where X is the devoured creature's power. +SetInfo:VAN Special \ No newline at end of file diff --git a/src/main/java/forge/Card.java b/src/main/java/forge/Card.java index 168a1890016..4d48a54f78a 100644 --- a/src/main/java/forge/Card.java +++ b/src/main/java/forge/Card.java @@ -8346,7 +8346,7 @@ public class Card extends GameEntity implements Comparable { additionalLog = "(As -1/-1 Counters)"; } if (source.hasKeyword("Deathtouch") && this.isCreature()) { - Singletons.getModel().getGame().getAction().destroy(this); + Singletons.getModel().getGame().getAction().destroy(this, null); additionalLog = "(Deathtouch)"; } else if (this.isInPlay() && !wither) { this.damage += damageToAdd; diff --git a/src/main/java/forge/card/ability/effects/ControlGainEffect.java b/src/main/java/forge/card/ability/effects/ControlGainEffect.java index e0263bd79d3..839d143435f 100644 --- a/src/main/java/forge/card/ability/effects/ControlGainEffect.java +++ b/src/main/java/forge/card/ability/effects/ControlGainEffect.java @@ -197,9 +197,9 @@ public class ControlGainEffect extends SpellAbilityEffect { public void resolve() { if (bNoRegen) { - Singletons.getModel().getGame().getAction().destroyNoRegeneration(c); + Singletons.getModel().getGame().getAction().destroyNoRegeneration(c, null); } else { - Singletons.getModel().getGame().getAction().destroy(c); + Singletons.getModel().getGame().getAction().destroy(c, null); } } }; diff --git a/src/main/java/forge/card/ability/effects/CounterEffect.java b/src/main/java/forge/card/ability/effects/CounterEffect.java index c72603a9cd7..dffc1b272bc 100644 --- a/src/main/java/forge/card/ability/effects/CounterEffect.java +++ b/src/main/java/forge/card/ability/effects/CounterEffect.java @@ -106,7 +106,7 @@ public class CounterEffect extends SpellAbilityEffect { // Destroy Permanent may be able to be turned into a SubAbility if (tgtSA.isAbility() && sa.hasParam("DestroyPermanent")) { - Singletons.getModel().getGame().getAction().destroy(tgtSACard); + Singletons.getModel().getGame().getAction().destroy(tgtSACard, sa); } if (sa.hasParam("RememberCountered")) { diff --git a/src/main/java/forge/card/ability/effects/DestroyAllEffect.java b/src/main/java/forge/card/ability/effects/DestroyAllEffect.java index 43297ef91c5..6311a8993d3 100644 --- a/src/main/java/forge/card/ability/effects/DestroyAllEffect.java +++ b/src/main/java/forge/card/ability/effects/DestroyAllEffect.java @@ -87,13 +87,13 @@ public class DestroyAllEffect extends SpellAbilityEffect { if (noRegen) { for (int i = 0; i < list.size(); i++) { - if (Singletons.getModel().getGame().getAction().destroyNoRegeneration(list.get(i)) && remDestroyed) { + if (Singletons.getModel().getGame().getAction().destroyNoRegeneration(list.get(i), sa) && remDestroyed) { card.addRemembered(list.get(i)); } } } else { for (int i = 0; i < list.size(); i++) { - if (Singletons.getModel().getGame().getAction().destroy(list.get(i)) && remDestroyed) { + if (Singletons.getModel().getGame().getAction().destroy(list.get(i), sa) && remDestroyed) { card.addRemembered(list.get(i)); } } diff --git a/src/main/java/forge/card/ability/effects/DestroyEffect.java b/src/main/java/forge/card/ability/effects/DestroyEffect.java index f00c694abf0..51367b4bf37 100644 --- a/src/main/java/forge/card/ability/effects/DestroyEffect.java +++ b/src/main/java/forge/card/ability/effects/DestroyEffect.java @@ -96,9 +96,9 @@ public class DestroyEffect extends SpellAbilityEffect { if (sac) { destroyed = Singletons.getModel().getGame().getAction().sacrifice(tgtC, sa); } else if (noRegen) { - destroyed = Singletons.getModel().getGame().getAction().destroyNoRegeneration(tgtC); + destroyed = Singletons.getModel().getGame().getAction().destroyNoRegeneration(tgtC, sa); } else { - destroyed = Singletons.getModel().getGame().getAction().destroy(tgtC); + destroyed = Singletons.getModel().getGame().getAction().destroy(tgtC, sa); } if (destroyed && remDestroyed) { card.addRemembered(tgtC); } @@ -111,9 +111,9 @@ public class DestroyEffect extends SpellAbilityEffect { if (sac) { destroyed = Singletons.getModel().getGame().getAction().sacrifice(unTgtC, sa); } else if (noRegen) { - destroyed = Singletons.getModel().getGame().getAction().destroyNoRegeneration(unTgtC); + destroyed = Singletons.getModel().getGame().getAction().destroyNoRegeneration(unTgtC, sa); } else { - destroyed = Singletons.getModel().getGame().getAction().destroy(unTgtC); + destroyed = Singletons.getModel().getGame().getAction().destroy(unTgtC, sa); } if (destroyed && remDestroyed) { card.addRemembered(unTgtC); } diff --git a/src/main/java/forge/card/ability/effects/SacrificeEffect.java b/src/main/java/forge/card/ability/effects/SacrificeEffect.java index d7c53e35181..94e063c5e2d 100644 --- a/src/main/java/forge/card/ability/effects/SacrificeEffect.java +++ b/src/main/java/forge/card/ability/effects/SacrificeEffect.java @@ -66,7 +66,7 @@ public class SacrificeEffect extends SpellAbilityEffect { for(Card sac : choosenToSacrifice) { boolean wasSacrificed = !destroy && game.getAction().sacrifice(sac, sa); - boolean wasDestroyed = destroy && game.getAction().destroy(sac); + boolean wasDestroyed = destroy && game.getAction().destroy(sac, sa); if ( remSacrificed && (wasDestroyed || wasSacrificed) ) { card.addRemembered(sac); diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index fb3f882ed9c..c385be15a9e 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -3406,6 +3406,10 @@ public class CardFactoryUtil { Card dinner = (Card) o; card.addDevoured(dinner); Singletons.getModel().getGame().getAction().sacrifice(dinner, null); + final HashMap runParams = new HashMap(); + runParams.put("Devoured", dinner); + card.getController().getGame().getTriggerHandler() + .runTrigger(TriggerType.Devoured, runParams, false); } } } // human @@ -3416,6 +3420,10 @@ public class CardFactoryUtil { if ((c.getNetAttack() <= 1) && ((c.getNetAttack() + c.getNetDefense()) <= 3)) { card.addDevoured(c); Singletons.getModel().getGame().getAction().sacrifice(c, null); + final HashMap runParams = new HashMap(); + runParams.put("Devoured", c); + card.getController().getGame().getTriggerHandler() + .runTrigger(TriggerType.Devoured, runParams, false); count++; } } diff --git a/src/main/java/forge/card/trigger/TriggerDestroyed.java b/src/main/java/forge/card/trigger/TriggerDestroyed.java new file mode 100644 index 00000000000..9ce42966148 --- /dev/null +++ b/src/main/java/forge/card/trigger/TriggerDestroyed.java @@ -0,0 +1,85 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.card.trigger; + +import forge.Card; +import forge.card.spellability.SpellAbility; + +/** + *

+ * Trigger_Destroyed class. + *

+ * + * @author Forge + * @version $Id: TriggerDestroyed.java 17802 2012-10-31 08:05:14Z Max mtg $ + */ +public class TriggerDestroyed extends Trigger { + + /** + *

+ * Constructor for Trigger_Destroyed. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerDestroyed(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + if (this.getMapParams().containsKey("ValidCauser")) { + if (!matchesValid(runParams2.get("Causer"), this.getMapParams().get("ValidCauser").split(","), + this.getHostCard())) { + return false; + } + } + if (this.getMapParams().containsKey("ValidCard")) { + if (!matchesValid(runParams2.get("Card"), this.getMapParams().get("ValidCard").split(","), + this.getHostCard())) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public final Trigger getCopy() { + final Trigger copy = new TriggerDestroyed(this.getMapParams(), this.getHostCard(), this.isIntrinsic()); + if (this.getOverridingAbility() != null) { + copy.setOverridingAbility(this.getOverridingAbility()); + } + + copyFieldsTo(copy); + return copy; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Card", this.getRunParams().get("Card")); + sa.setTriggeringObject("Causer", this.getRunParams().get("Causer")); + } +} diff --git a/src/main/java/forge/card/trigger/TriggerDevoured.java b/src/main/java/forge/card/trigger/TriggerDevoured.java new file mode 100644 index 00000000000..4b83937cf2c --- /dev/null +++ b/src/main/java/forge/card/trigger/TriggerDevoured.java @@ -0,0 +1,79 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.card.trigger; + +import forge.Card; +import forge.card.spellability.SpellAbility; + +/** + *

+ * Trigger_Devoured class. + *

+ * + * @author Forge + * @version $Id: TriggerSacrificed.java 17802 2012-10-31 08:05:14Z Max mtg $ + */ +public class TriggerDevoured extends Trigger { + + /** + *

+ * Constructor for Trigger_Devoured. + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerDevoured(final java.util.Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} */ + @Override + public final boolean performTest(final java.util.Map runParams2) { + final Card sac = (Card) runParams2.get("Devoured"); + if (this.getMapParams().containsKey("ValidDevoured")) { + if (!sac.isValid(this.getMapParams().get("ValidDevoured").split(","), this.getHostCard().getController(), + this.getHostCard())) { + return false; + } + } + return true; + } + + /** {@inheritDoc} */ + @Override + public final Trigger getCopy() { + final Trigger copy = new TriggerDevoured(this.getMapParams(), this.getHostCard(), this.isIntrinsic()); + if (this.getOverridingAbility() != null) { + copy.setOverridingAbility(this.getOverridingAbility()); + } + + copyFieldsTo(copy); + return copy; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa) { + sa.setTriggeringObject("Devoured", this.getRunParams().get("Devoured")); + } +} diff --git a/src/main/java/forge/card/trigger/TriggerType.java b/src/main/java/forge/card/trigger/TriggerType.java index 9af39aaf1d0..622a7b980a5 100644 --- a/src/main/java/forge/card/trigger/TriggerType.java +++ b/src/main/java/forge/card/trigger/TriggerType.java @@ -20,6 +20,8 @@ public enum TriggerType { ChangesZone(TriggerChangesZone.class), Clashed(TriggerClashed.class), + Destroyed(TriggerDestroyed.class), + Devoured(TriggerDevoured.class), Countered(TriggerCountered.class), TapsForMana(TriggerTapsForMana.class), CounterAdded(TriggerCounterAdded.class), diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index 50282765780..a8c86700c07 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -1018,7 +1018,7 @@ public class GameAction { checkAgain = true; } if (c.getNetDefense() <= 0 || c.getNetDefense() <= c.getDamage()) { - this.destroy(c); + this.destroy(c, null); checkAgain = true; } // Soulbond unpairing @@ -1189,7 +1189,7 @@ public class GameAction { * a {@link forge.Card} object. * @return a boolean. */ - public final boolean destroy(final Card c) { + public final boolean destroy(final Card c, final SpellAbility sa) { if (!c.canBeDestroyed()) { return false; } @@ -1208,7 +1208,7 @@ public class GameAction { return false; } - return this.destroyNoRegeneration(c); + return this.destroyNoRegeneration(c, sa); } /** @@ -1220,7 +1220,7 @@ public class GameAction { * a {@link forge.Card} object. * @return a boolean. */ - public final boolean destroyNoRegeneration(final Card c) { + public final boolean destroyNoRegeneration(final Card c, final SpellAbility sa) { if (!c.canBeDestroyed()) return false; @@ -1250,7 +1250,7 @@ public class GameAction { final AbilityStatic ability = new AbilityStatic(crd, ManaCost.ZERO) { @Override public void resolve() { - GameAction.this.destroy(crd); + GameAction.this.destroy(crd, sa); card.setDamage(0); // Play the Destroy sound @@ -1269,6 +1269,11 @@ public class GameAction { // Play the Destroy sound game.getEvents().post(new CardDestroyedEvent()); + // Run triggers + final HashMap runParams = new HashMap(); + runParams.put("Card", c); + runParams.put("Causer", sa.getActivatingPlayer()); + game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false); return this.sacrificeDestroy(c); } diff --git a/src/main/java/forge/game/GameActionUtil.java b/src/main/java/forge/game/GameActionUtil.java index 73fb9ba5595..e685b7651cf 100644 --- a/src/main/java/forge/game/GameActionUtil.java +++ b/src/main/java/forge/game/GameActionUtil.java @@ -103,9 +103,9 @@ public final class GameActionUtil { public void resolve() { final GameState game = Singletons.getModel().getGame(); if ( canRegenerate ) - game.getAction().destroy(affected); + game.getAction().destroy(affected, this); else - game.getAction().destroyNoRegeneration(affected); + game.getAction().destroyNoRegeneration(affected, this); } } diff --git a/src/main/java/forge/game/phase/EndOfTurn.java b/src/main/java/forge/game/phase/EndOfTurn.java index 10f411dd34c..0ddb0ee19b4 100644 --- a/src/main/java/forge/game/phase/EndOfTurn.java +++ b/src/main/java/forge/game/phase/EndOfTurn.java @@ -96,7 +96,7 @@ public class EndOfTurn extends Phase { @Override public void resolve() { if (card.isInPlay()) { - game.getAction().destroy(card); + game.getAction().destroy(card, this); } } }; @@ -116,7 +116,7 @@ public class EndOfTurn extends Phase { @Override public void resolve() { if (card.isInPlay()) { - game.getAction().destroy(card); + game.getAction().destroy(card, this); } } }; diff --git a/src/main/java/forge/game/phase/Upkeep.java b/src/main/java/forge/game/phase/Upkeep.java index 306fe5cc6c4..d3d92dad5f9 100644 --- a/src/main/java/forge/game/phase/Upkeep.java +++ b/src/main/java/forge/game/phase/Upkeep.java @@ -285,7 +285,7 @@ public class Upkeep extends Phase { if (c.getName().equals("Cosmic Horror")) { controller.addDamage(7, c); } - game.getAction().destroy(c); + game.getAction().destroy(c, this); } } @@ -444,20 +444,20 @@ public class Upkeep extends Phase { chooseArt.setMessage(abyss.getName() + " - Select one nonartifact creature to destroy"); FThreads.setInputAndWait(chooseArt); // Input if (!chooseArt.hasCancelled()) { - game.getAction().destroyNoRegeneration(chooseArt.getSelected().get(0)); + game.getAction().destroyNoRegeneration(chooseArt.getSelected().get(0), this); } } else { // computer final List indestruct = CardLists.getKeyword(targets, "Indestructible"); if (indestruct.size() > 0) { - game.getAction().destroyNoRegeneration(indestruct.get(0)); + game.getAction().destroyNoRegeneration(indestruct.get(0), this); } else if (targets.size() > 0) { final Card target = ComputerUtilCard.getWorstCreatureAI(targets); if (null == target) { // must be nothing valid to destroy } else { - game.getAction().destroyNoRegeneration(target); + game.getAction().destroyNoRegeneration(target, this); } } } @@ -504,11 +504,11 @@ public class Upkeep extends Phase { inp.setMessage("Select creature with power: " + power + " to sacrifice."); FThreads.setInputAndWait(inp); if(!inp.hasCancelled()) - game.getAction().destroyNoRegeneration(inp.getSelected().get(0)); + game.getAction().destroyNoRegeneration(inp.getSelected().get(0), this); } else { // computer final Card compyTarget = this.getCompyCardToDestroy(creatures); - game.getAction().destroyNoRegeneration(compyTarget); + game.getAction().destroyNoRegeneration(compyTarget, this); } } } // resolve diff --git a/src/main/java/forge/gui/GuiDisplayUtil.java b/src/main/java/forge/gui/GuiDisplayUtil.java index b723be45366..5648033c35f 100644 --- a/src/main/java/forge/gui/GuiDisplayUtil.java +++ b/src/main/java/forge/gui/GuiDisplayUtil.java @@ -684,7 +684,7 @@ public final class GuiDisplayUtil { PlanarDice.roll(p, res); - Singletons.getModel().getGame().getStack().chooseOrderOfSimultaneousStackEntryAll(); + p.getGame().getStack().chooseOrderOfSimultaneousStackEntryAll(); } } // end class GuiDisplayUtil From 90e3a9cefc3476526d3e38cf375b0e95d79f2ec9 Mon Sep 17 00:00:00 2001 From: swordshine Date: Thu, 4 Apr 2013 10:41:09 +0000 Subject: [PATCH 116/123] - Added Harsh Mercy and Kamahl's Summons - Vanguard: Karona, False God Avatar --- .gitattributes | 3 +++ res/cardsfolder/h/harsh_mercy.txt | 11 +++++++++++ res/cardsfolder/k/kamahls_summons.txt | 14 ++++++++++++++ res/cardsfolder/k/karona_false_god_avatar.txt | 12 ++++++++++++ .../card/ability/effects/ChooseColorEffect.java | 2 +- .../ability/effects/ChooseNumberEffect.java | 2 +- .../card/ability/effects/ChooseTypeEffect.java | 12 ++++++------ .../ability/effects/ControlExchangeEffect.java | 17 +++++++++++++---- 8 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 res/cardsfolder/h/harsh_mercy.txt create mode 100644 res/cardsfolder/k/kamahls_summons.txt create mode 100644 res/cardsfolder/k/karona_false_god_avatar.txt diff --git a/.gitattributes b/.gitattributes index 4b707c32b3c..698728543cc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4728,6 +4728,7 @@ res/cardsfolder/h/harrow.txt svneol=native#text/plain res/cardsfolder/h/harrowing_journey.txt -text res/cardsfolder/h/harsh_deceiver.txt -text res/cardsfolder/h/harsh_justice.txt -text +res/cardsfolder/h/harsh_mercy.txt -text res/cardsfolder/h/haru_onna.txt svneol=native#text/plain res/cardsfolder/h/harvest_gwyllion.txt svneol=native#text/plain res/cardsfolder/h/harvest_pyre.txt -text @@ -5545,6 +5546,7 @@ res/cardsfolder/k/kamahl_fist_of_krosa.txt svneol=native#text/plain res/cardsfolder/k/kamahl_pit_fighter.txt svneol=native#text/plain res/cardsfolder/k/kamahls_desire.txt svneol=native#text/plain res/cardsfolder/k/kamahls_sledge.txt -text +res/cardsfolder/k/kamahls_summons.txt -text res/cardsfolder/k/kami_of_ancient_law.txt svneol=native#text/plain res/cardsfolder/k/kami_of_empty_graves.txt svneol=native#text/plain res/cardsfolder/k/kami_of_false_hope.txt svneol=native#text/plain @@ -5571,6 +5573,7 @@ res/cardsfolder/k/karn_liberated.txt -text res/cardsfolder/k/karn_silver_golem.txt svneol=native#text/plain res/cardsfolder/k/karns_touch.txt svneol=native#text/plain res/cardsfolder/k/karona_false_god.txt -text +res/cardsfolder/k/karona_false_god_avatar.txt -text res/cardsfolder/k/karonas_zealot.txt -text res/cardsfolder/k/karoo.txt svneol=native#text/plain res/cardsfolder/k/karoo_meerkat.txt svneol=native#text/plain diff --git a/res/cardsfolder/h/harsh_mercy.txt b/res/cardsfolder/h/harsh_mercy.txt new file mode 100644 index 00000000000..caf3c32231b --- /dev/null +++ b/res/cardsfolder/h/harsh_mercy.txt @@ -0,0 +1,11 @@ +Name:Harsh Mercy +ManaCost:2 W +Types:Sorcery +A:SP$ RepeatEach | Cost$ 2 W | RepeatPlayers$ Player | RepeatSubAbility$ DBChooseType | SubAbility$ DBDestroyAll | StackDescription$ SpellDescription | SpellDescription$ Each player chooses a creature type. Destroy all creatures that aren't of a type chosen this way. They can't be regenerated. +SVar:DBChooseType:DB$ ChooseType | Defined$ Player.IsRemembered | Type$ Creature | AILogic$ MostProminentComputerControls | SubAbility$ DBRemember +SVar:DBRemember:DB$ PumpAll | ValidCards$ Creature.ChosenType | RememberAllPumped$ True +SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Creature.IsNotRemembered | NoRegen$ True | SubAbility$ DBCleanup | StackDescription$ None +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/harsh_mercy.jpg +Oracle:Each player chooses a creature type. Destroy all creatures that aren't of a type chosen this way. They can't be regenerated. +SetInfo:ONS Rare \ No newline at end of file diff --git a/res/cardsfolder/k/kamahls_summons.txt b/res/cardsfolder/k/kamahls_summons.txt new file mode 100644 index 00000000000..38f0e6d2534 --- /dev/null +++ b/res/cardsfolder/k/kamahls_summons.txt @@ -0,0 +1,14 @@ +Name:Kamahl's Summons +ManaCost:3 G +Types:Sorcery +A:SP$ RepeatEach | Cost$ 3 G | RepeatPlayers$ Player | RepeatSubAbility$ DBChoose | StackDescription$ SpellDescription | SubAbility$ DBRepeatToken | SpellDescription$ Each player may reveal any number of creature cards from his or her hand. Then each player puts a 2/2 green Bear creature token onto the battlefield for each card he or she revealed this way. +SVar:DBChoose:DB$ Reveal | Defined$ Player.IsRemembered | AnyNumber$ True | RevealValid$ Creature | RememberRevealed$ True +SVar:DBRepeatToken:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBToken | SubAbility$ DBCleanup +SVar:DBToken:DB$ Token | TokenAmount$ X | References$ X | TokenName$ Bear | TokenTypes$ Creature,Bear | TokenOwner$ Player.IsRemembered | TokenColors$ Green | TokenPower$ 2 | TokenToughness$ 2 +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$ValidHand Card.IsRemembered+RememberedPlayerCtrl +SVar:NeedsToPlayVar:Y GE3 +SVar:Y:Count$ValidHand Creature.YouCtrl +SVar:Picture:http://www.wizards.com/global/images/magic/general/kamahls_summons.jpg +Oracle:Each player may reveal any number of creature cards from his or her hand. Then each player puts a 2/2 green Bear creature token onto the battlefield for each card he or she revealed this way. +SetInfo:ONS Uncommon \ No newline at end of file diff --git a/res/cardsfolder/k/karona_false_god_avatar.txt b/res/cardsfolder/k/karona_false_god_avatar.txt new file mode 100644 index 00000000000..bccf8b36ab2 --- /dev/null +++ b/res/cardsfolder/k/karona_false_god_avatar.txt @@ -0,0 +1,12 @@ +Name:Karona, False God Avatar +ManaCost:no cost +Types:Vanguard +HandLifeModifier:-1/+8 +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ TrigExchangeChoose | TriggerDescription$ At the beginning of your upkeep, exchange control of a permanent you control chosen at random and a permanent target opponent controls chosen at random. +SVar:TrigExchangeChoose:AB$ ChooseCard | Cost$ 0 | ValidTgts$ Opponent | Choices$ Permanent.TargetedPlayerCtrl | AtRandom$ True | Amount$ 1 | RememberChosen$ True | SubAbility$ ChooseYou +SVar:ChooseYou:DB$ ChooseCard | Choices$ Permanent.YouCtrl | Amount$ 1 | AtRandom$ True | RememberChosen$ True | SubAbility$ DBExchange +SVar:DBExchange:DB$ ExchangeControl | Defined$ Remembered | BothDefined$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Picture:http://www.cardforge.org/fpics/vgd-lq/karona_false_god_avatar.jpg +Oracle:Hand -1, life +8\nAt the beginning of your upkeep, exchange control of a permanent you control chosen at random and a permanent target opponent controls chosen at random. +SetInfo:VAN Special \ No newline at end of file diff --git a/src/main/java/forge/card/ability/effects/ChooseColorEffect.java b/src/main/java/forge/card/ability/effects/ChooseColorEffect.java index 5a89059afd1..cfefeffb9af 100644 --- a/src/main/java/forge/card/ability/effects/ChooseColorEffect.java +++ b/src/main/java/forge/card/ability/effects/ChooseColorEffect.java @@ -46,7 +46,7 @@ public class ChooseColorEffect extends SpellAbilityEffect { for (final Player p : tgtPlayers) { if ((tgt == null) || p.canBeTargetedBy(sa)) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { if (sa.hasParam("OrColors")) { ImmutableList choices = Constant.Color.ONLY_COLORS; final List o = GuiChoose.getChoices("Choose a color or colors", 1, choices.size(), choices); diff --git a/src/main/java/forge/card/ability/effects/ChooseNumberEffect.java b/src/main/java/forge/card/ability/effects/ChooseNumberEffect.java index 85e0a511038..ddfc6b971ac 100644 --- a/src/main/java/forge/card/ability/effects/ChooseNumberEffect.java +++ b/src/main/java/forge/card/ability/effects/ChooseNumberEffect.java @@ -68,7 +68,7 @@ public class ChooseNumberEffect extends SpellAbilityEffect { for (final Player p : tgtPlayers) { if ((tgt == null) || p.canBeTargetedBy(sa)) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { int chosen; if (random) { final Random randomGen = new Random(); diff --git a/src/main/java/forge/card/ability/effects/ChooseTypeEffect.java b/src/main/java/forge/card/ability/effects/ChooseTypeEffect.java index 72cd09f3c13..59c7c6bfb65 100644 --- a/src/main/java/forge/card/ability/effects/ChooseTypeEffect.java +++ b/src/main/java/forge/card/ability/effects/ChooseTypeEffect.java @@ -60,7 +60,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect { } boolean valid = false; while (!valid) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { final Object o = GuiChoose.one("Choose a card type", validTypes); if (null == o) { return; @@ -82,14 +82,14 @@ public class ChooseTypeEffect extends SpellAbilityEffect { String chosenType = ""; boolean valid = false; while (!valid) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { final ArrayList validChoices = CardType.getCreatureTypes(); for (final String s : invalidTypes) { validChoices.remove(s); } chosenType = GuiChoose.one("Choose a creature type", validChoices); } else { - Player ai = sa.getActivatingPlayer(); + Player ai = p; Player opp = ai.getOpponent(); String chosen = ""; if (sa.hasParam("AILogic")) { @@ -128,7 +128,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect { } else if (type.equals("Basic Land")) { boolean valid = false; while (!valid) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { final String choice = GuiChoose.one("Choose a basic land type", CardType.getBasicTypes()); if (null == choice) { return; @@ -138,7 +138,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect { card.setChosenType(choice); } } else { - Player ai = sa.getActivatingPlayer(); + Player ai = p; String chosen = ""; if (sa.hasParam("AILogic")) { final String logic = sa.getParam("AILogic"); @@ -176,7 +176,7 @@ public class ChooseTypeEffect extends SpellAbilityEffect { } else if (type.equals("Land")) { boolean valid = false; while (!valid) { - if (sa.getActivatingPlayer().isHuman()) { + if (p.isHuman()) { final String choice = GuiChoose .one("Choose a land type", CardType.getLandTypes()); if (null == choice) { diff --git a/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java b/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java index bca48c00ea9..1b22e74431e 100644 --- a/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java +++ b/src/main/java/forge/card/ability/effects/ControlExchangeEffect.java @@ -1,5 +1,6 @@ package forge.card.ability.effects; +import java.util.ArrayList; import java.util.List; import forge.Card; @@ -21,12 +22,16 @@ public class ControlExchangeEffect extends SpellAbilityEffect { Card object1 = null; Card object2 = null; final Target tgt = sa.getTarget(); - List tgts = tgt.getTargetCards(); + List tgts = tgt == null ? new ArrayList() : tgt.getTargetCards(); if (tgts.size() > 0) { object1 = tgts.get(0); } if (sa.hasParam("Defined")) { - object2 = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).get(0); + List cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa); + object2 = cards.isEmpty() ? null : cards.get(0); + if (cards.size() > 1 && sa.hasParam("BothDefined")) { + object1 = cards.get(1); + } } else if (tgts.size() > 1) { object2 = tgts.get(1); } @@ -42,12 +47,16 @@ public class ControlExchangeEffect extends SpellAbilityEffect { Card object1 = null; Card object2 = null; final Target tgt = sa.getTarget(); - List tgts = tgt.getTargetCards(); + List tgts = tgt == null ? new ArrayList() : tgt.getTargetCards(); if (tgts.size() > 0) { object1 = tgts.get(0); } if (sa.hasParam("Defined")) { - object2 = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa).get(0); + final List cards = AbilityUtils.getDefinedCards(sa.getSourceCard(), sa.getParam("Defined"), sa); + object2 = cards.isEmpty() ? null : cards.get(0); + if (cards.size() > 1 && sa.hasParam("BothDefined")) { + object1 = cards.get(1); + } } else if (tgts.size() > 1) { object2 = tgts.get(1); } From 24e37b13a16af6c68a24548c59f270eb6efbc6cf Mon Sep 17 00:00:00 2001 From: swordshine Date: Thu, 4 Apr 2013 11:06:11 +0000 Subject: [PATCH 117/123] - Added Kaboom! --- .gitattributes | 1 + res/cardsfolder/c/comet_storm.txt | 4 ++-- res/cardsfolder/k/kaboom!.txt | 12 ++++++++++++ res/cardsfolder/k/karmic_justice.txt | 2 ++ 4 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 res/cardsfolder/k/kaboom!.txt diff --git a/.gitattributes b/.gitattributes index 698728543cc..d4f72456e24 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5528,6 +5528,7 @@ res/cardsfolder/k/kaalia_of_the_vast.txt -text res/cardsfolder/k/kabira_crossroads.txt svneol=native#text/plain res/cardsfolder/k/kabira_evangel.txt -text res/cardsfolder/k/kabira_vindicator.txt svneol=native#text/plain +res/cardsfolder/k/kaboom!.txt -text res/cardsfolder/k/kabuto_moth.txt svneol=native#text/plain res/cardsfolder/k/kaervek_the_merciless.txt svneol=native#text/plain res/cardsfolder/k/kaerveks_hex.txt svneol=native#text/plain diff --git a/res/cardsfolder/c/comet_storm.txt b/res/cardsfolder/c/comet_storm.txt index 9596ffd8525..b77e23e7078 100644 --- a/res/cardsfolder/c/comet_storm.txt +++ b/res/cardsfolder/c/comet_storm.txt @@ -7,5 +7,5 @@ SVar:TargetsNum:Count$TimesKicked/Plus.1 SVar:Picture:http://www.wizards.com/global/images/magic/general/comet_storm.jpg SVar:X:Count$xPaid Oracle:Multikicker {1} (You may pay an additional {1} any number of times as you cast this spell.)\nChoose target creature or player, then choose another target creature or player for each time Comet Storm was kicked. Comet Storm deals X damage to each of them. -SetInfo:WWK Mythic -SetInfo:CMD Mythic +SetInfo:COM Mythic +SetInfo:WWK Mythic \ No newline at end of file diff --git a/res/cardsfolder/k/kaboom!.txt b/res/cardsfolder/k/kaboom!.txt new file mode 100644 index 00000000000..3c8aeddfb04 --- /dev/null +++ b/res/cardsfolder/k/kaboom!.txt @@ -0,0 +1,12 @@ +Name:Kaboom! +ManaCost:4 R +Types:Sorcery +A:SP$ RepeatEach | Cost$ 4 R | ValidTgts$ Player | TargetMin$ 0 | TargetMax$ Maxplayer | References$ Maxplayer | RepeatPlayers$ Targeted | RepeatSubAbility$ DBDigUntil | StackDescription$ SpellDescription | SpellDescription$ Choose any number of target players. For each of those players, reveal cards from the top of your library until you reveal a nonland card. Kaboom deals damage equal to that card's converted mana cost to that player, then you put the revealed cards on the bottom of your library in any order. +SVar:DBDigUntil:DB$ DigUntil | Defined$ You | Valid$ Card.nonLand | ValidDescription$ nonland card | FoundDestination$ Library | FoundLibraryPosition$ -1 | RevealedDestination$ Library | RevealedLibraryPosition$ -1 | RememberFound$ True | SubAbility$ DBDmg +SVar:DBDmg:DB$ DealDamage | Defined$ Player.IsRemembered | NumDmg$ X | References$ X | SubAbility$ DBCleanup +SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True +SVar:X:Remembered$CardManaCost +SVar:Maxplayer:PlayerCountPlayers$Amount +SVar:Picture:http://www.wizards.com/global/images/magic/general/kaboom!.jpg +Oracle:Choose any number of target players. For each of those players, reveal cards from the top of your library until you reveal a nonland card. Kaboom deals damage equal to that card's converted mana cost to that player, then you put the revealed cards on the bottom of your library in any order. +SetInfo:ONS Rare \ No newline at end of file diff --git a/res/cardsfolder/k/karmic_justice.txt b/res/cardsfolder/k/karmic_justice.txt index 64d72c14040..009a27a3e9e 100644 --- a/res/cardsfolder/k/karmic_justice.txt +++ b/res/cardsfolder/k/karmic_justice.txt @@ -4,3 +4,5 @@ Types:Enchantment T:Mode$ Destroyed | ValidCauser$ Player.Opponent | ValidCard$ Permanent.nonCreature+YouCtrl | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigDestroy | TriggerDescription$ Whenever a spell or ability an opponent controls destroys a noncreature permanent you control, you may destroy target permanent that opponent controls. SVar:TrigDestroy:AB$ Destroy | Cost$ 0 | ValidTgts$ Permanent | TargetsWithDefinedController$ TriggeredCauser SVar:Picture:http://www.wizards.com/global/images/magic/general/karmic_justice.jpg +Oracle:Whenever a spell or ability an opponent controls destroys a noncreature permanent you control, you may destroy target permanent that opponent controls. +SetInfo:ODY Rare \ No newline at end of file From 92428d93d1204a38262bc1b3fbdc629f0c674786 Mon Sep 17 00:00:00 2001 From: Sloth Date: Thu, 4 Apr 2013 11:22:36 +0000 Subject: [PATCH 118/123] - Fixed a possible NPE in destroyNoRegeneration. --- src/main/java/forge/game/GameAction.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/forge/game/GameAction.java b/src/main/java/forge/game/GameAction.java index a8c86700c07..797b1dfb5ac 100644 --- a/src/main/java/forge/game/GameAction.java +++ b/src/main/java/forge/game/GameAction.java @@ -1221,6 +1221,7 @@ public class GameAction { * @return a boolean. */ public final boolean destroyNoRegeneration(final Card c, final SpellAbility sa) { + Player activator = null; if (!c.canBeDestroyed()) return false; @@ -1266,13 +1267,16 @@ public class GameAction { return false; } } // totem armor + if (sa != null) { + activator = sa.getActivatingPlayer(); + } // Play the Destroy sound game.getEvents().post(new CardDestroyedEvent()); // Run triggers final HashMap runParams = new HashMap(); runParams.put("Card", c); - runParams.put("Causer", sa.getActivatingPlayer()); + runParams.put("Causer", activator); game.getTriggerHandler().runTrigger(TriggerType.Destroyed, runParams, false); return this.sacrificeDestroy(c); From 1551007ca2d0882018100f1543ae0705ae21b6cf Mon Sep 17 00:00:00 2001 From: Sloth Date: Thu, 4 Apr 2013 11:25:40 +0000 Subject: [PATCH 119/123] - Updated some SVars. --- res/cardsfolder/h/harsh_mercy.txt | 1 + res/cardsfolder/k/kaboom!.txt | 1 + res/cardsfolder/k/kamahls_summons.txt | 1 + 3 files changed, 3 insertions(+) diff --git a/res/cardsfolder/h/harsh_mercy.txt b/res/cardsfolder/h/harsh_mercy.txt index caf3c32231b..f44848ac79f 100644 --- a/res/cardsfolder/h/harsh_mercy.txt +++ b/res/cardsfolder/h/harsh_mercy.txt @@ -6,6 +6,7 @@ SVar:DBChooseType:DB$ ChooseType | Defined$ Player.IsRemembered | Type$ Creature SVar:DBRemember:DB$ PumpAll | ValidCards$ Creature.ChosenType | RememberAllPumped$ True SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Creature.IsNotRemembered | NoRegen$ True | SubAbility$ DBCleanup | StackDescription$ None SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/harsh_mercy.jpg Oracle:Each player chooses a creature type. Destroy all creatures that aren't of a type chosen this way. They can't be regenerated. SetInfo:ONS Rare \ No newline at end of file diff --git a/res/cardsfolder/k/kaboom!.txt b/res/cardsfolder/k/kaboom!.txt index 3c8aeddfb04..301bc75535a 100644 --- a/res/cardsfolder/k/kaboom!.txt +++ b/res/cardsfolder/k/kaboom!.txt @@ -7,6 +7,7 @@ SVar:DBDmg:DB$ DealDamage | Defined$ Player.IsRemembered | NumDmg$ X | Reference SVar:DBCleanup:DB$Cleanup | ClearRemembered$ True SVar:X:Remembered$CardManaCost SVar:Maxplayer:PlayerCountPlayers$Amount +SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/kaboom!.jpg Oracle:Choose any number of target players. For each of those players, reveal cards from the top of your library until you reveal a nonland card. Kaboom deals damage equal to that card's converted mana cost to that player, then you put the revealed cards on the bottom of your library in any order. SetInfo:ONS Rare \ No newline at end of file diff --git a/res/cardsfolder/k/kamahls_summons.txt b/res/cardsfolder/k/kamahls_summons.txt index 38f0e6d2534..25dc5ebec89 100644 --- a/res/cardsfolder/k/kamahls_summons.txt +++ b/res/cardsfolder/k/kamahls_summons.txt @@ -9,6 +9,7 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$ValidHand Card.IsRemembered+RememberedPlayerCtrl SVar:NeedsToPlayVar:Y GE3 SVar:Y:Count$ValidHand Creature.YouCtrl +SVar:RemAIDeck:True SVar:Picture:http://www.wizards.com/global/images/magic/general/kamahls_summons.jpg Oracle:Each player may reveal any number of creature cards from his or her hand. Then each player puts a 2/2 green Bear creature token onto the battlefield for each card he or she revealed this way. SetInfo:ONS Uncommon \ No newline at end of file From dd8a60a9af856ac7d6179a79e9fb8dbc66125888 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Thu, 4 Apr 2013 12:50:51 +0000 Subject: [PATCH 120/123] won't crash with empty strings --- src/main/java/forge/card/cardfactory/CardFactoryUtil.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java index c385be15a9e..8dccd2b289f 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryUtil.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryUtil.java @@ -1108,9 +1108,8 @@ public class CardFactoryUtil { */ public static int xCount(final Card c, final String expression) { int n = 0; - - if (StringUtils.isNumeric(expression)) - return Integer.parseInt(expression); + if (StringUtils.isBlank(expression)) return 0; + if (StringUtils.isNumeric(expression)) return Integer.parseInt(expression); final Player cardController = c.getController(); final Player oppController = cardController.getOpponent(); From cc245d2a98f8d8c090814754143a37994bb9cbb7 Mon Sep 17 00:00:00 2001 From: Sloth Date: Thu, 4 Apr 2013 12:52:52 +0000 Subject: [PATCH 121/123] - Added BecomesBlocked abilities. - Added Curtain of Light and Dazzling Beauty. --- .gitattributes | 3 ++ res/cardsfolder/c/curtain_of_light.txt | 9 ++++ res/cardsfolder/d/dazzling_beauty.txt | 9 ++++ src/main/java/forge/card/ability/ApiType.java | 1 + .../ability/effects/BecomesBlockedEffect.java | 54 +++++++++++++++++++ 5 files changed, 76 insertions(+) create mode 100644 res/cardsfolder/c/curtain_of_light.txt create mode 100644 res/cardsfolder/d/dazzling_beauty.txt create mode 100644 src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java diff --git a/.gitattributes b/.gitattributes index d4f72456e24..95dafc7a228 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2245,6 +2245,7 @@ res/cardsfolder/c/cursed_rack.txt svneol=native#text/plain res/cardsfolder/c/cursed_ronin.txt svneol=native#text/plain res/cardsfolder/c/cursed_scroll.txt svneol=native#text/plain res/cardsfolder/c/cursed_totem.txt svneol=native#text/plain +res/cardsfolder/c/curtain_of_light.txt -text res/cardsfolder/c/custody_battle.txt -text svneol=unset#text/plain res/cardsfolder/c/customs_depot.txt svneol=native#text/plain res/cardsfolder/c/cut_the_earthly_bond.txt svneol=native#text/plain @@ -2386,6 +2387,7 @@ res/cardsfolder/d/day_of_the_dragons.txt svneol=native#text/plain res/cardsfolder/d/daybreak_coronet.txt -text res/cardsfolder/d/daybreak_ranger_nightfall_predator.txt -text res/cardsfolder/d/daze.txt svneol=native#text/plain +res/cardsfolder/d/dazzling_beauty.txt -text res/cardsfolder/d/dead_gone.txt -text res/cardsfolder/d/dead_iron_sledge.txt svneol=native#text/plain res/cardsfolder/d/dead_reckoning.txt -text svneol=unset#text/plain @@ -13624,6 +13626,7 @@ src/main/java/forge/card/ability/effects/AnimateAllEffect.java -text src/main/java/forge/card/ability/effects/AnimateEffect.java -text src/main/java/forge/card/ability/effects/AnimateEffectBase.java svneol=native#text/plain src/main/java/forge/card/ability/effects/AttachEffect.java -text +src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java -text src/main/java/forge/card/ability/effects/BondEffect.java -text src/main/java/forge/card/ability/effects/ChangeZoneAllEffect.java -text src/main/java/forge/card/ability/effects/ChangeZoneEffect.java -text diff --git a/res/cardsfolder/c/curtain_of_light.txt b/res/cardsfolder/c/curtain_of_light.txt new file mode 100644 index 00000000000..56a53e94cd7 --- /dev/null +++ b/res/cardsfolder/c/curtain_of_light.txt @@ -0,0 +1,9 @@ +Name:Curtain of Light +ManaCost:1 W +Types:Instant +Text:Cast CARDNAME only during combat after blockers are declared. +A:SP$ BecomesBlocked | Cost$ 1 W | ValidTgts$ Creature.attacking+unblocked | TgtPrompt$ Select target unblocked attacking creature | SubAbility$ Draw | ActivationPhases$ Declare Blockers - Play Instants and Abilities->EndCombat | SpellDescription$ Target unblocked attacking creature becomes blocked. (This spell works on unblockable creatures.) Draw a card. +SVar:Draw:DB$ Draw | NumCards$ 1 +SVar:Picture:http://www.wizards.com/global/images/magic/general/curtain_of_light.jpg +Oracle:Cast Curtain of Light only during combat after blockers are declared.\nTarget unblocked attacking creature becomes blocked. (This spell works on unblockable creatures.)\nDraw a card. +SetInfo:SOK Common \ No newline at end of file diff --git a/res/cardsfolder/d/dazzling_beauty.txt b/res/cardsfolder/d/dazzling_beauty.txt new file mode 100644 index 00000000000..3a2df04d736 --- /dev/null +++ b/res/cardsfolder/d/dazzling_beauty.txt @@ -0,0 +1,9 @@ +Name:Dazzling Beauty +ManaCost:2 W +Types:Instant +Text:Cast CARDNAME only during combat after blockers are declared. +A:SP$ BecomesBlocked | Cost$ 2 W | ValidTgts$ Creature.attacking+unblocked | TgtPrompt$ Select target unblocked attacking creature | SubAbility$ Draw | ActivationPhases$ Declare Blockers - Play Instants and Abilities->EndCombat | SpellDescription$ Target unblocked attacking creature becomes blocked. (This spell works on unblockable creatures.) Draw a card at the beginning of the next turn's upkeep. +SVar:Draw:DB$ Draw | NumCards$ 1 | NextUpkeep$ True +SVar:Picture:http://www.wizards.com/global/images/magic/general/dazzling_beauty.jpg +Oracle:Cast Dazzling Beauty only during the declare blockers step.\nTarget unblocked attacking creature becomes blocked. (This spell works on unblockable creatures.)\nDraw a card at the beginning of the next turn's upkeep. +SetInfo:MIR Common \ No newline at end of file diff --git a/src/main/java/forge/card/ability/ApiType.java b/src/main/java/forge/card/ability/ApiType.java index 0459b15f268..ca6dfd7fdc4 100644 --- a/src/main/java/forge/card/ability/ApiType.java +++ b/src/main/java/forge/card/ability/ApiType.java @@ -112,6 +112,7 @@ public enum ApiType { Animate (AnimateEffect.class, AnimateAi.class), AnimateAll (AnimateAllEffect.class, AnimateAllAi.class), Attach (AttachEffect.class, AttachAi.class), + BecomesBlocked (BecomesBlockedEffect.class, CannotPlayAi.class), Bond (BondEffect.class, BondAi.class), ChangeZone(ChangeZoneEffect.class, ChangeZoneAi.class), ChangeZoneAll(ChangeZoneAllEffect.class, ChangeZoneAllAi.class), diff --git a/src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java b/src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java new file mode 100644 index 00000000000..10d1922ad99 --- /dev/null +++ b/src/main/java/forge/card/ability/effects/BecomesBlockedEffect.java @@ -0,0 +1,54 @@ +package forge.card.ability.effects; + +import java.util.HashMap; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import forge.Card; +import forge.Singletons; +import forge.card.ability.SpellAbilityEffect; +import forge.card.cardfactory.CardFactoryUtil; +import forge.card.spellability.Ability; +import forge.card.spellability.SpellAbility; +import forge.card.spellability.Target; +import forge.card.trigger.TriggerType; + +public class BecomesBlockedEffect extends SpellAbilityEffect { + + @Override + protected String getStackDescription(SpellAbility sa) { + final StringBuilder sb = new StringBuilder(); + + final List tgtCards = getTargetCards(sa); + + sb.append(StringUtils.join(tgtCards, ", ")); + sb.append(" becomes blocked."); + + return sb.toString(); + } + + @Override + public void resolve(SpellAbility sa) { + + final Target tgt = sa.getTarget(); + for (final Card c : getTargetCards(sa)) { + if ((tgt == null) || c.canBeTargetedBy(sa)) { + Singletons.getModel().getGame().getCombat().setBlocked(c); + if (!c.getDamageHistory().getCreatureGotBlockedThisCombat()) { + final HashMap runParams = new HashMap(); + runParams.put("Attacker", c); + runParams.put("Blocker", null); + runParams.put("NumBlockers", 0); + Singletons.getModel().getGame().getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false); + + // Bushido + for (final Ability ab : CardFactoryUtil.getBushidoEffects(c)) { + Singletons.getModel().getGame().getStack().add(ab); + } + } + } + } + + } +} From 354f6f1692c0072600e073bf21785906e231f77d Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 4 Apr 2013 13:09:30 +0000 Subject: [PATCH 122/123] - Added a fluff piece to the changes.txt file. - Added new card names to changes.txt. --- CHANGES.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index 848fefa6ebd..a9cc5e8b34c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -51,6 +51,10 @@ An importer option was added for including pictures in set-specific card directo When you mouse over a flip, transform or Morph (controlled by you) card in battlefield, you may hold SHIFT to see other state of that card at the side panel that displays card picture and details. +- Milestone reached - +There is now under 700 unsupported cards, and we should be at or near 85% for all the sets. + + --------- New Cards --------- @@ -79,6 +83,13 @@ Cut the Tethers Delay Runed Halo Heroism +Blazing Effigy +Karmic Justice +Harsh Mercy +Kamahl's Summons +Kaboom! +Curtain of Light +Dazzling Beauty ---------- @@ -86,6 +97,13 @@ New Planes ---------- Sea of Sand +Astral Arena +Bloodhill Bastion +Gavony +Hedron Fields of Agadeem +Kessig +Nephalia +The Zephyr Maze -------------------- @@ -98,6 +116,8 @@ Peacekeeper Avatar Stonehewer Giant Avatar Sliver Queen Avatar Jhoira of the Ghitu Avatar +Kresh the Bloodbraided Avatar +Karona, False God Avatar ------------ From a117cb53c36933f9ce2228a61e25558b4d632189 Mon Sep 17 00:00:00 2001 From: Sloth Date: Thu, 4 Apr 2013 13:23:01 +0000 Subject: [PATCH 123/123] - Added AI support to BecomesBlocked. --- .gitattributes | 1 + src/main/java/forge/card/ability/ApiType.java | 3 +- .../card/ability/ai/BecomesBlockedAi.java | 65 +++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/main/java/forge/card/ability/ai/BecomesBlockedAi.java diff --git a/.gitattributes b/.gitattributes index 95dafc7a228..318036a40b7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13527,6 +13527,7 @@ src/main/java/forge/card/ability/ai/AlwaysPlayAi.java -text src/main/java/forge/card/ability/ai/AnimateAi.java -text src/main/java/forge/card/ability/ai/AnimateAllAi.java -text src/main/java/forge/card/ability/ai/AttachAi.java -text +src/main/java/forge/card/ability/ai/BecomesBlockedAi.java -text src/main/java/forge/card/ability/ai/BondAi.java -text src/main/java/forge/card/ability/ai/CanPlayAsDrawbackAi.java -text src/main/java/forge/card/ability/ai/CannotPlayAi.java -text diff --git a/src/main/java/forge/card/ability/ApiType.java b/src/main/java/forge/card/ability/ApiType.java index ca6dfd7fdc4..ae794f7c796 100644 --- a/src/main/java/forge/card/ability/ApiType.java +++ b/src/main/java/forge/card/ability/ApiType.java @@ -10,6 +10,7 @@ import forge.card.ability.ai.AlwaysPlayAi; import forge.card.ability.ai.AnimateAi; import forge.card.ability.ai.AnimateAllAi; import forge.card.ability.ai.AttachAi; +import forge.card.ability.ai.BecomesBlockedAi; import forge.card.ability.ai.BondAi; import forge.card.ability.ai.CanPlayAsDrawbackAi; import forge.card.ability.ai.CannotPlayAi; @@ -112,7 +113,7 @@ public enum ApiType { Animate (AnimateEffect.class, AnimateAi.class), AnimateAll (AnimateAllEffect.class, AnimateAllAi.class), Attach (AttachEffect.class, AttachAi.class), - BecomesBlocked (BecomesBlockedEffect.class, CannotPlayAi.class), + BecomesBlocked (BecomesBlockedEffect.class, BecomesBlockedAi.class), Bond (BondEffect.class, BondAi.class), ChangeZone(ChangeZoneEffect.class, ChangeZoneAi.class), ChangeZoneAll(ChangeZoneAllEffect.class, ChangeZoneAllAi.class), diff --git a/src/main/java/forge/card/ability/ai/BecomesBlockedAi.java b/src/main/java/forge/card/ability/ai/BecomesBlockedAi.java new file mode 100644 index 00000000000..a5c6609c673 --- /dev/null +++ b/src/main/java/forge/card/ability/ai/BecomesBlockedAi.java @@ -0,0 +1,65 @@ +package forge.card.ability.ai; + + +import java.util.List; + +import forge.Card; +import forge.CardLists; +import forge.Singletons; +import forge.card.ability.SpellAbilityAi; +import forge.card.spellability.SpellAbility; +import forge.card.spellability.Target; +import forge.game.ai.ComputerUtilCard; +import forge.game.player.AIPlayer; +import forge.game.zone.ZoneType; + +public class BecomesBlockedAi extends SpellAbilityAi { + + @Override + protected boolean canPlayAI(AIPlayer aiPlayer, SpellAbility sa) { + final Card source = sa.getSourceCard(); + final Target tgt = sa.getTarget(); + + List list = Singletons.getModel().getGame().getCardsIn(ZoneType.Battlefield); + list = CardLists.getValidCards(list, tgt.getValidTgts(), source.getController(), source); + list = CardLists.getTargetableCards(list, sa); + + while (tgt.getNumTargeted() < tgt.getMaxTargets(source, sa)) { + Card choice = null; + + if (list.isEmpty()) { + return false; + } + + choice = ComputerUtilCard.getBestCreatureAI(list); + + if (choice == null) { // can't find anything left + return false; + } + + list.remove(choice); + tgt.addTarget(choice); + } + return true; + } + + @Override + public boolean chkAIDrawback(SpellAbility sa, AIPlayer aiPlayer) { + + // TODO - implement AI + return false; + } + + /* (non-Javadoc) + * @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean) + */ + @Override + protected boolean doTriggerAINoCost(AIPlayer aiPlayer, SpellAbility sa, boolean mandatory) { + boolean chance; + + // TODO - implement AI + chance = false; + + return chance; + } +}