From dc2cd5daaf6170b1d18ef34bf6839757b0f260c2 Mon Sep 17 00:00:00 2001 From: swordshine Date: Sat, 8 Jun 2013 06:07:00 +0000 Subject: [PATCH] - Added Battlefield Scrounger, Leashling, and Penance --- .gitattributes | 4 + res/cardsfolder/b/battlefield_scrounger.txt | 8 + res/cardsfolder/l/leashling.txt | 7 + res/cardsfolder/p/penance.txt | 11 + src/main/java/forge/card/cost/Cost.java | 20 +- .../forge/card/cost/CostPutCardToLib.java | 339 ++++++++++++++++++ src/main/java/forge/game/ai/ComputerUtil.java | 45 +++ .../java/forge/game/player/HumanPlay.java | 42 +++ 8 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 res/cardsfolder/b/battlefield_scrounger.txt create mode 100644 res/cardsfolder/l/leashling.txt create mode 100644 res/cardsfolder/p/penance.txt create mode 100644 src/main/java/forge/card/cost/CostPutCardToLib.java diff --git a/.gitattributes b/.gitattributes index 6bec2fa8aee..a56c2c9ff65 100644 --- a/.gitattributes +++ b/.gitattributes @@ -848,6 +848,7 @@ res/cardsfolder/b/battle_strain.txt svneol=native#text/plain res/cardsfolder/b/battlefield_forge.txt svneol=native#text/plain res/cardsfolder/b/battlefield_medic.txt svneol=native#text/plain res/cardsfolder/b/battlefield_percher.txt svneol=native#text/plain +res/cardsfolder/b/battlefield_scrounger.txt -text res/cardsfolder/b/battleflight_eagle.txt -text res/cardsfolder/b/battlegate_mimic.txt svneol=native#text/plain res/cardsfolder/b/battlegrace_angel.txt svneol=native#text/plain @@ -6109,6 +6110,7 @@ res/cardsfolder/l/leap.txt svneol=native#text/plain res/cardsfolder/l/leap_of_faith.txt -text res/cardsfolder/l/leap_of_flame.txt svneol=native#text/plain res/cardsfolder/l/leaping_lizard.txt svneol=native#text/plain +res/cardsfolder/l/leashling.txt -text 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 @@ -7881,6 +7883,7 @@ res/cardsfolder/p/pegasus_refuge.txt svneol=native#text/plain res/cardsfolder/p/pegasus_stampede.txt svneol=native#text/plain res/cardsfolder/p/pelakka_wurm.txt svneol=native#text/plain res/cardsfolder/p/pemmins_aura.txt svneol=native#text/plain +res/cardsfolder/p/penance.txt -text res/cardsfolder/p/pendelhaven.txt svneol=native#text/plain res/cardsfolder/p/pendelhaven_elder.txt svneol=native#text/plain res/cardsfolder/p/pendrell_drake.txt svneol=native#text/plain @@ -14188,6 +14191,7 @@ src/main/java/forge/card/cost/CostPartMana.java -text src/main/java/forge/card/cost/CostPartWithList.java -text src/main/java/forge/card/cost/CostPayLife.java -text src/main/java/forge/card/cost/CostPayment.java svneol=native#text/plain +src/main/java/forge/card/cost/CostPutCardToLib.java -text src/main/java/forge/card/cost/CostPutCounter.java -text src/main/java/forge/card/cost/CostRemoveCounter.java -text src/main/java/forge/card/cost/CostReturn.java -text diff --git a/res/cardsfolder/b/battlefield_scrounger.txt b/res/cardsfolder/b/battlefield_scrounger.txt new file mode 100644 index 00000000000..8335bb65549 --- /dev/null +++ b/res/cardsfolder/b/battlefield_scrounger.txt @@ -0,0 +1,8 @@ +Name:Battlefield Scrounger +ManaCost:3 G G +Types:Creature Centaur +PT:3/3 +A:AB$ Pump | Cost$ PutCardToLibFromGrave<3/-1/Card> | Activation$ Threshold | ActivationLimit$ 1 | NumAtt$ +3 | NumDef$ +3 | PrecostDesc$ Threshold - | SpellDescription$ CARDNAME gets +3/+3 until end of turn. Activate this ability only once each turn, and only if seven or more cards are in your graveyard. +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/battlefield_scrounger.jpg +Oracle:Threshold - Put three cards from your graveyard on the bottom of your library: Battlefield Scrounger gets +3/+3 until end of turn. Activate this ability only once each turn, and only if seven or more cards are in your graveyard. diff --git a/res/cardsfolder/l/leashling.txt b/res/cardsfolder/l/leashling.txt new file mode 100644 index 00000000000..ff93ebe4e06 --- /dev/null +++ b/res/cardsfolder/l/leashling.txt @@ -0,0 +1,7 @@ +Name:Leashling +ManaCost:6 +Types:Artifact Creature Hound +PT:3/3 +A:AB$ ChangeZone | Cost$ PutCardToLibFromHand<1/0/Card> | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return CARDNAME to its owner's hand. +SVar:Picture:http://www.wizards.com/global/images/magic/general/leashling.jpg +Oracle:Put a card from your hand on top of your library: Return Leashling to its owner's hand. diff --git a/res/cardsfolder/p/penance.txt b/res/cardsfolder/p/penance.txt new file mode 100644 index 00000000000..3b7808f649f --- /dev/null +++ b/res/cardsfolder/p/penance.txt @@ -0,0 +1,11 @@ +Name:Penance +ManaCost:2 W +Types:Enchantment +A:AB$ ChooseSource | Cost$ PutCardToLibFromHand<1/0/Card> | Choices$ Card.Red,Card.Black | RememberChosen$ True | AILogic$ NeedsPrevention | SubAbility$ DBEffect | SpellDescription$ The next time a black or red source of your choice would deal damage this turn, prevent that damage. +SVar:DBEffect:DB$ Effect | ReplacementEffects$ RPreventNextFromSource | RememberObjects$ Remembered | SVars$ RPreventNextFromSource,ExileEffect | SubAbility$ DBCleanup | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 +SVar:RPreventNextFromSource:Event$ DamageDone | ValidSource$ Card.IsRemembered | ReplaceWith$ ExileEffect | PreventionEffect$ True | Description$ The next time the chosen source deals damage, prevent that damage. +SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:RemRandomDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/penance.jpg +Oracle:Put a card from your hand on top of your library: The next time a black or red source of your choice would deal damage this turn, prevent that damage. diff --git a/src/main/java/forge/card/cost/Cost.java b/src/main/java/forge/card/cost/Cost.java index fbb9d982752..a09b55fd9ab 100644 --- a/src/main/java/forge/card/cost/Cost.java +++ b/src/main/java/forge/card/cost/Cost.java @@ -316,12 +316,30 @@ public class Cost { final String description = splitStr.length > 2 ? splitStr[2] : null; return new CostReveal(splitStr[0], splitStr[1], description); } + + if(parse.startsWith("PutCardToLibFromHand<")) { + final String[] splitStr = abCostParse(parse, 4); + final String description = splitStr.length > 3 ? splitStr[3] : null; + return new CostPutCardToLib(splitStr[0], splitStr[1], splitStr[2], description, ZoneType.Hand); + } + + if(parse.startsWith("PutCardToLibFromGrave<")) { + final String[] splitStr = abCostParse(parse, 4); + final String description = splitStr.length > 3 ? splitStr[3] : null; + return new CostPutCardToLib(splitStr[0], splitStr[1], splitStr[2], description, ZoneType.Graveyard); + } + if(parse.startsWith("PutCardToLibFromSameGrave<")) { + final String[] splitStr = abCostParse(parse, 4); + final String description = splitStr.length > 3 ? splitStr[3] : null; + return new CostPutCardToLib(splitStr[0], splitStr[1], splitStr[2], description, ZoneType.Graveyard, true); + } + // These won't show up with multiples if (parse.equals("Untap") || parse.equals("Q")) { return new CostUntap(); } - + if (parse.equals("T")) { return new CostTap(); } diff --git a/src/main/java/forge/card/cost/CostPutCardToLib.java b/src/main/java/forge/card/cost/CostPutCardToLib.java new file mode 100644 index 00000000000..c1e8696018a --- /dev/null +++ b/src/main/java/forge/card/cost/CostPutCardToLib.java @@ -0,0 +1,339 @@ +/* + * 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.cost; + +import java.util.ArrayList; +import java.util.List; + +import forge.Card; +import forge.CardLists; +import forge.CardPredicates; +import forge.Singletons; +import forge.card.ability.AbilityUtils; +import forge.card.spellability.SpellAbility; +import forge.game.Game; +import forge.game.ai.ComputerUtil; +import forge.game.player.Player; +import forge.game.zone.ZoneType; +import forge.gui.GuiChoose; +import forge.gui.input.InputSelectCards; +import forge.gui.input.InputSelectCardsFromList; + +/** + * This is for the "PutCardToLib" Cost. + */ +public class CostPutCardToLib extends CostPartWithList { + // PutCardToLibFromHand + // PutCardToLibFromSameGrave + // PutCardToLibFromGrave + + private ZoneType from = ZoneType.Hand; + private boolean sameZone = false; + private String libPosition = "0"; + + /** + * Gets the from. + * + * @return the from + */ + public final ZoneType getFrom() { + return this.from; + } + + /** + * Gets the libposition. + * + * @return the libposition + */ + public final String getLibPos() { + return this.libPosition; + } + + /** + * Instantiates a new cost exile. + * + * @param amount + * the amount + * @param type + * the type + * @param description + * the description + * @param from + * the from + */ + public CostPutCardToLib(final String amount, final String libpos, + final String type, final String description, final ZoneType from) { + super(amount, type, description); + if (from != null) { + this.from = from; + } + this.libPosition = libpos; + } + + public CostPutCardToLib(final String amount, final String libpos, final String type, + final String description, final ZoneType from, final boolean sameZone) { + this(amount, libpos, type, description, from); + this.sameZone = sameZone; + } + + + /* + * (non-Javadoc) + * + * @see forge.card.cost.CostPart#toString() + */ + @Override + public final String toString() { + final StringBuilder sb = new StringBuilder(); + final Integer i = this.convertAmount(); + sb.append("Put "); + + final String desc = this.getTypeDescription() == null ? this.getType() : this.getTypeDescription(); + sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), desc)); + + if (this.sameZone) { + sb.append(" from the same "); + } else { + sb.append(" from your "); + } + + sb.append(this.from).append(" on "); + + if (this.libPosition.equals("0")) { + sb.append("top of"); + } else { + sb.append("the bottom of"); + } + + if (this.sameZone) { + sb.append(" their owner's library"); + } else { + sb.append(" your library"); + } + + return sb.toString(); + } + + /* (non-Javadoc) + * @see forge.card.cost.CostPartWithList#getHashForList() + */ + @Override + public String getHashForList() { + return "CardPutToLib"; + } + + /* + * (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) { + final Player activator = ability.getActivatingPlayer(); + final Card source = ability.getSourceCard(); + final Game game = activator.getGame(); + + Integer i = this.convertAmount(); + + if (i == null) { + final String sVar = ability.getSVar(this.getAmount()); + if (sVar.equals("XChoice")) { + return true; + } + i = AbilityUtils.calculateAmount(source, this.getAmount(), ability); + } + + List typeList = new ArrayList(); + if (this.sameZone) { + typeList = new ArrayList(game.getCardsIn(this.getFrom())); + } else { + typeList = new ArrayList(activator.getCardsIn(this.getFrom())); + } + + typeList = CardLists.getValidCards(typeList, this.getType().split(";"), activator, source); + + if (typeList.size() < i) { + return false; + } + + if (this.sameZone) { + boolean foundPayable = false; + List players = game.getPlayers(); + for (Player p : players) { + if (CardLists.filter(typeList, CardPredicates.isController(p)).size() >= i) { + foundPayable = true; + break; + } + } + if (!foundPayable) { + return false; + } + } + + return true; + + } + + /* + * (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 Game game) { + final String amount = this.getAmount(); + Integer c = this.convertAmount(); + final Card source = ability.getSourceCard(); + final Player activator = ability.getActivatingPlayer(); + + List list; + + if (this.sameZone) { + list = new ArrayList(game.getCardsIn(this.getFrom())); + } else { + list = new ArrayList(activator.getCardsIn(this.getFrom())); + } + + if (c == null) { + final String sVar = ability.getSVar(amount); + // Generalize this + if (sVar.equals("XChoice")) { + c = Cost.chooseXValue(source, ability, this.getList().size()); + } else { + c = AbilityUtils.calculateAmount(source, amount, ability); + } + } + + list = CardLists.getValidCards(list, this.getType().split(";"), activator, source); + + if (this.from == ZoneType.Hand) { + InputSelectCards inp = new InputSelectCardsFromList(c, c, list); + inp.setMessage("Put %d card(s) from your " + from ); + inp.setCancelAllowed(true); + Singletons.getControl().getInputQueue().setInputAndWait(inp); + return !inp.hasCancelled() && executePayment(ability, inp.getSelected()); + } + + if (this.sameZone){ + List players = game.getPlayers(); + List payableZone = new ArrayList(); + for (Player p : players) { + List enoughType = CardLists.filter(list, CardPredicates.isOwner(p)); + if (enoughType.size() < c) { + list.removeAll(enoughType); + } else { + payableZone.add(p); + } + } + return PutFromSame(list, c, payableZone); + } else {//Graveyard + return PutFromMiscZone(ability, c, list); + } + } + + /** + * TODO: Write javadoc for this method. + * @param sa + * @param nNeeded + * @param typeList + * @return a boolean + */ + private boolean PutFromMiscZone(SpellAbility sa, int nNeeded, List typeList) { + for (int i = 0; i < nNeeded; i++) { + if (typeList.isEmpty()) { + return false; + } + + final Card c = GuiChoose.oneOrNone("Put from " + getFrom() + " to library", typeList); + + if (c != null) { + typeList.remove(c); + executePayment(sa, c); + } else { + return false; + } + } + return true; + } + + private boolean PutFromSame(List list, int nNeeded, List payableZone) { + if (nNeeded == 0) { + return true; + } + + + final Player p = GuiChoose.oneOrNone(String.format("Put cards from whose %s?", getFrom()), payableZone); + if (p == null) { + return false; + } + + List typeList = CardLists.filter(list, CardPredicates.isOwner(p)); + + for (int i = 0; i < nNeeded; i++) { + if (typeList.isEmpty()) { + return false; + } + + final Card c = GuiChoose.oneOrNone("Put cards from " + getFrom() + " to Library", typeList); + + if (c != null) { + typeList.remove(c); + executePayment(null, c); + } else { + return false; + } + } + return true; + } + + /* (non-Javadoc) + * @see forge.card.cost.CostPartWithList#executePayment(forge.card.spellability.SpellAbility, forge.Card) + */ + @Override + protected void doPayment(SpellAbility ability, Card targetCard) { + ability.getActivatingPlayer().getGame().getAction().moveToLibrary(targetCard, Integer.parseInt(getLibPos())); + } + + /* (non-Javadoc) + * @see forge.card.cost.CostPart#decideAIPayment(forge.game.player.AIPlayer, forge.card.spellability.SpellAbility, forge.Card) + */ + @Override + public PaymentDecision decideAIPayment(Player ai, SpellAbility ability, Card source) { + Integer c = this.convertAmount(); + if (c == null) { + final String sVar = ability.getSVar(this.getAmount()); + // Generalize this + if (sVar.equals("XChoice")) { + return null; + } + + c = AbilityUtils.calculateAmount(source, this.getAmount(), ability); + } + if (this.sameZone) { + // TODO Determine exile from same zone for AI + return null; + } else { + List chosen = ComputerUtil.choosePutToLibraryFrom(ai, this.getFrom(), this.getType(), source, ability.getTargetCard(), c); + return null == chosen ? null : new PaymentDecision(chosen); + } + } +} diff --git a/src/main/java/forge/game/ai/ComputerUtil.java b/src/main/java/forge/game/ai/ComputerUtil.java index 4b273da70b1..969ba64235a 100644 --- a/src/main/java/forge/game/ai/ComputerUtil.java +++ b/src/main/java/forge/game/ai/ComputerUtil.java @@ -506,6 +506,51 @@ public class ComputerUtil { return exileList; } + /** + *

+ * choosePutToLibraryFrom. + *

+ * + * @param zone + * a {@link java.lang.String} object. + * @param type + * a {@link java.lang.String} object. + * @param activate + * a {@link forge.Card} object. + * @param target + * a {@link forge.Card} object. + * @param amount + * a int. + * @return a {@link forge.CardList} object. + */ + public static List choosePutToLibraryFrom(final Player ai, final ZoneType zone, final String type, final Card activate, + final Card target, final int amount) { + List typeList = ai.getCardsIn(zone); + + typeList = CardLists.getValidCards(typeList, type.split(","), activate.getController(), activate); + + if ((target != null) && target.getController() == ai && typeList.contains(target)) { + typeList.remove(target); // don't move the card we're pumping + } + + if (typeList.size() < amount) { + return null; + } + + CardLists.sortByPowerAsc(typeList); + final List list = new ArrayList(); + + if (zone != ZoneType.Hand) { + Collections.reverse(typeList); + } + + for (int i = 0; i < amount; i++) { + list.add(typeList.get(i)); + } + + return list; + } + /** *

* chooseTapType. diff --git a/src/main/java/forge/game/player/HumanPlay.java b/src/main/java/forge/game/player/HumanPlay.java index d388543772d..d49ed81206b 100644 --- a/src/main/java/forge/game/player/HumanPlay.java +++ b/src/main/java/forge/game/player/HumanPlay.java @@ -8,6 +8,7 @@ import org.apache.commons.lang3.StringUtils; import forge.Card; import forge.CardLists; import forge.CardPredicates.Presets; +import forge.CardPredicates; import forge.CounterType; import forge.FThreads; import forge.GameLogEntryType; @@ -26,6 +27,7 @@ import forge.card.cost.CostPartMana; import forge.card.cost.CostPartWithList; import forge.card.cost.CostPayLife; import forge.card.cost.CostPayment; +import forge.card.cost.CostPutCardToLib; import forge.card.cost.CostPutCounter; import forge.card.cost.CostRemoveCounter; import forge.card.cost.CostReturn; @@ -396,6 +398,46 @@ public class HumanPlay { } } } + + else if (part instanceof CostPutCardToLib) { + //Jotun Grunt + int amount = Integer.parseInt(((CostPutCardToLib) part).getAmount()); + final ZoneType from = ((CostPutCardToLib) part).getFrom(); + List list = CardLists.getValidCards(p.getGame().getCardsIn(from), part.getType().split(","), p, source); + List players = p.getGame().getPlayers(); + List payableZone = new ArrayList(); + for (Player player : players) { + List enoughType = CardLists.filter(list, CardPredicates.isOwner(player)); + if (enoughType.size() < amount) { + list.removeAll(enoughType); + } else { + payableZone.add(player); + } + } + Player chosen = GuiChoose.oneOrNone(String.format("Put cards from whose %s?", + ((CostPutCardToLib) part).getFrom()), payableZone); + if (chosen == null) { + return false; + } + + List typeList = CardLists.filter(list, CardPredicates.isOwner(chosen)); + + for (int i = 0; i < amount; i++) { + if (typeList.isEmpty()) { + return false; + } + + final Card c = GuiChoose.oneOrNone("Put cards to Library", typeList); + + if (c != null) { + typeList.remove(c); + p.getGame().getAction().moveToLibrary(c, Integer.parseInt(((CostPutCardToLib) part).getLibPos())); + } else { + return false; + } + } + return true; + } else if (part instanceof CostSacrifice) { int amount = Integer.parseInt(((CostSacrifice)part).getAmount());