diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index ab9be7a2d7c..2d427ef70e1 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -181,20 +181,6 @@ public class PlayerControllerAi extends PlayerController { return selecteds; } - @Override - public List chooseFromTwoListsForEffect(FCollectionView optionList1, FCollectionView optionList2, - boolean optional, DelayedReveal delayedReveal, SpellAbility sa, String title, Player targetedPlayer) { - if (delayedReveal != null) { - reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix()); - } - T selected1 = chooseSingleEntityForEffect(optionList1, null, sa, title, optional, targetedPlayer); - T selected2 = chooseSingleEntityForEffect(optionList2, null, sa, title, optional || selected1!=null, targetedPlayer); - List selecteds = new ArrayList(); - if ( selected1 != null ) { selecteds.add(selected1); } - if ( selected2 != null ) { selecteds.add(selected2); } - return selecteds; - } - @Override public SpellAbility chooseSingleSpellForEffect(java.util.List spells, SpellAbility sa, String title, Map params) { @@ -1275,4 +1261,17 @@ public class PlayerControllerAi extends PlayerController { return chosenAmount; } + + @Override + public CardCollection chooseCardsForEffectMultiple(Map validMap, SpellAbility sa, String title) { + CardCollection choices = new CardCollection(); + + for (String mapKey: validMap.keySet()) { + CardCollection cc = validMap.get(mapKey); + cc.removeAll(choices); + choices.add(ComputerUtilCard.getBestAI(cc)); // TODO: should the AI limit itself here with the max number of cards in hand? + } + + return choices; + } } diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 30239f2e6cb..fcda7ac412e 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -62,6 +62,7 @@ public enum SpellApiToAi { .put(ApiType.Destroy, DestroyAi.class) .put(ApiType.DestroyAll, DestroyAllAi.class) .put(ApiType.Dig, DigAi.class) + .put(ApiType.DigMultiple, DigMultipleAi.class) .put(ApiType.DigUntil, DigUntilAi.class) .put(ApiType.Discard, DiscardAi.class) .put(ApiType.DrainMana, DrainManaAi.class) diff --git a/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java b/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java new file mode 100644 index 00000000000..07796e6fba9 --- /dev/null +++ b/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java @@ -0,0 +1,101 @@ +package forge.ai.ability; + +import forge.ai.ComputerUtil; +import forge.ai.SpellAbilityAi; +import forge.game.Game; +import forge.game.ability.AbilityUtils; +import forge.game.card.Card; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.player.PlayerActionConfirmMode; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + + +public class DigMultipleAi extends SpellAbilityAi { + /* (non-Javadoc) + * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) + */ + @Override + protected boolean canPlayAI(Player ai, SpellAbility sa) { + final Game game = ai.getGame(); + Player opp = ai.getWeakestOpponent(); + final Card host = sa.getHostCard(); + Player libraryOwner = ai; + + if (sa.usesTargeting()) { + sa.resetTargets(); + if (!opp.canBeTargetedBy(sa)) { + return false; + } else { + sa.getTargets().add(opp); + } + libraryOwner = opp; + } + + // return false if nothing to dig into + if (libraryOwner.getCardsIn(ZoneType.Library).isEmpty()) { + return false; + } + + if ("Never".equals(sa.getParam("AILogic"))) { + return false; + } else if ("AtOppEndOfTurn".equals(sa.getParam("AILogic"))) { + if (!(game.getPhaseHandler().getNextTurn() == ai && game.getPhaseHandler().is(PhaseType.END_OF_TURN))) { + return false; + } + } + + // don't deck yourself + if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) { + int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa); + if (libraryOwner == ai && ai.getCardsIn(ZoneType.Library).size() <= numToDig + 2) { + return false; + } + } + + // Don't use draw abilities before main 2 if possible + if (game.getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) && !sa.hasParam("ActivationPhases") + && !sa.hasParam("DestinationZone") && !ComputerUtil.castSpellInMain1(ai, sa)) { + return false; + } + + if (SpellAbilityAi.playReusable(ai, sa)) { + return true; + } + + if ((!game.getPhaseHandler().getNextTurn().equals(ai) + || game.getPhaseHandler().getPhase().isBefore(PhaseType.END_OF_TURN)) + && !sa.hasParam("PlayerTurn") && !SpellAbilityAi.isSorcerySpeed(sa) + && (ai.getCardsIn(ZoneType.Hand).size() > 1 || game.getPhaseHandler().getPhase().isBefore(PhaseType.DRAW)) + && !ComputerUtil.activateForCost(sa, ai)) { + return false; + } + + return !ComputerUtil.preventRunAwayActivations(sa); + } + + @Override + protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { + final Player opp = ai.getWeakestOpponent(); + if (sa.usesTargeting()) { + sa.resetTargets(); + if (mandatory && sa.canTarget(opp)) { + sa.getTargets().add(opp); + } else if (mandatory && sa.canTarget(ai)) { + sa.getTargets().add(ai); + } + } + + return true; + } + + /* (non-Javadoc) + * @see forge.card.ability.SpellAbilityAi#confirmAction(forge.card.spellability.SpellAbility, forge.game.player.PlayerActionConfirmMode, java.lang.String) + */ + @Override + public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { + return true; + } +} + diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 5596f94298d..7879d57558d 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -60,6 +60,7 @@ public enum ApiType { Destroy (DestroyEffect.class), DestroyAll (DestroyAllEffect.class), Dig (DigEffect.class), + DigMultiple (DigMultipleEffect.class), DigUntil (DigUntilEffect.class), Discard (DiscardEffect.class), DrainMana (DrainManaEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index 49f66a4fd69..7d8a8d4cab4 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -66,8 +66,6 @@ public class DigEffect extends SpellAbilityEffect { int destZone1ChangeNum = 1; final boolean mitosis = sa.hasParam("Mitosis"); String changeValid = sa.hasParam("ChangeValid") ? sa.getParam("ChangeValid") : ""; - //andOrValid is for cards with "creature card and/or a land card" - String andOrValid = sa.hasParam("AndOrValid") ? sa.getParam("AndOrValid") : ""; final boolean anyNumber = sa.hasParam("AnyNumber"); final int libraryPosition2 = sa.hasParam("LibraryPosition2") ? Integer.parseInt(sa.getParam("LibraryPosition2")) : -1; @@ -173,28 +171,18 @@ public class DigEffect extends SpellAbilityEffect { if (!noMove) { CardCollection movedCards; - CardCollection andOrCards; for (final Card c : top) { rest.add(c); } CardCollection valid; if (mitosis) { valid = sharesNameWithCardOnBattlefield(game, top); - andOrCards = new CardCollection(); } else if (!changeValid.isEmpty()) { if (changeValid.contains("ChosenType")) { changeValid = changeValid.replace("ChosenType", host.getChosenType()); } valid = CardLists.getValidCards(top, changeValid.split(","), host.getController(), host, sa); - if (!andOrValid.equals("")) { - andOrCards = CardLists.getValidCards(top, andOrValid.split(","), host.getController(), host, sa); - andOrCards.removeAll((Collection)valid); - valid.addAll(andOrCards); //pfps need to add andOr cards to valid to have set of all valid cards set up - } - else { - andOrCards = new CardCollection(); - } } else { // If all the cards are valid choices, no need for a separate reveal dialog to the chooser. pfps?? @@ -202,7 +190,6 @@ public class DigEffect extends SpellAbilityEffect { delayedReveal = null; } valid = top; - andOrCards = new CardCollection(); } if (forceRevealToController) { @@ -282,16 +269,13 @@ public class DigEffect extends SpellAbilityEffect { chooser.getController().tempShowCards(top); } List chosen = new ArrayList(); - if (!andOrValid.equals("")) { - valid.removeAll(andOrCards); //pfps remove andOr cards to get two two choices set up correctly - chosen = chooser.getController().chooseFromTwoListsForEffect(valid, andOrCards, optional, delayedReveal, sa, prompt, p); - } else { - int max = anyNumber ? valid.size() : Math.min(valid.size(),destZone1ChangeNum); - int min = (anyNumber || optional) ? 0 : max; - if ( max > 0 ) { // if max is 0 don't make a choice - chosen = chooser.getController().chooseEntitiesForEffect(valid, min, max, delayedReveal, sa, prompt, p); - } + + int max = anyNumber ? valid.size() : Math.min(valid.size(),destZone1ChangeNum); + int min = (anyNumber || optional) ? 0 : max; + if ( max > 0 ) { // if max is 0 don't make a choice + chosen = chooser.getController().chooseEntitiesForEffect(valid, min, max, delayedReveal, sa, prompt, p); } + chooser.getController().endTempShowCards(); movedCards.addAll(chosen); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java new file mode 100644 index 00000000000..1ca132be140 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java @@ -0,0 +1,167 @@ +package forge.game.ability.effects; + +import java.util.Collections; +import java.util.Map; + +import com.google.common.collect.Maps; + +import forge.game.Game; +import forge.game.ability.AbilityUtils; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.card.CardLists; +import forge.game.card.CardZoneTable; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.zone.PlayerZone; +import forge.game.zone.ZoneType; + +public class DigMultipleEffect extends SpellAbilityEffect { + + @Override + public void resolve(SpellAbility sa) { + // TODO Auto-generated method stub + final Card host = sa.getHostCard(); + final Player player = sa.getActivatingPlayer(); + final Game game = player.getGame(); + Player chooser = player; + int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa); + + final ZoneType srcZone = sa.hasParam("SourceZone") ? ZoneType.smartValueOf(sa.getParam("SourceZone")) : ZoneType.Library; + + final ZoneType destZone1 = sa.hasParam("DestinationZone") ? ZoneType.smartValueOf(sa.getParam("DestinationZone")) : ZoneType.Hand; + final ZoneType destZone2 = sa.hasParam("DestinationZone2") ? ZoneType.smartValueOf(sa.getParam("DestinationZone2")) : ZoneType.Library; + int libraryPosition = sa.hasParam("LibraryPosition") ? Integer.parseInt(sa.getParam("LibraryPosition")) : -1; + final int libraryPosition2 = sa.hasParam("LibraryPosition2") ? Integer.parseInt(sa.getParam("LibraryPosition2")) : -1; + + String changeValid = sa.hasParam("ChangeValid") ? sa.getParam("ChangeValid") : ""; + + CardZoneTable table = new CardZoneTable(); + for (final Player p : getTargetPlayers(sa)) { + if (sa.usesTargeting() && !p.canBeTargetedBy(sa)) { + continue; + } + final CardCollection top = new CardCollection(); + final CardCollection rest = new CardCollection(); + final PlayerZone sourceZone = p.getZone(srcZone); + + numToDig = Math.min(numToDig, sourceZone.size()); + for (int i = 0; i < numToDig; i++) { + top.add(sourceZone.get(i)); + } + + if (top.isEmpty()) { + continue; + } + + rest.addAll(top); + + if (sa.hasParam("Reveal")) { + game.getAction().reveal(top, p, false); + } else { + // reveal cards first + game.getAction().revealTo(top, player); + } + + Map validMap = Maps.newHashMap(); + + for (final String valid : changeValid.split(",")) { + CardCollection list = CardLists.getValidCards(top, valid, host.getController(), host, sa); + if (!list.isEmpty()) { + validMap.put(valid, list); + } + } + + if (validMap.isEmpty()) { + chooser.getController().notifyOfValue(sa, null, "No valid cards"); + continue; + } + + CardCollection chosen = chooser.getController().chooseCardsForEffectMultiple(validMap, sa, "Choose cards"); + + if (!chosen.isEmpty()) { + game.getAction().reveal(chosen, chooser, true, + chooser + " picked " + (chosen.size() == 1 ? "this card" : "these cards") + " from "); + } + + for (Card c : chosen) { + final ZoneType origin = c.getZone().getZoneType(); + final PlayerZone zone = c.getOwner().getZone(destZone1); + + if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) { + if (libraryPosition == -1 || libraryPosition > zone.size()) { + libraryPosition = zone.size(); + } + c = game.getAction().moveTo(zone, c, libraryPosition, sa); + } + else { + c = game.getAction().moveTo(zone, c, sa); + if (destZone1.equals(ZoneType.Battlefield)) { + if (sa.hasParam("Tapped")) { + c.setTapped(true); + } + } + } + if (!origin.equals(c.getZone().getZoneType())) { + table.put(origin, c.getZone().getZoneType(), c); + } + + if (sa.hasParam("ExileFaceDown")) { + c.turnFaceDown(true); + } + if (sa.hasParam("Imprint")) { + host.addImprintedCard(c); + } + if (sa.hasParam("ForgetOtherRemembered")) { + host.clearRemembered(); + } + if (sa.hasParam("RememberChanged")) { + host.addRemembered(c); + } + rest.remove(c); + } + + // now, move the rest to destZone2 + if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck || destZone2 == ZoneType.SchemeDeck + || destZone2 == ZoneType.Graveyard) { + CardCollection afterOrder = rest; + if (sa.hasParam("RestRandomOrder")) { + CardLists.shuffle(afterOrder); + } + if (libraryPosition2 != -1) { + // Closest to top + Collections.reverse(afterOrder); + } + for (final Card c : afterOrder) { + final ZoneType origin = c.getZone().getZoneType(); + Card m; + if (destZone2 == ZoneType.Library) { + m = game.getAction().moveToLibrary(c, libraryPosition2, sa); + } + else { + m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa); + } + if (m != null && !origin.equals(m.getZone().getZoneType())) { + table.put(origin, m.getZone().getZoneType(), m); + } + } + } + else { + // just move them randomly + for (int i = 0; i < rest.size(); i++) { + Card c = rest.get(i); + final ZoneType origin = c.getZone().getZoneType(); + final PlayerZone toZone = c.getOwner().getZone(destZone2); + c = game.getAction().moveTo(toZone, c, sa); + if (!origin.equals(c.getZone().getZoneType())) { + table.put(origin, c.getZone().getZoneType(), c); + } + } + } + } + //table trigger there + table.triggerChangesZoneAll(game); + } + +} diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index eb432ef4d5e..96f146e564f 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -115,7 +115,6 @@ public abstract class PlayerController { Map params); public abstract List chooseEntitiesForEffect(FCollectionView optionList, int min, int max, DelayedReveal delayedReveal, SpellAbility sa, String title, Player relatedPlayer); - public abstract List chooseFromTwoListsForEffect(FCollectionView optionList1, FCollectionView optionList2, boolean optional, DelayedReveal delayedReveal, SpellAbility sa, String title, Player relatedPlayer); public abstract boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message); public abstract boolean confirmBidAction(SpellAbility sa, PlayerActionConfirmMode bidlife, String string, int bid, Player winner); @@ -273,4 +272,7 @@ public abstract class PlayerController { public abstract List chooseOptionalCosts(SpellAbility choosen, List optionalCostValues); public abstract boolean confirmMulliganScry(final Player p); + + public abstract CardCollection chooseCardsForEffectMultiple(Map validMap, + SpellAbility sa, String title); } diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java index b34414575e6..f8a0f1eff77 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java @@ -180,12 +180,6 @@ public class PlayerControllerForTests extends PlayerController { return null; } - @Override - public List chooseFromTwoListsForEffect(FCollectionView optionList1, FCollectionView optionList2, boolean optional, DelayedReveal delayedReveal, SpellAbility sa, String title, Player targetedPlayer) { - // this isn't used - return null; - } - @Override public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) { return true; @@ -703,4 +697,9 @@ public class PlayerControllerForTests extends PlayerController { return 0; } + @Override + public CardCollection chooseCardsForEffectMultiple(Map validMap, SpellAbility sa, String title) { + // TODO Auto-generated method stub + return new CardCollection(); + } } diff --git a/forge-gui/res/cardsfolder/b/benefaction_of_rhonas.txt b/forge-gui/res/cardsfolder/b/benefaction_of_rhonas.txt index 3573c623f5e..9241011976b 100644 --- a/forge-gui/res/cardsfolder/b/benefaction_of_rhonas.txt +++ b/forge-gui/res/cardsfolder/b/benefaction_of_rhonas.txt @@ -1,6 +1,6 @@ Name:Benefaction of Rhonas ManaCost:2 G Types:Sorcery -A:SP$ Dig | Cost$ 2 G | DigNum$ 5 | Reveal$ True | ChangeNum$ 2 | ChangeValid$ Creature | AndOrValid$ Enchantment | DestinationZone2$ Graveyard | Optional$ True | SpellDescription$ Reveal the top five cards of your library. You may put a creature card and/or an enchantment card from among them into your hand. Put the rest into your graveyard. +A:SP$ DigMultiple | Cost$ 2 G | DigNum$ 5 | Reveal$ True | ChangeValid$ Creature,Enchantment | DestinationZone2$ Graveyard | SpellDescription$ Reveal the top five cards of your library. You may put a creature card and/or an enchantment card from among them into your hand. Put the rest into your graveyard. SVar:Picture:http://www.wizards.com/global/images/magic/general/benefaction_of_rhonas.jpg -Oracle:Reveal the top five cards of your library. You may put a creature card and/or an enchantment card from among them into your hand. Put the rest into your graveyard. \ No newline at end of file +Oracle:Reveal the top five cards of your library. You may put a creature card and/or an enchantment card from among them into your hand. Put the rest into your graveyard. diff --git a/forge-gui/res/cardsfolder/d/domris_ambush.txt b/forge-gui/res/cardsfolder/d/domris_ambush.txt index d5ff83ebab0..bf945ed5ea6 100644 --- a/forge-gui/res/cardsfolder/d/domris_ambush.txt +++ b/forge-gui/res/cardsfolder/d/domris_ambush.txt @@ -1,8 +1,8 @@ Name:Domri's Ambush ManaCost:R G Types:Sorcery -A:SP$ PutCounter | Cost$ R G | ValidTgts$ Creature.YouCtrl | CounterType$ P1P1 | TgtPrompt$ Select target creature you control to put a +1/+1 counter | SubAbility$ DBDamage | AILogic$ Fight | SpellDescription$ Put a +1/+1 counter on target creature you control. Then that creature deals damage equal to its power to target creature or planeswalker you don't control. -SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature.YouDontCtrl,Planeswalker.YouDontCtrl | TgtPrompt$ Select target creature or planeswalker you don't control | AILogic$ PowerDmg | NumDmg$ X | References$ X +A:SP$ PutCounter | Cost$ R G | ValidTgts$ Creature.YouCtrl | CounterType$ P1P1 | TgtPrompt$ Select target creature you control to put a +1/+1 counter | SubAbility$ DBDamage | AILogic$ PowerDmg | SpellDescription$ Put a +1/+1 counter on target creature you control. Then that creature deals damage equal to its power to target creature or planeswalker you don't control. +SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature.YouDontCtrl,Planeswalker.YouDontCtrl | TgtPrompt$ Select target creature or planeswalker you don't control | AILogic$ PowerDmg | NumDmg$ X | References$ X | DamageSource$ ParentTarget SVar:X:ParentTargeted$CardPower DeckHas:Ability$Counters Oracle:Put a +1/+1 counter on target creature you control. Then that creature deals damage equal to its power to target creature or planeswalker you don't control. diff --git a/forge-gui/res/cardsfolder/g/gift_of_the_gargantuan.txt b/forge-gui/res/cardsfolder/g/gift_of_the_gargantuan.txt index 274c19be107..3e1caceb1d7 100644 --- a/forge-gui/res/cardsfolder/g/gift_of_the_gargantuan.txt +++ b/forge-gui/res/cardsfolder/g/gift_of_the_gargantuan.txt @@ -1,6 +1,6 @@ Name:Gift of the Gargantuan ManaCost:2 G Types:Sorcery -A:SP$ Dig | Cost$ 2 G | DigNum$ 4 | ChangeValid$ Creature | AndOrValid$ Land | ChangeNum$ 2 | Optional$ True | SpellDescription$ Look at the top four cards of your library. You may reveal a creature card and/or a land card from among them and put the revealed cards into your hand. Put the rest on the bottom of your library in any order. +A:SP$ DigMultiple | Cost$ 2 G | DigNum$ 4 | ChangeValid$ Creature,Land | SpellDescription$ Look at the top four cards of your library. You may reveal a creature card and/or a land card from among them and put the revealed cards into your hand. Put the rest on the bottom of your library in any order. SVar:Picture:http://www.wizards.com/global/images/magic/general/gift_of_the_gargantuan.jpg Oracle:Look at the top four cards of your library. You may reveal a creature card and/or a land card from among them and put the revealed cards into your hand. Put the rest on the bottom of your library in any order. diff --git a/forge-gui/res/cardsfolder/k/kaalia_zenith_seeker.txt b/forge-gui/res/cardsfolder/k/kaalia_zenith_seeker.txt new file mode 100644 index 00000000000..1e63c75d05b --- /dev/null +++ b/forge-gui/res/cardsfolder/k/kaalia_zenith_seeker.txt @@ -0,0 +1,9 @@ +Name:Kaalia, Zenith Seeker +ManaCost:R W B +Types:Legendary Creature Human Cleric +PT:3/3 +K:Flying +K:Vigilance +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDigMulti | TriggerDescription$ When CARDNAME enters the battlefield, look at the top six cards of your library. You may reveal an Angel card, a Demon card, and/or a Dragon card from among them and put them into your hand. Put the rest on the bottom of your library in a random order. +SVar:TrigDigMulti:DB$ DigMultiple | DigNum$ 6 | ChangeValid$ Card.Angel,Card.Demon,Card.Dragon | SourceZone$ Library | DestinationZone$ Hand | DestinationZone2$ Library | LibraryPosition$ -1 | RestRandomOrder$ True +Oracle:Flying, vigilance\nWhen Kaalia, Zenith Seeker enters the battlefield, look at the top six cards of your library. You may reveal an Angel card, a Demon card, and/or a Dragon card from among them and put them into your hand. Put the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/k/kiora_master_of_the_depths.txt b/forge-gui/res/cardsfolder/k/kiora_master_of_the_depths.txt index 295d674bf33..fd562b26db6 100644 --- a/forge-gui/res/cardsfolder/k/kiora_master_of_the_depths.txt +++ b/forge-gui/res/cardsfolder/k/kiora_master_of_the_depths.txt @@ -4,7 +4,7 @@ Types:Legendary Planeswalker Kiora Loyalty:4 A:AB$ Untap | Cost$ AddCounter<1/LOYALTY> | ValidTgts$ Creature | TgtPrompt$ Choose target creature | TargetMin$ 0 | TargetMax$ 1 | Planeswalker$ True | SubAbility$ DBUntap | SpellDescription$ Untap up to one target creature and up to one target land. SVar:DBUntap:DB$ Untap | ValidTgts$ Land | TgtPrompt$ Choose target land | TargetMin$ 0 | TargetMax$ 1 -A:AB$ Dig | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | DigNum$ 4 | Reveal$ True | ChangeNum$ 2 | ChangeValid$ Creature | AndOrValid$ Land | DestinationZone2$ Graveyard | Optional$ True | SpellDescription$ Reveal the top four cards of your library. You may put a creature card and/or a land card from among them into your hand. Put the rest into your graveyard. +A:AB$ DigMultiple | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | DigNum$ 4 | Reveal$ True | ChangeValid$ Creature,Land | DestinationZone2$ Graveyard | Optional$ True | SpellDescription$ Reveal the top four cards of your library. You may put a creature card and/or a land card from among them into your hand. Put the rest into your graveyard. A:AB$ Effect | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem - Kiora, Master of the Depths | Image$ emblem_kiora_master_of_the_depths | Triggers$ TrigFight | SVars$ DBFight | Duration$ Permanent | AILogic$ Always | SubAbility$ DBToken | SpellDescription$ You get an emblem with "Whenever a creature enters the battlefield under your control, you may have it fight target creature." Then create three 8/8 blue Octopus creature tokens. SVar:TrigFight:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | Execute$ DBFight | OptionalDecider$ You | TriggerZones$ Command | TriggerDescription$ Whenever a creature enters the battlefield under your control, you may have it fight target creature. SVar:DBFight:DB$ Fight | Defined$ TriggeredCardLKICopy | ValidTgts$ Creature | TgtPrompt$ Choose target creature diff --git a/forge-gui/src/main/java/forge/match/input/InputSelectFromTwoLists.java b/forge-gui/src/main/java/forge/match/input/InputSelectFromTwoLists.java deleted file mode 100644 index 90c6b717240..00000000000 --- a/forge-gui/src/main/java/forge/match/input/InputSelectFromTwoLists.java +++ /dev/null @@ -1,152 +0,0 @@ -package forge.match.input; - -import java.util.Collection; -import java.util.List; -import java.util.ArrayList; - -import forge.game.GameEntity; -import forge.game.card.Card; -import forge.game.card.CardView; - -import forge.game.player.Player; -import forge.game.spellability.SpellAbility; -import forge.player.PlayerControllerHuman; -import forge.util.collect.FCollection; -import forge.util.collect.FCollectionView; -import forge.util.ITriggerEvent; -import forge.player.PlayerZoneUpdate; -import forge.player.PlayerZoneUpdates; -import forge.game.zone.Zone; -import forge.FThreads; - -public class InputSelectFromTwoLists extends InputSelectManyBase { - private final FCollectionView valid1, valid2; - private final FCollection validBoth; - private FCollectionView validChoices; - - protected final FCollection selected = new FCollection(); - protected final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates(); - protected Iterable zonesShown; // want to hide these zones when input done - - public InputSelectFromTwoLists(final PlayerControllerHuman controller, final boolean optional, - final FCollectionView list1, final FCollectionView list2, final SpellAbility sa0) { - super(controller, optional?0:1, 2, sa0); - valid1 = list1; - valid2 = list2; - validBoth = new FCollection(valid1); - for ( T v : valid2 ) { validBoth.add(v); } - validChoices = validBoth; - setSelectables(); - - for (final GameEntity c : validChoices) { - final Zone cz = (c instanceof Card) ? ((Card) c).getZone() : null ; - if ( cz != null ) { - zonesToUpdate.add(new PlayerZoneUpdate(cz.getPlayer().getView(),cz.getZoneType())); - } - } - FThreads.invokeInEdtNowOrLater(new Runnable() { - @Override public void run() { - controller.getGui().updateZones(zonesToUpdate); - zonesShown = controller.getGui().tempShowZones(controller.getPlayer().getView(),zonesToUpdate); - } - }); - } - - private void setSelectables() { - ArrayList vCards = new ArrayList(); - getController().getGui().clearSelectables(); - for ( T c : validChoices ) { - if ( c instanceof Card ) { - vCards.add(((Card)c).getView()) ; - } - } - getController().getGui().setSelectables(vCards); - } - - private void setValid() { - boolean selected1 = false, selected2 = false; - for ( T s : selected ) { - if ( valid1.contains(s) ) { selected1 = true; } - if ( valid2.contains(s) ) { selected2 = true; } - } - validChoices = selected1 ? ( selected2 ? FCollection.getEmpty() : valid2 ) : ( selected2 ? valid1 : validBoth ); - setSelectables(); - FThreads.invokeInEdtNowOrLater(new Runnable() { - @Override public void run() { - getController().getGui().updateZones(zonesToUpdate); - } - }); - } - - @Override - protected boolean onCardSelected(final Card c, final List otherCardsToSelect, final ITriggerEvent triggerEvent) { - if (!selectEntity(c)) { - return false; - } - refresh(); - return true; - } - - @Override - public String getActivateAction(final Card card) { - if (validChoices.contains(card)) { - if (selected.contains(card)) { - return "unselect card"; - } - return "select card"; - } - return null; - } - - @Override - protected void onPlayerSelected(final Player p, final ITriggerEvent triggerEvent) { - if (!selectEntity(p)) { - return; - } - refresh(); - } - - @Override - public final Collection getSelected() { - return selected; - } - - @SuppressWarnings("unchecked") - protected boolean selectEntity(final GameEntity c) { - if (!validChoices.contains(c) && !selected.contains(c)) { - return false; - } - - final boolean entityWasSelected = selected.contains(c); - if (entityWasSelected) { - selected.remove(c); - } - else { - selected.add((T)c); - } - setValid(); - onSelectStateChanged(c, !entityWasSelected); - - return true; - } - - // might re-define later - @Override - protected boolean hasEnoughTargets() { return selected.size() >= min; } - @Override - protected boolean hasAllTargets() { return selected.size() >= max; } - - @Override - protected String getMessage() { - return max == Integer.MAX_VALUE - ? String.format(message, selected.size()) - : String.format(message, max - selected.size()); - } - - @Override - protected void onStop() { - getController().getGui().hideZones(getController().getPlayer().getView(),zonesShown); - getController().getGui().clearSelectables(); - super.onStop(); - } -} diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 8cf6f495b0f..efaf8a53e5a 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -511,41 +511,6 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont return results; } - @Override - public List chooseFromTwoListsForEffect(final FCollectionView optionList1, final FCollectionView optionList2, - boolean optional, final DelayedReveal delayedReveal, final SpellAbility sa, final String title, final Player targetedPlayer) { - // Human is supposed to read the message and understand from it what to choose - // useful details for debugging problems with the mass select logic - Sentry.getContext().addExtra("Card", sa.getCardView().toString()); - Sentry.getContext().addExtra("SpellAbility", sa.toString()); - - if (delayedReveal != null) { - tempShow(delayedReveal.getCards()); - } - - tempShow(optionList1); - tempShow(optionList2); - - if (useSelectCardsInput(optionList1) && useSelectCardsInput(optionList2)) { - final InputSelectFromTwoLists input = new InputSelectFromTwoLists(this, optional, optionList1, optionList2, sa); - input.setCancelAllowed(optional); - input.setMessage(MessageUtil.formatMessage(title, player, targetedPlayer)); - input.showAndWait(); - endTempShowCards(); - return (List) input.getSelected(); - } - - final GameEntityView result1 = getGui().chooseSingleEntityForEffect(title, GameEntityView.getEntityCollection(optionList1), null, optional); - final GameEntityView result2 = getGui().chooseSingleEntityForEffect(title, GameEntityView.getEntityCollection(optionList2), null, (result1==null)?optional:true); - endTempShowCards(); - List results = new ArrayList<>(); - GameEntity entity1 = convertToEntity(result1); - if (entity1!=null) { results.add((T) entity1); } - GameEntity entity2 = convertToEntity(result2); - if (entity2!=null) { results.add((T) entity2); } - return results; - } - @Override public int chooseNumber(final SpellAbility sa, final String title, final int min, final int max) { if (min >= max) { @@ -2945,5 +2910,14 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont return v == null ? 0 : v.intValue(); } + @Override + public CardCollection chooseCardsForEffectMultiple(Map validMap, SpellAbility sa, String title) { + CardCollection result = new CardCollection(); + for (Map.Entry e : validMap.entrySet()) { + result.addAll(chooseCardsForEffect(e.getValue(), sa, title + " " + e.getKey(), 0, 1, true)); + } + return result; + } + }