From b54a99e78f399833af0f5d72858913abde888e5a Mon Sep 17 00:00:00 2001 From: jendave Date: Sat, 6 Aug 2011 08:53:25 +0000 Subject: [PATCH] - Added Remove<> as an new kind of Ability_Cost - Added Attunement to use Return<1/CARDNAME> - Changed meloku_the_clouded_mirror to use Return<1/Type> - Both cards are RemAIDecks until AI updated to handle these abilities. --- .gitattributes | 1 + res/cardsfolder/attunement.txt | 9 ++ res/cardsfolder/meloku_the_clouded_mirror.txt | 2 + src/forge/Ability_Activated.java | 2 +- src/forge/Ability_Cost.java | 109 ++++++++++--- src/forge/CardFactory_Creatures.java | 65 +------- src/forge/ComputerUtil.java | 31 ++++ src/forge/Cost_Payment.java | 147 +++++++++++++++++- 8 files changed, 273 insertions(+), 93 deletions(-) create mode 100644 res/cardsfolder/attunement.txt diff --git a/.gitattributes b/.gitattributes index 1d5eee22f7e..1efe4acce2a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -249,6 +249,7 @@ res/cardsfolder/assert_authority.txt -text svneol=native#text/plain res/cardsfolder/astral_steel.txt -text svneol=native#text/plain res/cardsfolder/atog.txt -text svneol=native#text/plain res/cardsfolder/attrition.txt -text svneol=native#text/plain +res/cardsfolder/attunement.txt -text svneol=native#text/plain res/cardsfolder/augury_adept.txt -text svneol=native#text/plain res/cardsfolder/augury_owl.txt -text svneol=native#text/plain res/cardsfolder/aura_blast.txt -text svneol=native#text/plain diff --git a/res/cardsfolder/attunement.txt b/res/cardsfolder/attunement.txt new file mode 100644 index 00000000000..8041cecd5dd --- /dev/null +++ b/res/cardsfolder/attunement.txt @@ -0,0 +1,9 @@ +Name:Attunement +ManaCost:2 U +Types:Enchantment +Text:no text +K:abDrawCards Return<1/CARDNAME>:3:Drawback$YouDiscard/4:Draw three cards, then discard four cards.:Attunement - Draw three cards, then discard four cards. +SVar:Rarity:Rare +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/attunement.jpg +End diff --git a/res/cardsfolder/meloku_the_clouded_mirror.txt b/res/cardsfolder/meloku_the_clouded_mirror.txt index fdd5b60f98e..679b0cc67bb 100644 --- a/res/cardsfolder/meloku_the_clouded_mirror.txt +++ b/res/cardsfolder/meloku_the_clouded_mirror.txt @@ -4,6 +4,8 @@ Types:Legendary Creature Moonfolk Wizard Text:no text PT:2/4 K:Flying +K:abMakeToken 1 Return<1/Land><>1<>Illusion<>U 1 1 Illusion<>Controller<>U<>Creature;Illusion<>1<>1<>Flying<>Put a 1/1 blue Illusion creature token with flying onto the battlefield. SVar:Rarity:Rare +SVar:RemAIDeck:True SVar:Picture:http://resources.wizards.com/magic/cards/chk/en-us/card75268.jpg End diff --git a/src/forge/Ability_Activated.java b/src/forge/Ability_Activated.java index f7de0446410..1cb820e7d60 100644 --- a/src/forge/Ability_Activated.java +++ b/src/forge/Ability_Activated.java @@ -25,7 +25,7 @@ abstract public class Ability_Activated extends SpellAbility implements java.io. if (Silence.size() != 0) return false; } - return AllZone.GameAction.isCardInPlay(c); + return Cost_Payment.canPayAdditionalCosts(payCosts, this) && AllZone.GameAction.isCardInPlay(c); //TODO: make sure you can't play the Computer's activated abilities } } diff --git a/src/forge/Ability_Cost.java b/src/forge/Ability_Cost.java index e644041a08d..7c2bb1ddc2e 100644 --- a/src/forge/Ability_Cost.java +++ b/src/forge/Ability_Cost.java @@ -48,6 +48,15 @@ public class Ability_Cost { private String discardType = ""; public String getDiscardType() { return discardType; } + private boolean returnCost = false; // Return something to owner's hand + public boolean getReturnCost() { return returnCost; } + private String returnType = ""; // or CARDNAME + public String getReturnType() { return returnType; } + private boolean returnThis = false; + public boolean getReturnThis() { return returnThis; } + private int returnAmount = 0; + public int getReturnAmount() { return returnAmount; } + public boolean hasNoManaCost() { return manaCost.equals("") || manaCost.equals("0"); }; private String manaCost = ""; public String getMana() { return manaCost; } @@ -106,7 +115,6 @@ public class Ability_Cost { String sacStr = "Sac<"; if(parse.contains(sacStr)) { - // todo(sol): add Support for sacrificing more than 1 of a type // todo: maybe separate SacThis from SacType? not sure if any card would use both sacCost = true; String[] splitStr = abCostParse(parse, sacStr, 2); @@ -117,6 +125,17 @@ public class Ability_Cost { sacThis = (sacType.equals("CARDNAME")); } + String returnStr = "Return<"; + if(parse.contains(returnStr)) { + returnCost = true; + String[] splitStr = abCostParse(parse, returnStr, 2); + parse = abUpdateParse(parse, returnStr); + + returnAmount = Integer.parseInt(splitStr[0]); + returnType = splitStr[1]; + returnThis = (returnType.equals("CARDNAME")); + } + if (parse.contains("Untap")){ untapCost = true; parse = parse.replace("Untap", "").trim(); @@ -220,7 +239,15 @@ public class Ability_Cost { first = false; } - cost.append(sacString(first)); + if (sacCost){ + cost.append(sacString(first)); + first = false; + } + + if (returnCost){ + cost.append(returnString(first)); + first = false; + } cost.append("."); return cost.toString(); @@ -316,7 +343,15 @@ public class Ability_Cost { first = false; } - cost.append(sacString(first)); + if (sacCost){ + cost.append(sacString(first)); + first = false; + } + + if (returnCost){ + cost.append(returnString(first)); + first = false; + } cost.append(": "); return cost.toString(); @@ -325,29 +360,53 @@ public class Ability_Cost { public String sacString(boolean first) { StringBuilder cost = new StringBuilder(); - if (sacCost){ - if (first){ - if (isAbility) - cost.append("Sacrifice "); - else - cost.append("sacrifice "); - } - else{ - if (isAbility) - cost.append(", sacrifice "); - else - cost.append(" and sacrifice "); - } - - if (sacType.equals("CARDNAME")) - cost.append(name); - else{ - cost.append(sacAmount).append(" "); - cost.append(sacType); - if (sacAmount > 1) - cost.append("s"); - } + if (first){ + if (isAbility) + cost.append("Sacrifice "); + else + cost.append("sacrifice "); + } + else{ + cost.append(", sacrifice "); + } + + if (sacType.equals("CARDNAME")) + cost.append(name); + else{ + cost.append(sacAmount).append(" "); + cost.append(sacType); + if (sacAmount > 1) + cost.append("s"); } return cost.toString(); } + + public String returnString(boolean first) + { + StringBuilder cost = new StringBuilder(); + if (first){ + if (isAbility) + cost.append("Return "); + else + cost.append("return "); + } + else{ + cost.append(", return "); + } + String pronoun = "its"; + if (returnType.equals("CARDNAME")) + cost.append(name); + else{ + cost.append(returnAmount).append(" "); + cost.append(returnType); + + if (returnAmount > 1){ + cost.append("s"); + pronoun = "their"; + } + cost.append(" you control"); + } + cost.append(" to ").append(pronoun).append(" owner's hand"); + return cost.toString(); + } } diff --git a/src/forge/CardFactory_Creatures.java b/src/forge/CardFactory_Creatures.java index e1211ecc457..c81d88db419 100644 --- a/src/forge/CardFactory_Creatures.java +++ b/src/forge/CardFactory_Creatures.java @@ -8461,70 +8461,7 @@ public class CardFactory_Creatures { ability1.setBeforePayMana(CardFactoryUtil.input_targetType(ability1, "All")); ability2.setBeforePayMana(CardFactoryUtil.input_targetType(ability2, "Artifact")); }//*************** END ************ END ************************** - - - //*************** START *********** START ************************** - else if(cardName.equals("Meloku the Clouded Mirror")) { - final SpellAbility ability = new Ability(card, "1") { - @Override - public void resolve() { - CardFactoryUtil.makeToken("Illusion", "U 1 1 Illusion", card, "U", new String[] { - "Creature", "Illusion"}, 1, 1, new String[] {"Flying"}); - PlayerZone play = AllZone.getZone(Constant.Zone.Play, card.getController()); - - CardList land = new CardList(play.getCards()); - land = land.filter(new CardListFilter() { - public boolean addCard(Card c) { - return c.getType().contains("Land"); - } - }); - PlayerZone hand = AllZone.getZone(Constant.Zone.Hand, card.getController()); - - if (card.getController().equals(Constant.Player.Human)) - { - if(!land.isEmpty()) { - Object o = AllZone.Display.getChoiceOptional("Select target Land", land.toArray()); - Card l = (Card) o; - AllZone.GameAction.moveTo(hand, l); - } - } - else - { - land.shuffle(); - Card crd = land.get(0); - if (crd!=null) - AllZone.GameAction.moveTo(hand, crd); - } - - }//resolve() - - @Override - public boolean canPlay() { - PlayerZone play = AllZone.getZone(Constant.Zone.Play, card.getController()); - - CardList land = new CardList(play.getCards()); - land = land.filter(new CardListFilter() { - public boolean addCard(Card c) { - return c.getType().contains("Land"); - } - }); - return land.size() > 0 && super.canPlay(); - } - public boolean canPlayAI() - { - PlayerZone play = AllZone.getZone(Constant.Zone.Play, Constant.Player.Computer); - - CardList land = new CardList(play.getCards()); - land = land.getType("Land"); - return land.size() > 5; - } - };//SpellAbility - card.addSpellAbility(ability); - ability.setDescription("1, Return a land you control to its owner's hand: Put a 1/1 blue Illusion creature token with flying into play."); - ability.setStackDescription("Put 1/1 token with flying into play"); - ability.setBeforePayMana(new Input_PayManaCost(ability)); - }//*************** END ************ END ************************** - + //*************** START *********** START ************************** else if(cardName.equals("Hammerfist Giant")) { diff --git a/src/forge/ComputerUtil.java b/src/forge/ComputerUtil.java index 46fc7ed3009..f3834dc0baa 100644 --- a/src/forge/ComputerUtil.java +++ b/src/forge/ComputerUtil.java @@ -299,6 +299,23 @@ public class ComputerUtil return false; } + if (cost.getReturnCost()){ + // if there's a return in the cost, just because we can Pay it doesn't mean we want to. + if (!cost.getReturnThis()){ + PlayerZone play = AllZone.getZone(Constant.Zone.Play, Constant.Player.Computer); + CardList typeList = new CardList(play.getCards()); + typeList = typeList.getValidCards(cost.getReturnType().split(",")); + Card target = sa.getTargetCard(); + if (target != null && target.getController().equals(Constant.Player.Computer)) // don't bounce the card we're pumping + typeList.remove(target); + + if (cost.getReturnAmount() > typeList.size()) + return false; + } + else if (!AllZone.GameAction.isCardInPlay(card)) + return false; + } + return true; } @@ -562,6 +579,20 @@ public class ComputerUtil CardListUtil.sortAttackLowFirst(typeList); return typeList.get(index); } + + static public Card chooseReturnType(String type, Card activate, Card target){ + PlayerZone play = AllZone.getZone(Constant.Zone.Play, Constant.Player.Computer); + CardList typeList = new CardList(play.getCards()); + typeList = typeList.getValidCards(type.split(",")); + if (target != null && target.getController().equals(Constant.Player.Computer) && typeList.contains(target)) // don't bounce the card we're pumping + typeList.remove(target); + + if (typeList.size() == 0) + return null; + + CardListUtil.sortAttackLowFirst(typeList); + return typeList.get(0); + } static public CardList getPossibleAttackers() { diff --git a/src/forge/Cost_Payment.java b/src/forge/Cost_Payment.java index c958d8514a1..46d423164c3 100644 --- a/src/forge/Cost_Payment.java +++ b/src/forge/Cost_Payment.java @@ -26,6 +26,7 @@ public class Cost_Payment { private boolean payLife; private boolean payDiscard; private boolean payTapXType; + private boolean payReturn; private boolean bCancel = false; @@ -39,6 +40,7 @@ public class Cost_Payment { public void setPayDiscard(boolean bSac){ payDiscard = bSac; } public void setPaySac(boolean bSac){ paySac = bSac; } public void setPayTapXType(boolean bTapX) { payTapXType = bTapX; } + public void setPayReturn(boolean bReturn){ payReturn = bReturn; } final private Input changeInput = new Input() { private static final long serialVersionUID = -5750122411788688459L; }; @@ -55,9 +57,13 @@ public class Cost_Payment { payLife = !cost.getLifeCost(); payDiscard = !cost.getDiscardCost(); payTapXType = !cost.getTapXTypeCost(); + payReturn = !cost.getReturnCost(); } public static boolean canPayAdditionalCosts(Ability_Cost cost, SpellAbility ability){ + if (cost == null) + return true; + final Card card = ability.getSourceCard(); if (cost.getTap() && (card.isTapped() || card.isSick())) return false; @@ -125,10 +131,23 @@ public class Cost_Payment { CardList typeList = new CardList(play.getCards()); typeList = typeList.getValidCards(cost.getSacType().split(",")); - if (typeList.size() == 0) + if (typeList.size() < cost.getSacAmount()) return false; } - else if (cost.getSacThis() && !AllZone.GameAction.isCardInPlay(card)) + else if (!AllZone.GameAction.isCardInPlay(card)) + return false; + } + + if (cost.getReturnCost()){ + if (!cost.getReturnThis()){ + PlayerZone play = AllZone.getZone(Constant.Zone.Play, card.getController()); + CardList typeList = new CardList(play.getCards()); + + typeList = typeList.getValidCards(cost.getReturnType().split(",")); + if (typeList.size() < cost.getReturnAmount()) + return false; + } + else if (!AllZone.GameAction.isCardInPlay(card)) return false; } @@ -239,13 +258,21 @@ public class Cost_Payment { changeInput.stopSetNext(sacrificeType(ability, cost.getSacType(), this)); return false; } + + if (!payReturn && cost.getReturnCost()){ // return stuff here + if (cost.getReturnThis()) + changeInput.stopSetNext(returnThis(ability, this)); + else + changeInput.stopSetNext(returnType(ability, cost.getReturnType(), this)); + return false; + } req.finishPaying(); return true; } public boolean isAllPaid(){ - return (payTap && payUntap && payMana && paySubCounter && paySac && payLife && payDiscard && payTapXType); + return (payTap && payUntap && payMana && paySubCounter && paySac && payLife && payDiscard && payTapXType && payReturn); } public void cancelPayment(){ @@ -285,12 +312,15 @@ public class Cost_Payment { // can't really undiscard things // can't really unsacrifice things + + // can't really unreturn things } public void payComputerCosts(){ // make sure ComputerUtil.canPayAdditionalCosts() is updated when updating new Costs ArrayList sacCard = new ArrayList(); ArrayList tapXCard = new ArrayList(); + ArrayList returnCard = new ArrayList(); ability.setActivatingPlayer(Constant.Player.Computer); // double check if something can be sacrificed here. Real check is in ComputerUtil.canPayAdditionalCosts() @@ -308,6 +338,20 @@ public class Cost_Payment { } } + if (cost.getReturnCost()){ + if (cost.getReturnThis()) + returnCard.add(card); + else{ + for(int i = 0; i < cost.getReturnAmount(); i++) + returnCard.add(ComputerUtil.chooseReturnType(cost.getReturnType(), card, ability.getTargetCard())); + } + + if (returnCard.size() != cost.getReturnAmount()){ + System.out.println("Couldn't find a valid card to return for: "+card.getName()); + return; + } + } + if (cost.getTapXTypeCost()) { boolean tap = cost.getTap(); @@ -372,6 +416,11 @@ public class Cost_Payment { for(Card c : sacCard) AllZone.GameAction.sacrifice(c); } + + if (cost.getReturnCost()){ + for(Card c : returnCard) + AllZone.GameAction.moveToHand(c); + } AllZone.Stack.add(ability); } @@ -581,4 +630,96 @@ public class Cost_Payment { }; return target; }//input_tapXCost() + + public static Input returnThis(final SpellAbility spell, final Cost_Payment payment) { + Input target = new Input() { + private static final long serialVersionUID = 2685832214519141903L; + + @Override + public void showMessage() { + Card card = spell.getSourceCard(); + if(card.getController().equals(Constant.Player.Human) && AllZone.GameAction.isCardInPlay(card)) { + StringBuilder sb = new StringBuilder(); + sb.append(card.getName()); + sb.append(" - Return to Hand?"); + Object[] possibleValues = {"Yes", "No"}; + Object choice = JOptionPane.showOptionDialog(null, sb.toString(), card.getName() + " - Cost", + JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, + null, possibleValues, possibleValues[0]); + if(choice.equals(0)) { + payment.setPayReturn(true); + AllZone.GameAction.moveToHand(card); + stop(); + payment.payCost(); + } + else{ + payment.setCancel(true); + stop(); + payment.payCost(); + } + } + } + }; + return target; + }//input_sacrifice() + + public static Input returnType(final SpellAbility spell, final String type, final Cost_Payment payment){ + Input target = new Input() { + private static final long serialVersionUID = 2685832214519141903L; + private CardList typeList; + private int nReturns = 0; + private int nNeeded = payment.getCost().getReturnAmount(); + + @Override + public void showMessage() { + StringBuilder msg = new StringBuilder("Return "); + int nLeft = nNeeded - nReturns; + msg.append(nLeft).append(" "); + msg.append(type); + if (nLeft > 1){ + msg.append("s"); + } + + PlayerZone play = AllZone.getZone(Constant.Zone.Play, spell.getSourceCard().getController()); + typeList = new CardList(play.getCards()); + typeList = typeList.getValidCards(type.split(",")); + AllZone.Display.showMessage(msg.toString()); + ButtonUtil.enableOnlyCancel(); + } + + @Override + public void selectButtonCancel() { + cancel(); + } + + @Override + public void selectCard(Card card, PlayerZone zone) { + if(typeList.contains(card)) { + nReturns++; + AllZone.GameAction.moveToHand(card); + typeList.remove(card); + //in case nothing else to return + if(nReturns == nNeeded) + done(); + else if (typeList.size() == 0) // this really shouldn't happen + cancel(); + else + showMessage(); + } + } + + public void done(){ + payment.setPayReturn(true); + stop(); + payment.payCost(); + } + + public void cancel(){ + payment.setCancel(true); + stop(); + payment.payCost(); + } + }; + return target; + }//sacrificeType() }