diff --git a/.gitattributes b/.gitattributes index 5d7d54db0f1..77359793958 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8843,6 +8843,7 @@ res/cardsfolder/r/restless_dead.txt svneol=native#text/plain res/cardsfolder/r/restless_dreams.txt -text res/cardsfolder/r/restock.txt svneol=native#text/plain res/cardsfolder/r/restoration_angel.txt -text +res/cardsfolder/r/restore_balance.txt -text res/cardsfolder/r/restore_the_peace.txt -text res/cardsfolder/r/restrain.txt -text res/cardsfolder/r/resurrection.txt svneol=native#text/plain @@ -14042,6 +14043,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/BalanceAi.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 @@ -14143,6 +14145,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/BalanceEffect.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/ChangeTargetsEffect.java -text diff --git a/res/cardsfolder/b/balance.txt b/res/cardsfolder/b/balance.txt index 4602936df0a..ff52aa2a2e0 100644 --- a/res/cardsfolder/b/balance.txt +++ b/res/cardsfolder/b/balance.txt @@ -1,12 +1,8 @@ Name:Balance ManaCost:1 W Types:Sorcery -Text:Each player chooses a number of lands he or she controls equal to the number of lands controlled by the player who controls the fewest, then sacrifices the rest. Players discard cards and sacrifice creatures the same way. +A:SP$ Balance | Cost$ 1 W | Valid$ Land | AILogic$ BalanceCreaturesAndLands | SubAbility$ BalanceHands | SpellDescription$ Each player chooses a number of lands he or she controls equal to the number of lands controlled by the player who controls the fewest, then sacrifices the rest. Players discard cards and sacrifice creatures the same way. +SVar:BalanceHands:DB$ Balance | Zone$ Hand | SubAbility$ BalanceCreatures +SVar:BalanceCreatures:DB$ Balance | Valid$ Creature SVar:Picture:http://www.wizards.com/global/images/magic/general/balance.jpg -Oracle:Each player chooses a number of lands he or she controls equal to the number of lands controlled by the player who controls the fewest, then sacrifices the rest. Players discard cards and sacrifice creatures the same way. -SetInfo:2ED Rare -SetInfo:V09 Mythic -SetInfo:LEB Rare -SetInfo:LEA Rare -SetInfo:4ED Rare -SetInfo:3ED Rare \ No newline at end of file +Oracle:Each player chooses a number of lands he or she controls equal to the number of lands controlled by the player who controls the fewest, then sacrifices the rest. Players discard cards and sacrifice creatures the same way. \ No newline at end of file diff --git a/res/cardsfolder/b/balancing_act.txt b/res/cardsfolder/b/balancing_act.txt index 54a0e55d8f6..e5f860492ec 100644 --- a/res/cardsfolder/b/balancing_act.txt +++ b/res/cardsfolder/b/balancing_act.txt @@ -1,24 +1,8 @@ Name:Balancing Act ManaCost:2 W W Types:Sorcery -A:SP$ RepeatEach | Cost$ 2 W W | RepeatPlayers$ Player | RepeatSubAbility$ FindFewestPermanent | StackDescription$ SpellDescription | SubAbility$ DBChooseRepeat | SpellDescription$ Each player chooses a number of permanents he or she controls equal to the number of permanents controlled by the player who controls the fewest, then sacrifices the rest. Each player discards cards the same way. -SVar:FindFewestPermanent:DB$ StoreSVar | SVar$ MinPermanent | Type$ CountSVar | Expression$ NumPermanent | ConditionCheckSVar$ NumPermanent | ConditionSVarCompare$ LTMinPermanent -SVar:NumPermanent:Count$Valid Permanent.RememberedPlayerCtrl -SVar:MinPermanent:Number$9999 -SVar:DBChooseRepeat:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChoose | StackDescription$ None | SubAbility$ SacAll -SVar:DBChoose:DB$ ChooseCard | Defined$ Player.IsRemembered | Choices$ Permanent.RememberedPlayerCtrl | Amount$ MinPermanent | References$ MinPermanent | ChoiceTitle$ Choose permanents you control | RememberChosen$ True -SVar:SacAll:DB$ SacrificeAll | ValidCards$ Permanent.IsNotRemembered | SubAbility$ DBCleanup1 | StackDescription$ None -SVar:DBCleanup1:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBFindFewestHand -SVar:DBFindFewestHand:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ FindFewestHand | StackDescription$ None | SubAbility$ DBChooseRepeat2 -SVar:FindFewestHand:DB$ StoreSVar | SVar$ MinHand | Type$ CountSVar | Expression$ NumHand | ConditionCheckSVar$ NumHand | ConditionSVarCompare$ LTMinHand -SVar:MinHand:Number$9999 -SVar:NumHand:Count$ValidHand Card.RememberedPlayerCtrl -SVar:DBChooseRepeat2:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChooseHand | StackDescription$ None | SubAbility$ DisCardAll -SVar:DBChooseHand:DB$ ChooseCard | Defined$ Player.IsRemembered | Choices$ Card.RememberedPlayerCtrl | ChoiceZone$ Hand | Amount$ MinHand | References$ MinHand | ChoiceTitle$ Choose cards in your hand | RememberChosen$ True -SVar:DisCardAll:DB$ Discard | Mode$ Defined | DefinedCards$ ValidHand Card.IsNotRemembered | Defined$ Each | SubAbility$ DBCleanup2 -SVar:DBCleanup2:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBReset1 -SVar:DBReset1:DB$ StoreSVar | SVar$ MinPermanent | Type$ Number | Expression$ 9999 | SubAbility$ DBReset2 -SVar:DBReset2:DB$ StoreSVar | SVar$ MinHand | Type$ Number | Expression$ 9999 +A:SP$ Balance | Cost$ 2 W W | Valid$ Permanent | AILogic$ BalancePermanents | SubAbility$ BalanceHands | SpellDescription$ Each player chooses a number of permanents he or she controls equal to the number of permanents controlled by the player who controls the fewest, then sacrifices the rest. Each player discards cards the same way. +SVar:BalanceHands:DB$ Balance | Zone$ Hand SVar:Picture:http://www.wizards.com/global/images/magic/general/balancing_act.jpg SVar:RemAIDeck:True SVar:RemRandomDeck:True diff --git a/res/cardsfolder/r/restore_balance.txt b/res/cardsfolder/r/restore_balance.txt new file mode 100644 index 00000000000..c7b80a63ee5 --- /dev/null +++ b/res/cardsfolder/r/restore_balance.txt @@ -0,0 +1,11 @@ +Name:Restore Balance +ManaCost:no cost +Types:Sorcery +Colors:white +K:Suspend:6:W +A:SP$ Balance | Cost$ 1 W | Valid$ Land | AILogic$ BalanceCreaturesAndLands | SubAbility$ BalanceCreatures | SpellDescription$ Each player chooses a number of lands he or she controls equal to the number of lands controlled by the player who controls the fewest, then sacrifices the rest. Players discard cards and sacrifice creatures the same way. | ActivationLimit$ 0 +SVar:BalanceCreatures:DB$ Balance | Valid$ Creature | SubAbility$ BalanceHands +SVar:BalanceHands:DB$ Balance | Zone$ Hand +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/restore_balance.jpg +Oracle:Sorcery\nSuspend 6- {W} (Rather than cast this card from your hand, pay {W} and exile it with six time counters on it. At the beginning of your upkeep, remove a time counter. When the last is removed, cast it without paying its mana cost.)\nEach player chooses a number of lands he or she controls equal to the number of lands controlled by the player who controls the fewest, then sacrifices the rest. Players sacrifice creatures and discard cards the same way. diff --git a/src/main/java/forge/card/ability/ApiType.java b/src/main/java/forge/card/ability/ApiType.java index 2c6270a216d..dbce168c9fd 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.BalanceAi; import forge.card.ability.ai.BecomesBlockedAi; import forge.card.ability.ai.BondAi; import forge.card.ability.ai.CanPlayAsDrawbackAi; @@ -114,6 +115,7 @@ public enum ApiType { Animate (AnimateEffect.class, AnimateAi.class), AnimateAll (AnimateAllEffect.class, AnimateAllAi.class), Attach (AttachEffect.class, AttachAi.class), + Balance (BalanceEffect.class, BalanceAi.class), BecomesBlocked (BecomesBlockedEffect.class, BecomesBlockedAi.class), Bond (BondEffect.class, BondAi.class), ChangeTargets(ChangeTargetsEffect.class, CannotPlayAi.class), diff --git a/src/main/java/forge/card/ability/ai/BalanceAi.java b/src/main/java/forge/card/ability/ai/BalanceAi.java new file mode 100644 index 00000000000..624623967bf --- /dev/null +++ b/src/main/java/forge/card/ability/ai/BalanceAi.java @@ -0,0 +1,53 @@ +package forge.card.ability.ai; + +import java.util.List; + +import org.apache.commons.lang.math.RandomUtils; + +import forge.Card; +import forge.CardLists; +import forge.CardPredicates; +import forge.card.ability.SpellAbilityAi; +import forge.card.spellability.SpellAbility; +import forge.game.player.Player; +import forge.game.zone.ZoneType; + +public class BalanceAi extends SpellAbilityAi { + + /* (non-Javadoc) + * @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility) + */ + @Override + protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { + String logic = sa.getParam("AILogic"); + + int diff = 0; + // TODO Add support for multiplayer logic + final Player opp = aiPlayer.getOpponent(); + final List humPerms = opp.getCardsIn(ZoneType.Battlefield); + final List compPerms = aiPlayer.getCardsIn(ZoneType.Battlefield); + + if ("BalanceCreaturesAndLands".equals(logic)) { + // Copied over from hardcoded Balance. We should be checking value of the lands/creatures not just counting + diff += CardLists.filter(humPerms, CardPredicates.Presets.LANDS).size() - + CardLists.filter(compPerms, CardPredicates.Presets.LANDS).size(); + diff += 1.5 * ( CardLists.filter(humPerms, CardPredicates.Presets.CREATURES).size() - + CardLists.filter(compPerms, CardPredicates.Presets.CREATURES).size()); + } else if ("BalancePermanents".equals(logic)) { + // Don't cast if you have to sacrifice permanents + diff += humPerms.size() - compPerms.size(); + } + + if (diff < 0) { + // Don't sacrifice permanents even if opponent has a ton of cards in hand + return false; + } + + final List humHand = opp.getCardsIn(ZoneType.Hand); + final List compHand = aiPlayer.getCardsIn(ZoneType.Hand); + diff += 0.5 * (humHand.size() - compHand.size()); + + // Larger differential == more chance to actually cast this spell + return diff > 2 && RandomUtils.nextInt(100) < diff*10; + } +} diff --git a/src/main/java/forge/card/ability/effects/BalanceEffect.java b/src/main/java/forge/card/ability/effects/BalanceEffect.java new file mode 100644 index 00000000000..1dc63d17747 --- /dev/null +++ b/src/main/java/forge/card/ability/effects/BalanceEffect.java @@ -0,0 +1,61 @@ +package forge.card.ability.effects; + +import java.util.ArrayList; +import java.util.List; + +import forge.Card; +import forge.CardLists; +import forge.card.ability.SpellAbilityEffect; +import forge.card.spellability.SpellAbility; +import forge.game.Game; +import forge.game.player.Player; +import forge.game.zone.ZoneType; + +/** + * TODO: Write javadoc for this type. + * + */ +public class BalanceEffect extends SpellAbilityEffect { + + /* (non-Javadoc) + * @see forge.card.ability.SpellAbilityEffect#resolve(forge.card.spellability.SpellAbility) + */ + @Override + public void resolve(SpellAbility sa) { + Player activator = sa.getActivatingPlayer(); + Card source = sa.getSourceCard(); + Game game = activator.getGame(); + String valid = sa.hasParam("Valid") ? sa.getParam("Valid") : "Card"; + ZoneType zone = sa.hasParam("Zone") ? ZoneType.smartValueOf(sa.getParam("Zone")) : ZoneType.Battlefield; + + int min = Integer.MAX_VALUE; + + final List players = game.getPlayers(); + final List> validCards = new ArrayList>(players.size()); + + for(int i = 0; i < players.size(); i++) { + // Find the minimum of each Valid per player + validCards.add(CardLists.getValidCards(players.get(i).getCardsIn(zone), valid, activator, source)); + min = Math.min(min, validCards.get(i).size()); + } + + for(int i = 0; i < players.size(); i++) { + Player p = players.get(i); + int numToBalance = validCards.get(i).size() - min; + if (numToBalance == 0) { + continue; + } + if (zone.equals(ZoneType.Hand)) { + for (Card card : p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance)) { + if ( null == card ) continue; + p.discard(card, sa); + } + } else { // Battlefield + for(Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) { + if ( null == card ) continue; + game.getAction().sacrifice(card, sa); + } + } + } + } +} diff --git a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java index 8749a9d4bb4..212d372572c 100644 --- a/src/main/java/forge/card/cardfactory/CardFactorySorceries.java +++ b/src/main/java/forge/card/cardfactory/CardFactorySorceries.java @@ -17,12 +17,9 @@ */ package forge.card.cardfactory; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import forge.Card; import forge.CardLists; -import forge.CardPredicates; import forge.Singletons; import forge.CardPredicates.Presets; import forge.card.cost.Cost; @@ -45,108 +42,6 @@ import forge.gui.input.InputPayManaExecuteCommands; */ public class CardFactorySorceries { - private static final void balanceLands(Game game, Spell card) { - - int minLands = Integer.MAX_VALUE; - for (Player p : game.getPlayers()) { - int pL = p.getLandsInPlay().size(); - if( pL < minLands ) - minLands = pL; - } - - for (Player p : game.getPlayers()) { - - List l = p.getLandsInPlay(); - int sac = l.size() - minLands; - if (sac == 0) { - continue; - } - - List toSac = p.getController().choosePermanentsToSacrifice(card, sac, sac, l, "land(s)"); - for( Card crd : toSac ) - p.getGame().getAction().sacrifice(crd, card); - } - } - - private static final void balanceHands(Game game, Spell spell) { - int min = Integer.MAX_VALUE; - for (Player p : game.getPlayers()) { - min = Math.min(min, p.getZone(ZoneType.Hand).size()); - } - - for (Player p : game.getPlayers()) { - List hand = new ArrayList(p.getCardsIn(ZoneType.Hand)); - int sac = hand.size() - min; - if (sac == 0) { - continue; - } - - List toDiscard = p.getController().chooseCardsToDiscardFrom(p, spell, hand, sac, sac); // "Select %d more card(s) to discard" - for (Card c : toDiscard) - p.discard(c, spell); - } - } - - private static final void balanceCreatures(Game game, Spell card) { - List> creats = new ArrayList>(); - for (Player p : game.getPlayers()) { - creats.add(p.getCreaturesInPlay()); - } - int min = Integer.MAX_VALUE; - for (List h : creats) { - int s = h.size(); - min = Math.min(min, s); - } - Iterator> cc = creats.iterator(); - for (Player p : game.getPlayers()) { - - List c = cc.next(); - int sac = c.size() - min; - if (sac == 0) { - continue; - } - List toSac = p.getController().choosePermanentsToSacrifice(card, sac, sac, c, "creature(s)"); - - for( Card crd : toSac ) - p.getGame().getAction().sacrifice(crd, card); - } - } - - private static final SpellAbility getBalance(final Card card) { - return new Spell(card) { - private static final long serialVersionUID = -5941893280103164961L; - - @Override - public void resolve() { - final Game game = this.getActivatingPlayer().getGame(); - balanceLands(game, this); - balanceHands(game, this); - balanceCreatures(game, this); - } - - @Override - public boolean canPlayAI() { - int diff = 0; - final Player ai = getActivatingPlayer(); - final Player opp = ai.getOpponent(); - final List humLand = opp.getLandsInPlay(); - final List compLand = ai.getLandsInPlay(); - diff += humLand.size() - compLand.size(); - - final List humCreats = opp.getCreaturesInPlay(); - List compCreats = ai.getCreaturesInPlay(); - compCreats = CardLists.filter(compCreats, CardPredicates.Presets.CREATURES); - diff += 1.5 * (humCreats.size() - compCreats.size()); - - final List humHand = opp.getCardsIn(ZoneType.Hand); - final List compHand = ai.getCardsIn(ZoneType.Hand); - diff += 0.5 * (humHand.size() - compHand.size()); - - return diff > 2; - } - }; - } - private static final SpellAbility getTransmuteArtifact(final Card card) { /* * Sacrifice an artifact. If you do, search your library for an @@ -219,8 +114,7 @@ public class CardFactorySorceries { public static void buildCard(final Card card, final String cardName) { - if (cardName.equals("Balance")) { card.addSpellAbility(getBalance(card)); - } else if (cardName.equals("Transmute Artifact")) { card.addSpellAbility(getTransmuteArtifact(card)); + if (cardName.equals("Transmute Artifact")) { card.addSpellAbility(getTransmuteArtifact(card)); } } // getCard }