diff --git a/.gitattributes b/.gitattributes index cf56ad90f6b..1611d7b7897 100644 --- a/.gitattributes +++ b/.gitattributes @@ -48,6 +48,7 @@ forge-ai/src/main/java/forge/ai/ability/CharmAi.java -text forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java -text forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java -text forge-ai/src/main/java/forge/ai/ability/ChooseColorAi.java -text +forge-ai/src/main/java/forge/ai/ability/ChooseDirectionAi.java -text forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java -text forge-ai/src/main/java/forge/ai/ability/ChooseNumberAi.java -text forge-ai/src/main/java/forge/ai/ability/ChoosePlayerAi.java -text @@ -307,6 +308,7 @@ forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java -text forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java -text forge-game/src/main/java/forge/game/ability/effects/ChooseCardNameEffect.java -text forge-game/src/main/java/forge/game/ability/effects/ChooseColorEffect.java -text +forge-game/src/main/java/forge/game/ability/effects/ChooseDirectionEffect.java -text forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java -text forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java -text forge-game/src/main/java/forge/game/ability/effects/ChoosePlayerEffect.java -text @@ -9560,6 +9562,7 @@ forge-gui/res/cardsfolder/o/ordeal_of_purphoros.txt -text forge-gui/res/cardsfolder/o/ordeal_of_thassa.txt -text forge-gui/res/cardsfolder/o/order_chaos.txt -text forge-gui/res/cardsfolder/o/order_of_leitbur.txt svneol=native#text/plain +forge-gui/res/cardsfolder/o/order_of_succession.txt -text forge-gui/res/cardsfolder/o/order_of_the_ebon_hand.txt svneol=native#text/plain forge-gui/res/cardsfolder/o/order_of_the_golden_cricket.txt svneol=native#text/plain forge-gui/res/cardsfolder/o/order_of_the_sacred_bell.txt svneol=native#text/plain diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index d5b375f5e2f..cad509f4e3b 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -41,6 +41,7 @@ import forge.card.mana.ManaCost; import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.DeckSection; +import forge.game.Direction; import forge.game.Game; import forge.game.GameActionUtil; import forge.game.GameEntity; @@ -1534,6 +1535,23 @@ public class AiController { } } + public boolean chooseDirection(SpellAbility sa) { + if( sa == null || sa.getApi() == null ) { + throw new UnsupportedOperationException(); + } + // Left:True; Right:False + if ("GainControl".equals(sa.getParam("AILogic")) && game.getPlayers().size() > 2) { + List creats = CardLists.getType(game.getCardsIn(ZoneType.Battlefield), "Creature"); + List left = CardLists.filterControlledBy(creats, game.getNextPlayerAfter(player, Direction.Left)); + List right = CardLists.filterControlledBy(creats, game.getNextPlayerAfter(player, Direction.Right)); + if (!left.isEmpty() || !right.isEmpty()) { + List all = new ArrayList(left); + all.addAll(right); + return left.contains(ComputerUtilCard.getBestCreatureAI(all)); + } + } + return MyRandom.getRandom().nextBoolean(); + } } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 266974678f7..ef5367f745d 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -503,6 +503,7 @@ public class PlayerControllerAi extends PlayerController { case TapOrUntap: return true; case UntapOrLeaveTapped: return defaultVal != null && defaultVal.booleanValue(); case UntapTimeVault: return false; // TODO Should AI skip his turn for time vault? + case LeftOrRight: return brains.chooseDirection(sa); default: return MyRandom.getRandom().nextBoolean(); } diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 4445fcb8e92..829d3f56f62 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -31,6 +31,7 @@ public enum SpellApiToAi { apiToClass.put(ApiType.Charm, CharmAi.class); apiToClass.put(ApiType.ChooseCard, ChooseCardAi.class); apiToClass.put(ApiType.ChooseColor, ChooseColorAi.class); + apiToClass.put(ApiType.ChooseDirection, ChooseDirectionAi.class); apiToClass.put(ApiType.ChooseNumber, ChooseNumberAi.class); apiToClass.put(ApiType.ChoosePlayer, ChoosePlayerAi.class); apiToClass.put(ApiType.ChooseSource, ChooseSourceAi.class); diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseDirectionAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseDirectionAi.java new file mode 100644 index 00000000000..bde68c42479 --- /dev/null +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseDirectionAi.java @@ -0,0 +1,32 @@ +package forge.ai.ability; + +import forge.ai.SpellAbilityAi; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; + +public class ChooseDirectionAi 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 String logic = sa.getParam("AILogic"); + if (logic == null) { + return false; + } else { + // TODO: default ai + } + return true; + } + + @Override + public boolean chkAIDrawback(SpellAbility sa, Player ai) { + return canPlayAI(ai, sa); + } + + @Override + protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { + return canPlayAI(ai, sa); + } +} diff --git a/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java b/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java index 3fea2d3662c..a8a82d58647 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RepeatEachAi.java @@ -1,6 +1,8 @@ package forge.ai.ability; import com.google.common.base.Predicate; + +import forge.ai.ComputerUtilCard; import forge.ai.SpellAbilityAi; import forge.game.card.Card; import forge.game.card.CardLists; @@ -11,6 +13,7 @@ import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -114,4 +117,9 @@ public class RepeatEachAi extends SpellAbilityAi { return true; } + + @Override + protected Card chooseSingleCard(Player ai, SpellAbility sa, Collection options, boolean isOptional, Player targetedPlayer) { + return ComputerUtilCard.getBestCreatureAI(options); + } } diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 1afe12ec748..b46801ea4dc 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -529,13 +529,25 @@ public class Game { * {@code null} if there are no players in the game. */ public Player getNextPlayerAfter(final Player playerTurn) { + return getNextPlayerAfter(playerTurn, this.turnOrder); + } + + /** + * Get the player whose turn it is after a given player's turn, taking turn + * order into account. + * @param playerTurn a {@link Player}, or {@code null}. + * @param turnOrder a {@link Direction} + * @return A {@link Player}, whose turn comes after the current player, or + * {@code null} if there are no players in the game. + */ + public Player getNextPlayerAfter(final Player playerTurn, final Direction turnOrder) { int iPlayer = roIngamePlayers.indexOf(playerTurn); if (roIngamePlayers.isEmpty()) { return null; } - final int shift = this.getTurnOrder().getShift(); + final int shift = turnOrder.getShift(); final int totalNumPlayers = allPlayers.size(); if (-1 == iPlayer) { // if playerTurn has just lost int iAlive; 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 75aec52ff79..ae2be086d44 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -30,6 +30,7 @@ public enum ApiType { Charm (CharmEffect.class), ChooseCard (ChooseCardEffect.class), ChooseColor (ChooseColorEffect.class), + ChooseDirection (ChooseDirectionEffect.class), ChooseNumber (ChooseNumberEffect.class), ChoosePlayer (ChoosePlayerEffect.class), ChooseSource (ChooseSourceEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseDirectionEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseDirectionEffect.java new file mode 100644 index 00000000000..0bcfd5424fd --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseDirectionEffect.java @@ -0,0 +1,35 @@ +package forge.game.ability.effects; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.Lists; + +import forge.game.Direction; +import forge.game.Game; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.player.Player; +import forge.game.player.PlayerController.BinaryChoiceType; +import forge.game.spellability.SpellAbility; + +public class ChooseDirectionEffect extends SpellAbilityEffect { + + /* (non-Javadoc) + * @see forge.card.abilityfactory.SpellEffect#resolve(java.util.Map, forge.card.spellability.SpellAbility) + */ + @Override + public void resolve(final SpellAbility sa) { + final Card source = sa.getHostCard(); + final Game game = source.getGame(); + final List left = new ArrayList(game.getPlayers()); + // TODO: We'd better set up turn order UI here + final String info = "Left (clockwise): " + left + "\r\nRight (anticlockwise):" + Lists.reverse(left); + sa.getActivatingPlayer().getController().notifyOfValue(sa, source, info); + + boolean chosen = sa.getActivatingPlayer().getController().chooseBinary(sa, + "Choose a direction", BinaryChoiceType.LeftOrRight); + source.setChosenDirection(chosen ? Direction.Left : Direction.Right); + } + +} diff --git a/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java index 90f6e25105b..7887a791fde 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java @@ -117,8 +117,8 @@ public class RepeatEachEffect extends SpellAbilityEffect { } if (recordChoice) { boolean random = sa.hasParam("Random"); + Map> recordMap = new HashMap>(); if (sa.hasParam("ChoosePlayer")) { - Map> recordMap = new HashMap>(); for (Card card : repeatCards) { Player p; if (random) { @@ -132,14 +132,40 @@ public class RepeatEachEffect extends SpellAbilityEffect { recordMap.put(p, Lists.newArrayList(card)); } } - for (Entry> entry : recordMap.entrySet()) { - // Remember the player and imprint the cards - source.addRemembered(entry.getKey()); - source.getImprinted().addAll(entry.getValue()); - AbilityUtils.resolve(repeat); - source.removeRemembered(entry.getKey()); - source.getImprinted().removeAll(entry.getValue()); - } + } else if (sa.hasParam("ChooseCard")) { + List list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), + sa.getParam("ChooseCard"), source.getController(), source); + String filterController = sa.getParam("FilterControlledBy"); + // default: Starting with you and proceeding in the chosen direction + Player p = sa.getActivatingPlayer(); + do { + List valid = new ArrayList(list); + if ("NextPlayerInChosenDirection".equals(filterController)) { + valid = CardLists.filterControlledBy(valid, + game.getNextPlayerAfter(p, source.getChosenDirection())); + } + Card card = p.getController().chooseSingleEntityForEffect(valid, sa, "Choose a card"); + if (recordMap.containsKey(p)) { + recordMap.get(p).add(0, card); + } else { + recordMap.put(p, Lists.newArrayList(card)); + } + if (source.getChosenDirection() != null) { + p = game.getNextPlayerAfter(p, source.getChosenDirection()); + } else { + p = game.getNextPlayerAfter(p); + } + } while (!p.equals(sa.getActivatingPlayer())); + + } + + for (Entry> entry : recordMap.entrySet()) { + // Remember the player and imprint the cards + source.addRemembered(entry.getKey()); + source.getImprinted().addAll(entry.getValue()); + AbilityUtils.resolve(repeat); + source.removeRemembered(entry.getKey()); + source.getImprinted().removeAll(entry.getValue()); } } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index a1cc6be0053..c38b31f7f5d 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -207,6 +207,7 @@ public class Card extends GameEntity implements Comparable { private int chosenNumber; private Player chosenPlayer; private List chosenCard = new ArrayList(); + private Direction chosenDirection = null; private Card cloneOrigin = null; private final List clones = new ArrayList(); @@ -1725,6 +1726,14 @@ public class Card extends GameEntity implements Comparable { this.chosenCard = c; } + public Direction getChosenDirection() { + return chosenDirection; + } + + public void setChosenDirection(Direction chosenDirection) { + this.chosenDirection = chosenDirection; + } + // used for cards like Meddling Mage... /** *

@@ -1898,6 +1907,12 @@ public class Card extends GameEntity implements Comparable { sb.append("]\r\n"); } + if (this.chosenDirection != null) { + sb.append("\r\n[Chosen direction: "); + sb.append(this.getChosenDirection()); + sb.append("]\r\n"); + } + if (this.hauntedBy.size() != 0) { sb.append("Haunted by: "); for (final Card c : this.hauntedBy) { @@ -5476,7 +5491,7 @@ public class Card extends GameEntity implements Comparable { if (o instanceof Player) { if (!p.equals(o)) { return false; - } + } } } } @@ -5485,7 +5500,7 @@ public class Card extends GameEntity implements Comparable { if (o instanceof Player) { if (!p.equals(o)) { return false; - } + } } } } else if (property.startsWith("nonRememberedPlayerCtrl")) { 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 bb1b8dcf965..ee08ca28fae 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -65,6 +65,7 @@ public abstract class PlayerController { OddsOrEvens, UntapOrLeaveTapped, UntapTimeVault, + LeftOrRight, } protected final Game game; diff --git a/forge-gui/res/cardsfolder/o/order_of_succession.txt b/forge-gui/res/cardsfolder/o/order_of_succession.txt new file mode 100644 index 00000000000..781da7ee79d --- /dev/null +++ b/forge-gui/res/cardsfolder/o/order_of_succession.txt @@ -0,0 +1,8 @@ +Name:Order of Succession +ManaCost:3 U +Types:Sorcery +A:SP$ ChooseDirection | Cost$ 3 U | SubAbility$ DBRepeat | AILogic$ GainControl | SpellDescription$ Choose left or right. Starting with you and proceeding in the chosen direction, each player chooses a creature controlled by the next player in that direction. Each player gains control of the creature he or she chose. +SVar:DBRepeat:DB$ RepeatEach | RepeatSubAbility$ DBGainControl | RecordChoice$ True | ChooseCard$ Creature | FilterControlledBy$ NextPlayerInChosenDirection +SVar:DBGainControl:DB$ GainControl | NewController$ Remembered | AllValid$ Card.IsImprinted +SVar:RemAIDeck:True +SVar:Picture:http://www.wizards.com/global/images/magic/general/order_of_succession.jpg \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index f6fffd6e838..b472501c509 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -794,6 +794,8 @@ public class PlayerControllerHuman extends PlayerController { return ("Result: " + value); } switch(sa.getApi()) { + case ChooseDirection: + return value; case ChooseNumber: if (sa.hasParam("SecretlyChoose")) { return value;