From 3ce0c595cecd958d7ec2a42a46f5010bd58b495c Mon Sep 17 00:00:00 2001 From: drdev Date: Sun, 9 Mar 2014 20:44:44 +0000 Subject: [PATCH] Flesh out PlayerControllerHuman --- .gitattributes | 4 + .../forge/player/PlayerControllerHuman.java | 1127 ++++++++++++----- .../src/forge/player/TargetSelection.java | 387 ++++++ .../src/forge/screens/match/FControl.java | 54 +- .../screens/match/views/VAssignDamage.java | 438 +++++++ .../src/forge/screens/match/views/VField.java | 51 +- .../forge/screens/match/views/VPrompt.java | 3 - .../screens/match/views/VZoneDisplay.java | 10 +- .../src/forge/toolbox/FCardPanel.java | 14 +- .../src/forge/utils/GuiDisplayUtil.java | 485 +++++++ forge-m-base/src/forge/utils/GuiUtils.java | 139 ++ 11 files changed, 2384 insertions(+), 328 deletions(-) create mode 100644 forge-m-base/src/forge/player/TargetSelection.java create mode 100644 forge-m-base/src/forge/screens/match/views/VAssignDamage.java create mode 100644 forge-m-base/src/forge/utils/GuiDisplayUtil.java create mode 100644 forge-m-base/src/forge/utils/GuiUtils.java diff --git a/.gitattributes b/.gitattributes index dd8eb3cae1a..d352c14a76a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16053,6 +16053,7 @@ forge-m-base/src/forge/player/HumanPlay.java -text forge-m-base/src/forge/player/HumanPlaySpellAbility.java -text forge-m-base/src/forge/player/LobbyPlayerHuman.java -text forge-m-base/src/forge/player/PlayerControllerHuman.java -text +forge-m-base/src/forge/player/TargetSelection.java -text forge-m-base/src/forge/screens/FScreen.java -text forge-m-base/src/forge/screens/LaunchScreen.java -text forge-m-base/src/forge/screens/SplashScreen.java -text @@ -16092,6 +16093,7 @@ forge-m-base/src/forge/screens/match/input/InputSelectManyBase.java -text forge-m-base/src/forge/screens/match/input/InputSelectTargets.java -text forge-m-base/src/forge/screens/match/input/InputSynchronized.java -text forge-m-base/src/forge/screens/match/input/InputSyncronizedBase.java -text +forge-m-base/src/forge/screens/match/views/VAssignDamage.java -text forge-m-base/src/forge/screens/match/views/VAvatar.java -text forge-m-base/src/forge/screens/match/views/VField.java -text forge-m-base/src/forge/screens/match/views/VGameDetails.java -text @@ -16121,6 +16123,8 @@ forge-m-base/src/forge/utils/Constants.java -text forge-m-base/src/forge/utils/Evaluator.java -text forge-m-base/src/forge/utils/ForgePreferences.java -text forge-m-base/src/forge/utils/ForgeProfileProperties.java -text +forge-m-base/src/forge/utils/GuiDisplayUtil.java -text +forge-m-base/src/forge/utils/GuiUtils.java -text forge-m-base/src/forge/utils/Preferences.java -text forge-m-base/src/forge/utils/PreferencesStore.java -text forge-m-base/src/forge/utils/Utils.java -text diff --git a/forge-m-base/src/forge/player/PlayerControllerHuman.java b/forge-m-base/src/forge/player/PlayerControllerHuman.java index ec7f969aed2..8453c2c547c 100644 --- a/forge-m-base/src/forge/player/PlayerControllerHuman.java +++ b/forge-m-base/src/forge/player/PlayerControllerHuman.java @@ -1,16 +1,24 @@ package forge.player; +import com.google.common.base.Function; import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import forge.card.ColorSet; +import forge.card.MagicColor; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostShard; +import forge.deck.CardPool; import forge.deck.Deck; +import forge.deck.DeckSection; import forge.game.Game; import forge.game.GameEntity; +import forge.game.GameLogEntryType; import forge.game.GameObject; import forge.game.GameType; +import forge.game.ability.effects.CharmEffect; import forge.game.card.Card; import forge.game.card.CardShields; import forge.game.card.CounterType; @@ -19,6 +27,7 @@ import forge.game.cost.Cost; import forge.game.cost.CostPart; import forge.game.cost.CostPartMana; import forge.game.mana.Mana; +import forge.game.phase.PhaseType; import forge.game.player.LobbyPlayer; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; @@ -30,11 +39,35 @@ import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.TargetChoices; import forge.game.trigger.Trigger; import forge.game.trigger.WrappedAbility; +import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.item.PaperCard; +import forge.model.FModel; +import forge.screens.match.FControl; +import forge.screens.match.input.InputAttack; +import forge.screens.match.input.InputBlock; +import forge.screens.match.input.InputConfirm; +import forge.screens.match.input.InputConfirmMulligan; +import forge.screens.match.input.InputPassPriority; +import forge.screens.match.input.InputProliferate; +import forge.screens.match.input.InputSelectCardsForConvoke; +import forge.screens.match.input.InputSelectCardsFromList; +import forge.screens.match.input.InputSelectEntitiesFromList; +import forge.toolbox.FOptionPane; +import forge.toolbox.GuiChoose; +import forge.toolbox.GuiDialog; +import forge.util.Lang; +import forge.util.TextUtil; +import forge.utils.ForgePreferences.FPref; + +import org.apache.commons.lang3.Range; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import javax.swing.*; + +import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.*; @@ -49,468 +82,984 @@ public class PlayerControllerHuman extends PlayerController { super(game0, p, lp); } + public boolean isUiSetToSkipPhase(final Player turn, final PhaseType phase) { + return !FControl.stopAtPhase(turn, phase); + } + + /** + * Uses GUI to learn which spell the player (human in our case) would like to play + */ + public SpellAbility getAbilityToPlay(List abilities, MouseEvent triggerEvent) { + if (triggerEvent == null) { + if (abilities.isEmpty()) { + return null; + } + if (abilities.size() == 1) { + return abilities.get(0); + } + return GuiChoose.oneOrNone("Choose ability to play", abilities); + } + + if (abilities.isEmpty()) { + return null; + } + if (abilities.size() == 1 && !abilities.get(0).promptIfOnlyPossibleAbility()) { + if (abilities.get(0).canPlay()) { + return abilities.get(0); //only return ability if it's playable, otherwise return null + } + return null; + } + + for (final SpellAbility ab : abilities) { + if (ab.canPlay()) { + FControl.getView().getPrompt().getInputProxy().selectAbility(ab); + return ab; + } + } + return null; //TODO: delay ability until choice made + } + + /** + * TODO: Write javadoc for this method. + * @param c + */ + /**public void playFromSuspend(Card c) { + c.setSuspendCast(true); + HumanPlay.playCardWithoutPayingManaCost(player, c); + }**/ + + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#mayPlaySpellAbilityForFree(forge.card.spellability.SpellAbility) + */ @Override - public SpellAbility getAbilityToPlay(List abilities, - MouseEvent triggerEvent) { - // TODO Auto-generated method stub - return null; + public void playSpellAbilityForFree(SpellAbility copySA, boolean mayChoseNewTargets) { + HumanPlay.playSaWithoutPayingManaCost(player.getGame(), copySA, mayChoseNewTargets); } @Override - public void playSpellAbilityForFree(SpellAbility copySA, - boolean mayChoseNewTargets) { - // TODO Auto-generated method stub - - } - - @Override - public void playSpellAbilityNoStack(SpellAbility effectSA, - boolean mayChoseNewTargets) { - // TODO Auto-generated method stub - + public void playSpellAbilityNoStack(SpellAbility effectSA, boolean canSetupTargets) { + HumanPlay.playSpellAbilityNoStack(player, effectSA, !canSetupTargets); } + /* (non-Javadoc) + * @see forge.game.player.PlayerController#sideboard(forge.deck.Deck) + */ @Override public List sideboard(Deck deck, GameType gameType) { - // TODO Auto-generated method stub - return null; + CardPool sideboard = deck.get(DeckSection.Sideboard); + if (sideboard == null) { + // Use an empty cardpool instead of null for 75/0 sideboarding scenario. + sideboard = new CardPool(); + } + + CardPool main = deck.get(DeckSection.Main); + + + int mainSize = main.countAll(); + int sbSize = sideboard.countAll(); + int combinedDeckSize = mainSize + sbSize; + + int deckMinSize = Math.min(mainSize, gameType.getDecksFormat().getMainRange().getMinimum()); + Range sbRange = gameType.getDecksFormat().getSideRange(); + // Limited doesn't have a sideboard max, so let the Main min take care of things. + int sbMax = sbRange == null ? combinedDeckSize : sbRange.getMaximum(); + + List newMain = null; + + if (sbSize == 0 && mainSize == deckMinSize) { + // Skip sideboard loop if there are no sideboarding opportunities + return null; + } + else { + // conformance should not be checked here + boolean conform = FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY); + do { + if (newMain != null) { + String errMsg; + if (newMain.size() < deckMinSize) { + errMsg = String.format("Too few cards in your main deck (minimum %d), please make modifications to your deck again.", deckMinSize); + } + else { + errMsg = String.format("Too many cards in your sideboard (maximum %d), please make modifications to your deck again.", sbMax); + } + FOptionPane.showErrorDialog(errMsg, "Invalid Deck"); + } + // Sideboard rules have changed for M14, just need to consider min maindeck and max sideboard sizes + // No longer need 1:1 sideboarding in non-limited formats + newMain = GuiChoose.sideboard(sideboard.toFlatList(), main.toFlatList()); + } while (conform && (newMain.size() < deckMinSize || combinedDeckSize - newMain.size() > sbMax)); + } + return newMain; + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#assignCombatDamage() + */ + @Override + public Map assignCombatDamage(Card attacker, List blockers, int damageDealt, GameEntity defender, boolean overrideOrder) { + // Attacker is a poor name here, since the creature assigning damage + // could just as easily be the blocker. + Map map; + if (defender != null && assignDamageAsIfNotBlocked(attacker)) { + map = new HashMap(); + map.put(null, damageDealt); + } + else { + if ((attacker.hasKeyword("Trample") && defender != null) || (blockers.size() > 1)) { + map = FControl.getDamageToAssign(attacker, blockers, damageDealt, defender, overrideOrder); + } + else { + map = new HashMap(); + map.put(blockers.get(0), damageDealt); + } + } + return map; + } + + private final boolean assignDamageAsIfNotBlocked(Card attacker) { + return attacker.hasKeyword("CARDNAME assigns its combat damage as though it weren't blocked.") + || (attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.") + && GuiDialog.confirm(attacker, "Do you want to assign its combat damage as though it weren't blocked?")); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#announceRequirements(java.lang.String) + */ + @Override + public Integer announceRequirements(SpellAbility ability, String announce, boolean canChooseZero) { + int min = canChooseZero ? 0 : 1; + return GuiChoose.getInteger("Choose " + announce + " for " + ability.getHostCard().getName(), + min, Integer.MAX_VALUE, min + 9); } @Override - public List chooseCardsYouWonToAddToDeck(List losses) { - // TODO Auto-generated method stub - return null; + public List choosePermanentsToSacrifice(SpellAbility sa, int min, int max, List valid, String message) { + String outerMessage = "Select %d " + message + "(s) to sacrifice"; + return choosePermanentsTo(min, max, valid, outerMessage); } @Override - public Map assignCombatDamage(Card attacker, - List blockers, int damageDealt, GameEntity defender, - boolean overrideOrder) { - // TODO Auto-generated method stub - return null; + public List choosePermanentsToDestroy(SpellAbility sa, int min, int max, List valid, String message) { + String outerMessage = "Select %d " + message + "(s) to be destroyed"; + return choosePermanentsTo(min, max, valid, outerMessage); + } + + private List choosePermanentsTo(int min, int max, List valid, String outerMessage) { + max = Math.min(max, valid.size()); + if (max <= 0) { + return new ArrayList(); + } + + InputSelectCardsFromList inp = new InputSelectCardsFromList(min == 0 ? 1 : min, max, valid); + inp.setMessage(outerMessage); + inp.setCancelAllowed(min == 0); + inp.showAndWait(); + return Lists.newArrayList(inp.getSelected()); + } + + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseCardsForEffect(java.util.Collection, forge.card.spellability.SpellAbility, java.lang.String, int, boolean) + */ + @Override + public List chooseCardsForEffect(List sourceList, SpellAbility sa, String title, int min, int max, boolean isOptional) { + // If only one card to choose, use a dialog box. + // Otherwise, use the order dialog to be able to grab multiple cards in one shot + if (max == 1) { + Card singleChosen = chooseSingleEntityForEffect(sourceList, sa, title, isOptional); + return singleChosen == null ? Lists.newArrayList() : Lists.newArrayList(singleChosen); + } + FControl.highlightCard(sa.getHostCard()); + + // try to use InputSelectCardsFromList when possible + boolean cardsAreInMyHandOrBattlefield = true; + for(Card c : sourceList) { + Zone z = c.getZone(); + if ( z != null && ( z.is(ZoneType.Battlefield) || z.is(ZoneType.Hand, player))) + continue; + cardsAreInMyHandOrBattlefield = false; + break; + } + + if(cardsAreInMyHandOrBattlefield) { + InputSelectCardsFromList sc = new InputSelectCardsFromList(min, max, sourceList); + sc.setMessage(title); + sc.setCancelAllowed(isOptional); + sc.showAndWait(); + return Lists.newArrayList(sc.getSelected()); + } + + return GuiChoose.many(title, "Chosen Cards", min, max, sourceList, sa.getHostCard()); } @Override - public Integer announceRequirements(SpellAbility ability, String announce, - boolean allowZero) { - // TODO Auto-generated method stub - return null; + public T chooseSingleEntityForEffect(Collection options, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) { + // Human is supposed to read the message and understand from it what to choose + if (options.isEmpty()) { + return null; + } + if (!isOptional && options.size() == 1) { + return Iterables.getFirst(options, null); + } + + boolean canUseSelectCardsInput = true; + for (GameEntity c : options) { + if (c instanceof Player) + continue; + Zone cz = ((Card)c).getZone(); + // can point at cards in own hand and anyone's battlefield + boolean canUiPointAtCards = cz != null && (cz.is(ZoneType.Hand) && cz.getPlayer() == player || cz.is(ZoneType.Battlefield)); + if (!canUiPointAtCards) { + canUseSelectCardsInput = false; + break; + } + } + + if (canUseSelectCardsInput) { + InputSelectEntitiesFromList input = new InputSelectEntitiesFromList(isOptional ? 0 : 1, 1, options); + input.setCancelAllowed(isOptional); + input.setMessage(formatMessage(title, targetedPlayer)); + input.showAndWait(); + return Iterables.getFirst(input.getSelected(), null); + } + + return isOptional ? GuiChoose.oneOrNone(title, options) : GuiChoose.one(title, options); + } + + @Override + public int chooseNumber(SpellAbility sa, String title, int min, int max) { + final Integer[] choices = new Integer[max + 1 - min]; + for (int i = 0; i <= max - min; i++) { + choices[i] = Integer.valueOf(i + min); + } + return GuiChoose.one(title, choices).intValue(); + } + + @Override + public int chooseNumber(SpellAbility sa, String title, List choices, Player relatedPlayer) { + return GuiChoose.one(title, choices).intValue(); } @Override - public List choosePermanentsToSacrifice(SpellAbility sa, int min, - int max, List validTargets, String message) { - // TODO Auto-generated method stub - return null; + public SpellAbility chooseSingleSpellForEffect(java.util.List spells, SpellAbility sa, String title) { + // Human is supposed to read the message and understand from it what to choose + return spells.size() < 2 ? spells.get(0) : GuiChoose.one(title, spells); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#confirmAction(forge.card.spellability.SpellAbility, java.lang.String, java.lang.String) + */ + @Override + public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, String message) { + return GuiDialog.confirm(sa.getHostCard(), message); } @Override - public List choosePermanentsToDestroy(SpellAbility sa, int min, - int max, List validTargets, String message) { - // TODO Auto-generated method stub - return null; + public boolean confirmStaticApplication(Card hostCard, GameEntity affected, String logic, String message) { + return GuiDialog.confirm(hostCard, message); } @Override - public TargetChoices chooseNewTargetsFor(SpellAbility ability) { - // TODO Auto-generated method stub - return null; - } + public boolean confirmTrigger(SpellAbility sa, Trigger regtrig, Map triggerParams, boolean isMandatory) { + if (this.shouldAlwaysAcceptTrigger(regtrig.getId())) { + return true; + } + if (this.shouldAlwaysDeclineTrigger(regtrig.getId())) { + return false; + } - @Override - public boolean chooseTargetsFor(SpellAbility currentAbility) { - // TODO Auto-generated method stub - return false; - } + final StringBuilder buildQuestion = new StringBuilder("Use triggered ability of "); + buildQuestion.append(regtrig.getHostCard().toString()).append("?"); + HashMap tos = sa.getTriggeringObjects(); + if (tos.containsKey("Attacker")) { + buildQuestion.append("\nAttacker: " + tos.get("Attacker")); + } + if (tos.containsKey("Card")) { + Card card = (Card) tos.get("Card"); + if (card != null && (card.getController() == player || game.getZoneOf(card) == null + || game.getZoneOf(card).getZoneType().isKnown())) { + buildQuestion.append("\nTriggered by: " + tos.get("Card")); + } + } - @Override - public Pair chooseTarget( - SpellAbility sa, - List> allTargets) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List chooseCardsForEffect(List sourceList, - SpellAbility sa, String title, int min, int max, boolean isOptional) { - // TODO Auto-generated method stub - return null; - } - - @Override - public T chooseSingleEntityForEffect( - Collection sourceList, SpellAbility sa, String title, - boolean isOptional, Player relatedPlayer) { - // TODO Auto-generated method stub - return null; - } - - @Override - public SpellAbility chooseSingleSpellForEffect(List spells, - SpellAbility sa, String title) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean confirmAction(SpellAbility sa, PlayerActionConfirmMode mode, - String message) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean confirmStaticApplication(Card hostCard, GameEntity affected, - String logic, String message) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean confirmTrigger(SpellAbility sa, Trigger regtrig, - Map triggerParams, boolean isMandatory) { - // TODO Auto-generated method stub - return false; + InputConfirm inp = new InputConfirm(buildQuestion.toString()); + inp.showAndWait(); + return inp.getResult(); } @Override public boolean getWillPlayOnFirstTurn(boolean isFirstGame) { - // TODO Auto-generated method stub - return false; + String prompt = String.format("%s, you %s\n\nWould you like to play or draw?", + player.getName(), isFirstGame ? " have won the coin toss." : " lost the last game."); + InputConfirm inp = new InputConfirm(prompt, "Play", "Draw"); + inp.showAndWait(); + return inp.getResult(); } @Override public List orderBlockers(Card attacker, List blockers) { - // TODO Auto-generated method stub - return null; + FControl.highlightCard(attacker); + return GuiChoose.order("Choose Damage Order for " + attacker, "Damaged First", blockers, attacker); } @Override public List orderAttackers(Card blocker, List attackers) { - // TODO Auto-generated method stub - return null; + FControl.highlightCard(blocker); + return GuiChoose.order("Choose Damage Order for " + blocker, "Damaged First", attackers, blocker); } + /* (non-Javadoc) + * @see forge.game.player.PlayerController#reveal(java.lang.String, java.util.List, forge.game.zone.ZoneType, forge.game.player.Player) + */ @Override - public void reveal(Collection cards, ZoneType zone, Player owner, - String messagePrefix) { - // TODO Auto-generated method stub - - } - - @Override - public void notifyOfValue(SpellAbility saSource, GameObject realtedTarget, - String value) { - // TODO Auto-generated method stub - + public void reveal(Collection cards, ZoneType zone, Player owner, String message) { + if (StringUtils.isBlank(message)) { + message = "Looking at cards in {player's} " + zone.name(); + } + String fm = formatMessage(message, owner); + if ( !cards.isEmpty() ) + GuiChoose.reveal(fm, cards); + else + GuiDialog.message("There are no cards in the named location", fm); } @Override public ImmutablePair, List> arrangeForScry(List topN) { - // TODO Auto-generated method stub - return null; + List toBottom = null; + List toTop = null; + + if (topN.size() == 1) { + if (willPutCardOnTop(topN.get(0))) { + toTop = topN; + } + else { + toBottom = topN; + } + } + else { + toBottom = GuiChoose.many("Select cards to be put on the bottom of your library", "Cards to put on the bottom", -1, topN, null); + topN.removeAll(toBottom); + if (topN.isEmpty()) { + toTop = null; + } + else if (topN.size() == 1) { + toTop = topN; + } + else { + toTop = GuiChoose.order("Arrange cards to be put on top of your library", "Cards arranged", topN, null); + } + } + return ImmutablePair.of(toTop, toBottom); } @Override public boolean willPutCardOnTop(Card c) { - // TODO Auto-generated method stub - return false; + return GuiDialog.confirm(c, "Where will you put " + c.getName() + " in your library", new String[]{"Top", "Bottom"}); } @Override - public List orderMoveToZoneList(List cards, - ZoneType destinationZone) { - // TODO Auto-generated method stub - return null; + public List orderMoveToZoneList(List cards, ZoneType destinationZone) { + switch (destinationZone) { + case Library: + return GuiChoose.order("Choose order of cards to put into the library", "Closest to top", cards, null); + case Battlefield: + return GuiChoose.order("Choose order of cards to put onto the battlefield", "Put first", cards, null); + case Graveyard: + return GuiChoose.order("Choose order of cards to put into the graveyard", "Closest to bottom", cards, null); + case PlanarDeck: + return GuiChoose.order("Choose order of cards to put into the planar deck", "Closest to top", cards, null); + case SchemeDeck: + return GuiChoose.order("Choose order of cards to put into the scheme deck", "Closest to top", cards, null); + case Stack: + return GuiChoose.order("Choose order of copies to cast", "Put first", cards, null); + default: + System.out.println("ZoneType " + destinationZone + " - Not Ordered"); + break; + } + return cards; } @Override - public List chooseCardsToDiscardFrom(Player playerDiscard, - SpellAbility sa, List validCards, int min, int max) { - // TODO Auto-generated method stub - return null; + public List chooseCardsToDiscardFrom(Player p, SpellAbility sa, List valid, int min, int max) { + if (p != player) { + return GuiChoose.many("Choose " + min + " card" + (min != 1 ? "s" : "") + " to discard", + "Discarded", min, min, valid, null); + } + + InputSelectCardsFromList inp = new InputSelectCardsFromList(min, max, valid); + inp.setMessage(sa.hasParam("AnyNumber") ? "Discard up to %d card(s)" : "Discard %d card(s)"); + inp.showAndWait(); + return Lists.newArrayList(inp.getSelected()); } @Override public void playMiracle(SpellAbility miracle, Card card) { - // TODO Auto-generated method stub - + if (GuiDialog.confirm(card, card + " - Drawn. Play for Miracle Cost?")) { + HumanPlay.playSpellAbility(player, miracle); + } } @Override public List chooseCardsToDelve(int colorLessAmount, List grave) { - // TODO Auto-generated method stub - return null; + List toExile = new ArrayList(); + int cardsInGrave = grave.size(); + final Integer[] cntChoice = new Integer[cardsInGrave + 1]; + for (int i = 0; i <= cardsInGrave; i++) { + cntChoice[i] = Integer.valueOf(i); + } + + final Integer chosenAmount = GuiChoose.one("Exile how many cards?", cntChoice); + System.out.println("Delve for " + chosenAmount); + + for (int i = 0; i < chosenAmount; i++) { + final Card nowChosen = GuiChoose.oneOrNone("Exile which card?", grave); + + if (nowChosen == null) { + // User canceled,abort delving. + toExile.clear(); + break; + } + + grave.remove(nowChosen); + toExile.add(nowChosen); + } + return toExile; } + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseTargets(forge.card.spellability.SpellAbility, forge.card.spellability.SpellAbilityStackInstance) + */ @Override - public List chooseCardsToRevealFromHand(int min, int max, - List valid) { - // TODO Auto-generated method stub - return null; + public TargetChoices chooseNewTargetsFor(SpellAbility ability) { + if (ability.getTargetRestrictions() == null) { + return null; + } + TargetChoices oldTarget = ability.getTargets(); + TargetSelection select = new TargetSelection(ability); + ability.resetTargets(); + if (select.chooseTargets(oldTarget.getNumTargeted())) { + return ability.getTargets(); + } + else { + // Return old target, since we had to reset them above + return oldTarget; + } } + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseCardsToDiscardUnlessType(int, java.lang.String, forge.card.spellability.SpellAbility) + */ @Override - public List chooseCardsToDiscardUnlessType(int min, List hand, - String param, SpellAbility sa) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List chooseSaToActivateFromOpeningHand( - List usableFromOpeningHand) { - // TODO Auto-generated method stub - return null; + public List chooseCardsToDiscardUnlessType(int num, List hand, final String uType, SpellAbility sa) { + final InputSelectEntitiesFromList target = new InputSelectEntitiesFromList(num, num, hand) { + private static final long serialVersionUID = -5774108410928795591L; + + @Override + protected boolean hasAllTargets() { + for (Card c : selected) { + if (c.isType(uType)) { + return true; + } + } + return super.hasAllTargets(); + } + }; + target.setMessage("Select %d card(s) to discard, unless you discard a " + uType + "."); + target.showAndWait(); + return Lists.newArrayList(target.getSelected()); } + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseManaFromPool(java.util.List) + */ @Override public Mana chooseManaFromPool(List manaChoices) { - // TODO Auto-generated method stub - return null; + List options = new ArrayList(); + for (int i = 0; i < manaChoices.size(); i++) { + Mana m = manaChoices.get(i); + options.add(String.format("%d. %s mana from %s", 1+i, MagicColor.toLongString(m.getColor()), m.getSourceCard())); + } + String chosen = GuiChoose.one("Pay Mana from Mana Pool", options); + String idx = TextUtil.split(chosen, '.')[0]; + return manaChoices.get(Integer.parseInt(idx)-1); } + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseSomeType(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.String) + */ @Override - public String chooseSomeType(String kindOfType, SpellAbility sa, - List validTypes, List invalidTypes, - boolean isOptional) { - // TODO Auto-generated method stub - return null; + public String chooseSomeType(String kindOfType, SpellAbility sa, List validTypes, List invalidTypes, boolean isOptional) { + if(isOptional) + return GuiChoose.oneOrNone("Choose a " + kindOfType.toLowerCase() + " type", validTypes); + else + return GuiChoose.one("Choose a " + kindOfType.toLowerCase() + " type", validTypes); } + /* (non-Javadoc) + * @see forge.game.player.PlayerController#confirmReplacementEffect(forge.card.replacement.ReplacementEffect, forge.card.spellability.SpellAbility, java.lang.String) + */ @Override - public Pair chooseAndRemoveOrPutCounter( - Card cardWithCounter) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean confirmReplacementEffect( - ReplacementEffect replacementEffect, SpellAbility effectSA, - String question) { - // TODO Auto-generated method stub - return false; + public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, String question) { + return GuiDialog.confirm(replacementEffect.getHostCard(), question); } @Override public List getCardsToMulligan(boolean isCommander, Player firstPlayer) { - // TODO Auto-generated method stub - return null; + final InputConfirmMulligan inp = new InputConfirmMulligan(player, firstPlayer, isCommander); + inp.showAndWait(); + return inp.isKeepHand() ? null : isCommander ? inp.getSelectedCards() : player.getCardsIn(ZoneType.Hand); } @Override public void declareAttackers(Player attacker, Combat combat) { - // TODO Auto-generated method stub - + // This input should not modify combat object itself, but should return user choice + InputAttack inpAttack = new InputAttack(attacker, player, combat); + inpAttack.showAndWait(); } @Override public void declareBlockers(Player defender, Combat combat) { - // TODO Auto-generated method stub - + // This input should not modify combat object itself, but should return user choice + InputBlock inpBlock = new InputBlock(player, defender, combat); + inpBlock.showAndWait(); } @Override public SpellAbility chooseSpellAbilityToPlay() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void playChosenSpellAbility(SpellAbility sa) { - // TODO Auto-generated method stub + PhaseType phase = game.getPhaseHandler().getPhase(); + boolean maySkipPriority = mayAutoPass(phase) || isUiSetToSkipPhase(game.getPhaseHandler().getPlayerTurn(), phase); + if (game.getStack().isEmpty() && maySkipPriority) { + return null; + } + else { + autoPassCancel(); // probably cancel, since something has happened + } + + InputPassPriority defaultInput = new InputPassPriority(player); + defaultInput.showAndWait(); + return defaultInput.getChosenSa(); + } + + @Override + public void playChosenSpellAbility(SpellAbility chosenSa) + { + HumanPlay.playSpellAbility(player, chosenSa); } @Override - public List chooseCardsToDiscardToMaximumHandSize(int numDiscard) { - // TODO Auto-generated method stub - return null; + public List chooseCardsToDiscardToMaximumHandSize(int nDiscard) { + final int n = player.getZone(ZoneType.Hand).size(); + final int max = player.getMaxHandSize(); + + InputSelectCardsFromList inp = new InputSelectCardsFromList(nDiscard, nDiscard, player.getZone(ZoneType.Hand).getCards()); + String msgFmt = "Cleanup Phase: You can only have a maximum of %d cards, you currently have %d cards in your hand - select %d card(s) to discard"; + String message = String.format(msgFmt, max, n, nDiscard); + inp.setMessage(message); + inp.setCancelAllowed(false); + inp.showAndWait(); + return Lists.newArrayList(inp.getSelected()); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseCardsToRevealFromHand(int, int, java.util.List) + */ + @Override + public List chooseCardsToRevealFromHand(int min, int max, List valid) { + max = Math.min(max, valid.size()); + min = Math.min(min, max); + InputSelectCardsFromList inp = new InputSelectCardsFromList(min, max, valid); + inp.setMessage("Choose Which Cards to Reveal"); + inp.showAndWait(); + return Lists.newArrayList(inp.getSelected()); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#payManaOptional(forge.Card, forge.card.cost.Cost) + */ + @Override + public boolean payManaOptional(Card c, Cost cost, SpellAbility sa, String prompt, ManaPaymentPurpose purpose) { + if ( sa == null && cost.isOnlyManaCost() && cost.getTotalMana().isZero() + && !FModel.getPreferences().getPrefBoolean(FPref.MATCHPREF_PROMPT_FREE_BLOCKS)) + return true; + + return HumanPlay.payCostDuringAbilityResolve(player, c, cost, sa, prompt); + } + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseSaToActivateFromOpeningHand(java.util.List) + */ + @Override + public List chooseSaToActivateFromOpeningHand(List usableFromOpeningHand) { + List srcCards = new ArrayList(); + for (SpellAbility sa : usableFromOpeningHand) { + srcCards.add(sa.getHostCard()); + } + List result = new ArrayList(); + if (srcCards.isEmpty()) { + return result; + } + List chosen = GuiChoose.many("Choose cards to activate from opening hand and their order", "Activate first", -1, srcCards, null); + for (Card c : chosen) { + for (SpellAbility sa : usableFromOpeningHand) { + if (sa.getHostCard() == c) { + result.add(sa); + break; + } + } + } + return result; + } + + // end of not related candidates for move. + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseBinary(java.lang.String, boolean) + */ + @Override + public boolean chooseBinary(SpellAbility sa, String question, BinaryChoiceType kindOfChoice, Boolean defaultVal) { + String[] labels = new String[]{"Option1", "Option2"}; + switch(kindOfChoice) { + case HeadsOrTails: labels = new String[]{"Heads", "Tails"}; break; + case TapOrUntap: labels = new String[]{"Tap", "Untap"}; break; + case OddsOrEvens: labels = new String[]{"Odds", "Evens"}; break; + case UntapOrLeaveTapped: labels = new String[]{"Untap", "Leave tapped"}; break; + case UntapTimeVault: labels = new String[]{"Untap (and skip this turn)", "Leave tapped"}; break; + case PlayOrDraw: labels = new String[]{"Play", "Draw"}; break; + } + return GuiDialog.confirm(sa.getHostCard(), question, defaultVal == null || defaultVal.booleanValue(), labels); } @Override - public boolean payManaOptional(Card card, Cost cost, SpellAbility sa, - String prompt, ManaPaymentPurpose purpose) { - // TODO Auto-generated method stub - return false; + public boolean chooseFlipResult(SpellAbility sa, Player flipper, boolean[] results, boolean call) { + String[] labelsSrc = call ? new String[]{"heads", "tails"} : new String[]{"win the flip", "lose the flip"}; + String[] strResults = new String[results.length]; + for (int i = 0; i < results.length; i++) { + strResults[i] = labelsSrc[results[i] ? 0 : 1]; + } + return GuiChoose.one(sa.getHostCard().getName() + " - Choose a result", strResults) == labelsSrc[0]; } @Override - public int chooseNumber(SpellAbility sa, String title, int min, int max) { - // TODO Auto-generated method stub - return 0; + public Card chooseProtectionShield(GameEntity entityBeingDamaged, List options, Map choiceMap) { + String title = entityBeingDamaged + " - select which prevention shield to use"; + return choiceMap.get(GuiChoose.one(title, options)); } @Override - public int chooseNumber(SpellAbility sa, String title, - List values, Player relatedPlayer) { - // TODO Auto-generated method stub - return 0; + public Pair chooseAndRemoveOrPutCounter(Card cardWithCounter) { + if (!cardWithCounter.hasCounters()) { + System.out.println("chooseCounterType was reached with a card with no counters on it. Consider filtering this card out earlier"); + return null; + } + + String counterChoiceTitle = "Choose a counter type on " + cardWithCounter; + final CounterType chosen = GuiChoose.one(counterChoiceTitle, cardWithCounter.getCounters().keySet()); + + String putOrRemoveTitle = "What to do with that '" + chosen.getName() + "' counter "; + final String putString = "Put another " + chosen.getName() + " counter on " + cardWithCounter; + final String removeString = "Remove a " + chosen.getName() + " counter from " + cardWithCounter; + final String addOrRemove = GuiChoose.one(putOrRemoveTitle, new String[]{putString,removeString}); + + return new ImmutablePair(chosen,addOrRemove); } @Override - public boolean chooseBinary(SpellAbility sa, String question, - BinaryChoiceType kindOfChoice, Boolean defaultChioce) { - // TODO Auto-generated method stub - return false; + public Pair chooseTarget(SpellAbility saSpellskite, List> allTargets) { + if (allTargets.size() < 2) { + return Iterables.getFirst(allTargets, null); + } + + final Function, String> fnToString = new Function, String>() { + @Override + public String apply(Pair targ) { + return targ.getRight().toString() + " - " + targ.getLeft().getStackDescription(); + } + }; + + List> chosen = GuiChoose.getChoices(saSpellskite.getHostCard().getName(), 1, 1, allTargets, null, fnToString); + return Iterables.getFirst(chosen, null); } @Override - public boolean chooseFlipResult(SpellAbility sa, Player flipper, - boolean[] results, boolean call) { - // TODO Auto-generated method stub - return false; + public void notifyOfValue(SpellAbility sa, GameObject realtedTarget, String value) { + String message = formatNotificationMessage(sa, realtedTarget, value); + if (sa.isManaAbility()) { + game.getGameLog().add(GameLogEntryType.LAND, message); + } else { + GuiDialog.message(message, sa.getHostCard() == null ? "" : sa.getHostCard().getName()); + } + } + + private String formatMessage(String message, Object related) { + if(related instanceof Player && message.indexOf("{player") >= 0) + message = message.replace("{player}", mayBeYou(related)).replace("{player's}", Lang.getPossesive(mayBeYou(related))); + + return message; + } + + // These are not much related to PlayerController + private String formatNotificationMessage(SpellAbility sa, GameObject target, String value) { + if (sa.getApi() == null || sa.getHostCard() == null) { + return ("Result: " + value); + } + switch(sa.getApi()) { + case ChooseNumber: + final boolean random = sa.hasParam("Random"); + return String.format(random ? "Randomly chosen number for %s is %s" : "%s choses number: %s", mayBeYou(target), value); + case FlipACoin: + String flipper = StringUtils.capitalize(mayBeYou(target)); + return sa.hasParam("NoCall") + ? String.format("%s flip comes up %s", Lang.getPossesive(flipper), value) + : String.format("%s %s the flip", flipper, Lang.joinVerb(flipper, value)); + case Protection: + String choser = StringUtils.capitalize(mayBeYou(target)); + return String.format("%s %s protection from %s", choser, Lang.joinVerb(choser, "choose"), value); + default: + return String.format("%s effect's value for %s is %s", sa.getHostCard().getName(), mayBeYou(target), value); + } + } + + private String mayBeYou(Object what) { + return what == null ? "(null)" : what == player ? "you" : what.toString(); + } + + // end of not related candidates for move. + + /* (non-Javadoc) + * @see forge.game.player.PlayerController#chooseModeForAbility(forge.card.spellability.SpellAbility, java.util.List, int, int) + */ + @Override + public List chooseModeForAbility(SpellAbility sa, int min, int num) { + List choices = CharmEffect.makePossibleOptions(sa); + String modeTitle = String.format("%s activated %s - Choose a mode", sa.getActivatingPlayer(), sa.getHostCard()); + List chosen = new ArrayList(); + for (int i = 0; i < num; i++) { + AbilitySub a; + if (i < min) { + a = GuiChoose.one(modeTitle, choices); + } + else { + a = GuiChoose.oneOrNone(modeTitle, choices); + } + if (null == a) { + break; + } + + choices.remove(a); + chosen.add(a); + } + return chosen; } @Override - public Card chooseProtectionShield(GameEntity entityBeingDamaged, - List options, Map choiceMap) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List chooseModeForAbility(SpellAbility sa, int min, - int num) { - // TODO Auto-generated method stub - return null; + public List chooseColors(String message, SpellAbility sa, int min, int max, List options) { + return GuiChoose.getChoices(message, min, max, options); } @Override public byte chooseColor(String message, SpellAbility sa, ColorSet colors) { - // TODO Auto-generated method stub - return 0; + int cntColors = colors.countColors(); + switch (cntColors) { + case 0: return 0; + case 1: return colors.getColor(); + default: return chooseColorCommon(message, sa == null ? null : sa.getHostCard(), colors, false); + } + } + + @Override + public byte chooseColorAllowColorless(String message, Card c, ColorSet colors) { + int cntColors = 1 + colors.countColors(); + switch (cntColors) { + case 1: return 0; + default: return chooseColorCommon(message, c, colors, true); + } + } + + private byte chooseColorCommon(String message, Card c, ColorSet colors, boolean withColorless) { + int cntColors = colors.countColors(); + if( withColorless ) cntColors++; + String[] colorNames = new String[cntColors]; + int i = 0; + if(withColorless) + colorNames[i++] = MagicColor.toLongString((byte)0); + for (byte b : colors) { + colorNames[i++] = MagicColor.toLongString(b); + } + if (colorNames.length > 2) { + return MagicColor.fromName(GuiChoose.one(message, colorNames)); + } + int idxChosen = GuiDialog.confirm(c, message, colorNames) ? 0 : 1; + return MagicColor.fromName(colorNames[idxChosen]); } @Override - public byte chooseColorAllowColorless(String message, Card c, - ColorSet colors) { - // TODO Auto-generated method stub - return 0; + public PaperCard chooseSinglePaperCard(SpellAbility sa, String message, Predicate cpp, String name) { + Iterable cardsFromDb = FModel.getMagicDb().getCommonCards().getUniqueCards(); + List cards = Lists.newArrayList(Iterables.filter(cardsFromDb, cpp)); + Collections.sort(cards); + return GuiChoose.one(message, cards); } @Override - public PaperCard chooseSinglePaperCard(SpellAbility sa, String message, - Predicate cpp, String name) { - // TODO Auto-generated method stub - return null; + public CounterType chooseCounterType(Collection options, SpellAbility sa, String prompt) { + if (options.size() <= 1) { + return Iterables.getFirst(options, null); + } + return GuiChoose.one(prompt, options); } @Override - public List chooseColors(String message, SpellAbility sa, int min, - int max, List options) { - // TODO Auto-generated method stub - return null; + public boolean confirmPayment(CostPart costPart, String question) { + InputConfirm inp = new InputConfirm(question); + inp.showAndWait(); + return inp.getResult(); } @Override - public CounterType chooseCounterType(Collection options, - SpellAbility sa, String prompt) { - // TODO Auto-generated method stub - return null; + public ReplacementEffect chooseSingleReplacementEffect(String prompt, List possibleReplacers, HashMap runParams) { + if(possibleReplacers.size() == 1) + return possibleReplacers.get(0); + return GuiChoose.one(prompt, possibleReplacers); } @Override - public boolean confirmPayment(CostPart costPart, String string) { - // TODO Auto-generated method stub - return false; + public String chooseProtectionType(String string, SpellAbility sa, List choices) { + return GuiChoose.one(string, choices); } @Override - public ReplacementEffect chooseSingleReplacementEffect(String prompt, - List possibleReplacers, - HashMap runParams) { - // TODO Auto-generated method stub - return null; - } - - @Override - public String chooseProtectionType(String string, SpellAbility sa, - List choices) { - // TODO Auto-generated method stub - return null; - } - - @Override - public CardShields chooseRegenerationShield(Card c) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, - boolean alreadyPaid, List allPayers) { - // TODO Auto-generated method stub - return false; + public boolean payCostToPreventEffect(Cost cost, SpellAbility sa, boolean alreadyPaid, List allPayers) { + // if it's paid by the AI already the human can pay, but it won't change anything + return HumanPlay.payCostDuringAbilityResolve(player, sa.getHostCard(), cost, sa, null); } @Override public void orderAndPlaySimultaneousSa(List activePlayerSAs) { - // TODO Auto-generated method stub - + List orderedSAs = activePlayerSAs; + if (activePlayerSAs.size() > 1) { // give a dual list form to create instead of needing to do it one at a time + orderedSAs = GuiChoose.order("Select order for Simultaneous Spell Abilities", "Resolve first", activePlayerSAs, null); + } + int size = orderedSAs.size(); + for (int i = size - 1; i >= 0; i--) { + SpellAbility next = orderedSAs.get(i); + if (next.isTrigger()) { + HumanPlay.playSpellAbility(player, next); + } else { + player.getGame().getStack().add(next); + } + } } @Override - public void playTrigger(Card host, WrappedAbility wrapperAbility, - boolean isMandatory) { - // TODO Auto-generated method stub - + public void playTrigger(Card host, WrappedAbility wrapperAbility, boolean isMandatory) { + HumanPlay.playSpellAbilityNoStack(player, wrapperAbility); } @Override public boolean playSaFromPlayEffect(SpellAbility tgtSA) { - // TODO Auto-generated method stub - return false; + HumanPlay.playSpellAbility(player, tgtSA); + return true; } @Override public Map chooseProliferation() { - // TODO Auto-generated method stub - return null; + InputProliferate inp = new InputProliferate(); + inp.setCancelAllowed(true); + inp.showAndWait(); + if (inp.hasCancelled()) { + return null; + } + return inp.getProliferationMap(); } @Override - public boolean chooseCardsPile(SpellAbility sa, List pile1, - List pile2, boolean faceUp) { - // TODO Auto-generated method stub - return false; + public boolean chooseTargetsFor(SpellAbility currentAbility) { + final TargetSelection select = new TargetSelection(currentAbility); + return select.chooseTargets(null); } @Override - public void revealAnte(String message, - Multimap removedAnteCards) { - // TODO Auto-generated method stub - + public boolean chooseCardsPile(SpellAbility sa, List pile1, List pile2, boolean faceUp) { + if (!faceUp) { + final String p1Str = String.format("Pile 1 (%s cards)", pile1.size()); + final String p2Str = String.format("Pile 2 (%s cards)", pile2.size()); + final String[] possibleValues = { p1Str , p2Str }; + return GuiDialog.confirm(sa.getHostCard(), "Choose a Pile", possibleValues); + } else { + final Card[] disp = new Card[pile1.size() + pile2.size() + 2]; + disp[0] = new Card(-1); + disp[0].setName("Pile 1"); + for (int i = 0; i < pile1.size(); i++) { + disp[1 + i] = pile1.get(i); + } + disp[pile1.size() + 1] = new Card(-2); + disp[pile1.size() + 1].setName("Pile 2"); + for (int i = 0; i < pile2.size(); i++) { + disp[pile1.size() + i + 2] = pile2.get(i); + } + + // make sure Pile 1 or Pile 2 is clicked on + while (true) { + final Object o = GuiChoose.one("Choose a pile", disp); + final Card c = (Card) o; + String name = c.getName(); + + if (!(name.equals("Pile 1") || name.equals("Pile 2"))) { + continue; + } + + return name.equals("Pile 1"); + } + } } @Override - public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, - SpellAbility sa, String prompt, boolean isActivatedAbility) { - // TODO Auto-generated method stub - return false; + public void revealAnte(String message, Multimap removedAnteCards) { + for (Player p : removedAnteCards.keySet()) { + GuiChoose.reveal(message + " from " + Lang.getPossessedObject(mayBeYou(p), "deck"), removedAnteCards.get(p)); + } + } + + @Override + public CardShields chooseRegenerationShield(Card c) { + if (c.getShield().size() < 2) { + return Iterables.getFirst(c.getShield(), null); + } + return GuiChoose.one("Choose a regeneration shield:", c.getShield()); + } + + @Override + public List chooseCardsYouWonToAddToDeck(List losses) { + return GuiChoose.many("Select cards to add to your deck", "Add these to my deck", 0, losses.size(), losses, null); } @Override - public Map chooseCardsForConvoke(SpellAbility sa, - ManaCost manaCost, List untappedCreats) { - // TODO Auto-generated method stub - return null; + public boolean payManaCost(ManaCost toPay, CostPartMana costPartMana, SpellAbility sa, String prompt, boolean isActivatedSa) { + return HumanPlay.payManaCost(toPay, costPartMana, sa, player, prompt, isActivatedSa); } @Override - public String chooseCardName(SpellAbility sa, Predicate cpp, - String valid, String message) { - // TODO Auto-generated method stub - return null; + public Map chooseCardsForConvoke(SpellAbility sa, ManaCost manaCost, List untappedCreats) { + InputSelectCardsForConvoke inp = new InputSelectCardsForConvoke(player, manaCost, untappedCreats); + inp.showAndWait(); + return inp.getConvokeMap(); } @Override - public Card chooseSingleCardForZoneChange(ZoneType destination, - List origin, SpellAbility sa, List fetchList, - String selectPrompt, boolean b, Player decider) { - // TODO Auto-generated method stub - return null; + public String chooseCardName(SpellAbility sa, Predicate cpp, String valid, String message) { + PaperCard cp = null; + while(true) { + cp = chooseSinglePaperCard(sa, message, cpp, sa.getHostCard().getName()); + Card instanceForPlayer = Card.fromPaperCard(cp, player); // the Card instance for test needs a game to be tested + if (instanceForPlayer.isValid(valid, sa.getHostCard().getController(), sa.getHostCard())) + return cp.getName(); + } + } + + @Override + public Card chooseSingleCardForZoneChange(ZoneType destination, List origin, SpellAbility sa, List fetchList, String selectPrompt, boolean b, Player decider) { + return chooseSingleEntityForEffect(fetchList, sa, selectPrompt, b, decider); } } diff --git a/forge-m-base/src/forge/player/TargetSelection.java b/forge-m-base/src/forge/player/TargetSelection.java new file mode 100644 index 00000000000..d7f340197c5 --- /dev/null +++ b/forge-m-base/src/forge/player/TargetSelection.java @@ -0,0 +1,387 @@ +/* + * 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.player; + +import com.google.common.base.Predicate; +import com.google.common.collect.Lists; + +import forge.game.Game; +import forge.game.GameObject; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.spellability.TargetRestrictions; +import forge.game.zone.Zone; +import forge.game.zone.ZoneType; +import forge.screens.match.input.InputSelectTargets; +import forge.toolbox.GuiChoose; +import forge.util.Aggregates; + +import java.util.ArrayList; +import java.util.List; + +/** + *

+ * Target_Selection class. + *

+ * + * @author Forge + * @version $Id: TargetSelection.java 25112 2014-03-09 10:18:52Z swordshine $ + */ +public class TargetSelection { + private final SpellAbility ability; + + + public TargetSelection(final SpellAbility sa) { + this.ability = sa; + } + + private final TargetRestrictions getTgt() { + return this.ability.getTargetRestrictions(); + } + + private boolean bTargetingDone = false; + + + /** + *

+ * resetTargets. + *

+ */ + + public final boolean chooseTargets(Integer numTargets) { + TargetRestrictions tgt = getTgt(); + final boolean canTarget = tgt != null && tgt.doesTarget(); + if (!canTarget) { + throw new RuntimeException("TargetSelection.chooseTargets called for ability that does not target - " + ability); + } + + // Number of targets is explicitly set only if spell is being redirected (ex. Swerve or Redirect) + final int minTargets = numTargets != null ? numTargets.intValue() : tgt.getMinTargets(ability.getHostCard(), ability); + final int maxTargets = numTargets != null ? numTargets.intValue() : tgt.getMaxTargets(ability.getHostCard(), ability); + final int numTargeted = ability.getTargets().getNumTargeted(); + + boolean hasEnoughTargets = minTargets == 0 || numTargeted >= minTargets; + boolean hasAllTargets = numTargeted == maxTargets && maxTargets > 0; + if (maxTargets == 0) return true; + // if not enough targets chosen, cancel Ability + if (this.bTargetingDone && !hasEnoughTargets) { + return false; + } + + if (this.bTargetingDone && hasEnoughTargets || hasAllTargets || tgt.isDividedAsYouChoose() && tgt.getStillToDivide() == 0) { + return true; + } + + if (!tgt.hasCandidates(this.ability, true) && !hasEnoughTargets) { + // Cancel ability if there aren't any valid Candidates + return false; + } + + final List zone = tgt.getZone(); + final boolean mandatory = tgt.getMandatory() && tgt.hasCandidates(this.ability, true); + + final boolean choiceResult; + final boolean random = tgt.isRandomTarget(); + if (random) { + List candidates = tgt.getAllCandidates(this.ability, true); + GameObject choice = Aggregates.random(candidates); + return ability.getTargets().add(choice); + } + else if (zone.size() == 1 && zone.get(0) == ZoneType.Stack) { + // If Zone is Stack, the choices are handled slightly differently. + // Handle everything inside function due to interaction with StackInstance + return this.chooseCardFromStack(mandatory); + } + else { + List validTargets = this.getValidCardsToTarget(); + if (zone.size() == 1 && (zone.get(0) == ZoneType.Battlefield || zone.get(0) == ZoneType.Hand)) { + InputSelectTargets inp = new InputSelectTargets(validTargets, ability, mandatory); + inp.showAndWait(); + choiceResult = !inp.hasCancelled(); + bTargetingDone = inp.hasPressedOk(); + } else { + // for every other case an all-purpose GuiChoose + choiceResult = this.chooseCardFromList(validTargets, true, mandatory); + } + } + // some inputs choose cards one-by-one and need to be called again + return choiceResult && chooseTargets(numTargets); + } + + + + // these have been copied over from CardFactoryUtil as they need two extra + // parameters for target selection. + // however, due to the changes necessary for SA_Requirements this is much + // different than the original + + /** + *

+ * chooseValidInput. + *

+ * @return + */ + private final List getValidCardsToTarget() { + final TargetRestrictions tgt = this.getTgt(); + final Game game = ability.getActivatingPlayer().getGame(); + final List zone = tgt.getZone(); + + final boolean canTgtStack = zone.contains(ZoneType.Stack); + List validCards = CardLists.getValidCards(game.getCardsIn(zone), tgt.getValidTgts(), this.ability.getActivatingPlayer(), this.ability.getHostCard()); + List choices = CardLists.getTargetableCards(validCards, this.ability); + if (canTgtStack) { + // Since getTargetableCards doesn't have additional checks if one of the Zones is stack + // Remove the activating card from targeting itself if its on the Stack + Card activatingCard = ability.getHostCard(); + if (activatingCard.isInZone(ZoneType.Stack)) { + choices.remove(ability.getHostCard()); + } + } + List targetedObjects = this.ability.getUniqueTargets(); + + if (tgt.isUniqueTargets()) { + for (final Object o : targetedObjects) { + if ((o instanceof Card) && targetedObjects.contains(o)) { + choices.remove(o); + } + } + } + + // Remove cards already targeted + final List targeted = Lists.newArrayList(ability.getTargets().getTargetCards()); + for (final Card c : targeted) { + if (choices.contains(c)) { + choices.remove(c); + } + } + + // If all cards (including subability targets) must have the same controller + if (tgt.isSameController() && !targetedObjects.isEmpty()) { + final List list = new ArrayList(); + for (final Object o : targetedObjects) { + if (o instanceof Card) { + list.add((Card) o); + } + } + if (!list.isEmpty()) { + final Card card = list.get(0); + choices = CardLists.filter(choices, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.sharesControllerWith(card); + } + }); + } + } + // If second target has properties related to the first + if (tgt.getRelatedProperty() != null && !targetedObjects.isEmpty()) { + final List list = new ArrayList(); + final String related = tgt.getRelatedProperty(); + for (final Object o : targetedObjects) { + if (o instanceof Card) { + list.add((Card) o); + } + } + if (!list.isEmpty()) { + final Card card = list.get(0); + if ("LEPower".equals(related)) { + choices = CardLists.filter(choices, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.getNetAttack() <= card.getNetAttack(); + } + }); + } + if ("LECMC".equals(related)) { + choices = CardLists.filter(choices, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.getCMC() <= card.getCMC(); + } + }); + } + } + } + // If all cards must be from the same zone + if (tgt.isSingleZone() && !targeted.isEmpty()) { + choices = CardLists.filterControlledBy(choices, targeted.get(0).getController()); + } + // If all cards must be from different zones + if (tgt.isDifferentZone() && !targeted.isEmpty()) { + choices = CardLists.filterControlledBy(choices, targeted.get(0).getController().getOpponent()); + } + // If all cards must have different controllers + if (tgt.isDifferentControllers() && !targeted.isEmpty()) { + final List availableControllers = new ArrayList(game.getPlayers()); + for (int i = 0; i < targeted.size(); i++) { + availableControllers.remove(targeted.get(i).getController()); + } + choices = CardLists.filterControlledBy(choices, availableControllers); + } + // If the cards can't share a creature type + if (tgt.isWithoutSameCreatureType() && !targeted.isEmpty()) { + final Card card = targeted.get(0); + choices = CardLists.filter(choices, new Predicate() { + @Override + public boolean apply(final Card c) { + return !c.sharesCreatureTypeWith(card); + } + }); + } + return choices; + } + + /** + *

+ * chooseCardFromList. + *

+ * + * @param choices + * a {@link forge.CardList} object. + * @param targeted + * a boolean. + * @param mandatory + * a boolean. + */ + private final boolean chooseCardFromList(final List choices, final boolean targeted, final boolean mandatory) { + // Send in a list of valid cards, and popup a choice box to target + final Game game = ability.getActivatingPlayer().getGame(); + + final List crdsBattle = new ArrayList(); + final List crdsExile = new ArrayList(); + final List crdsGrave = new ArrayList(); + final List crdsLibrary = new ArrayList(); + final List crdsStack = new ArrayList(); + final List crdsAnte = new ArrayList(); + for (final Card inZone : choices) { + Zone zz = game.getZoneOf(inZone); + if (zz.is(ZoneType.Battlefield)) crdsBattle.add(inZone); + else if (zz.is(ZoneType.Exile)) crdsExile.add(inZone); + else if (zz.is(ZoneType.Graveyard)) crdsGrave.add(inZone); + else if (zz.is(ZoneType.Library)) crdsLibrary.add(inZone); + else if (zz.is(ZoneType.Stack)) crdsStack.add(inZone); + else if (zz.is(ZoneType.Ante)) crdsAnte.add(inZone); + } + List choicesFiltered = new ArrayList(); + if (!crdsBattle.isEmpty()) { + choicesFiltered.add("--CARDS ON BATTLEFIELD:--"); + choicesFiltered.addAll(crdsBattle); + } + if (!crdsExile.isEmpty()) { + choicesFiltered.add("--CARDS IN EXILE:--"); + choicesFiltered.addAll(crdsExile); + } + if (!crdsGrave.isEmpty()) { + choicesFiltered.add("--CARDS IN GRAVEYARD:--"); + choicesFiltered.addAll(crdsGrave); + } + if (!crdsLibrary.isEmpty()) { + choicesFiltered.add("--CARDS IN LIBRARY:--"); + choicesFiltered.addAll(crdsLibrary); + } + if (!crdsStack.isEmpty()) { + choicesFiltered.add("--CARDS IN STACK:--"); + choicesFiltered.addAll(crdsStack); + } + if (!crdsAnte.isEmpty()) { + choicesFiltered.add("--CARDS IN ANTE:--"); + choicesFiltered.addAll(crdsAnte); + } + + final String msgDone = "[FINISH TARGETING]"; + if (this.getTgt().isMinTargetsChosen(this.ability.getHostCard(), this.ability)) { + // is there a more elegant way of doing this? + choicesFiltered.add(msgDone); + } + + Object chosen = null; + if (!choices.isEmpty() && mandatory) { + chosen = GuiChoose.one(getTgt().getVTSelection(), choicesFiltered); + } + else { + chosen = GuiChoose.oneOrNone(getTgt().getVTSelection(), choicesFiltered); + } + if (chosen == null) { + return false; + } + if (msgDone.equals(chosen)) { + bTargetingDone = true; + return true; + } + + if (chosen instanceof Card) + ability.getTargets().add((Card)chosen); + return true; + } + + /** + *

+ * chooseCardFromStack. + *

+ * + * @param mandatory + * a boolean. + */ + private final boolean chooseCardFromStack(final boolean mandatory) { + final TargetRestrictions tgt = this.getTgt(); + final String message = tgt.getVTSelection(); + // Find what's targetable, then allow human to choose + final List selectOptions = new ArrayList(); + + final Game game = ability.getActivatingPlayer().getGame(); + for (SpellAbilityStackInstance si : game.getStack()) { + SpellAbility abilityOnStack = si.getSpellAbility(); + if (ability.equals(abilityOnStack)) { + // By peeking at stack item, target is set to its SI state. So set it back before adding targets + ability.resetTargets(); + } + else if (ability.canTargetSpellAbility(abilityOnStack)) { + selectOptions.add(abilityOnStack); + } + } + + while(!bTargetingDone) { + if (tgt.isMaxTargetsChosen(this.ability.getHostCard(), this.ability)) { + bTargetingDone = true; + return true; + } + + if (!selectOptions.contains("[FINISH TARGETING]") && tgt.isMinTargetsChosen(this.ability.getHostCard(), this.ability)) { + selectOptions.add("[FINISH TARGETING]"); + } + + if (selectOptions.isEmpty()) { + // Not enough targets, cancel targeting + return false; + } else { + final Object madeChoice = GuiChoose.oneOrNone(message, selectOptions); + if (madeChoice == null) { + return false; + } + if (madeChoice instanceof SpellAbility) { + ability.getTargets().add((SpellAbility)madeChoice); + } else // 'FINISH TARGETING' chosen + bTargetingDone = true; + } + } + return true; + } +} diff --git a/forge-m-base/src/forge/screens/match/FControl.java b/forge-m-base/src/forge/screens/match/FControl.java index 3fae566b5a3..c636c2fd938 100644 --- a/forge-m-base/src/forge/screens/match/FControl.java +++ b/forge-m-base/src/forge/screens/match/FControl.java @@ -1,8 +1,10 @@ package forge.screens.match; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.lang3.tuple.Pair; @@ -13,6 +15,7 @@ import com.google.common.eventbus.Subscribe; import forge.FThreads; import forge.Forge; import forge.game.Game; +import forge.game.GameEntity; import forge.game.Match; import forge.game.card.Card; import forge.game.combat.Combat; @@ -28,8 +31,10 @@ import forge.screens.match.events.UiEvent; import forge.screens.match.events.UiEventAttackerDeclared; import forge.screens.match.events.UiEventBlockerAssigned; import forge.screens.match.input.InputQueue; +import forge.screens.match.views.VAssignDamage; import forge.screens.match.views.VPhaseIndicator.PhaseLabel; import forge.screens.match.views.VPlayerPanel; +import forge.toolbox.FCardPanel; import forge.utils.ForgePreferences.FPref; public class FControl { @@ -92,12 +97,12 @@ public class FControl { // It's important to run match in a different thread to allow GUI inputs to be invoked from inside game. // Game is set on pause while gui player takes decisions - /*game.getAction().invoke(new Runnable() { + game.getAction().invoke(new Runnable() { @Override public void run() { match.startGame(game); } - });*/ + }); } public static Game getGame() { @@ -187,6 +192,25 @@ public class FControl { return view.getPlayerPanels().get(p); } + public static void highlightCard(final Card c) { + for (VPlayerPanel playerPanel : FControl.getView().getPlayerPanels().values()) { + for (FCardPanel p : playerPanel.getField().getCardPanels()) { + if (p.getCard().equals(c)) { + p.setHighlighted(true); + return; + } + } + } + } + + public static void clearCardHighlights() { + for (VPlayerPanel playerPanel : FControl.getView().getPlayerPanels().values()) { + for (FCardPanel p : playerPanel.getField().getCardPanels()) { + p.setHighlighted(false); + } + } + } + public static boolean mayShowCard(Card c) { return true;// game == null || !gameHasHumanPlayer || c.canBeShownTo(getCurrentPlayer()); } @@ -212,7 +236,31 @@ public class FControl { } CCombat.SINGLETON_INSTANCE.setModel(combat); CCombat.SINGLETON_INSTANCE.update();*/ - } // showBlockers() + } + + @SuppressWarnings("unchecked") + public static Map getDamageToAssign(final Card attacker, final List blockers, final int damage, final GameEntity defender, final boolean overrideOrder) { + if (damage <= 0) { + return new HashMap(); + } + + // If the first blocker can absorb all of the damage, don't show the Assign Damage Frame + Card firstBlocker = blockers.get(0); + if (!overrideOrder && !attacker.hasKeyword("Deathtouch") && firstBlocker.getLethalDamage() >= damage) { + Map res = new HashMap(); + res.put(firstBlocker, damage); + return res; + } + + final Object[] result = { null }; // how else can I extract a value from EDT thread? + FThreads.invokeInEdtAndWait(new Runnable() { + @Override + public void run() { + VAssignDamage v = new VAssignDamage(attacker, blockers, damage, defender, overrideOrder); + result[0] = v.getDamageMap(); + }}); + return (Map)result[0]; + } private static Set highlightedPlayers = new HashSet(); public static void setHighlighted(Player ge, boolean b) { diff --git a/forge-m-base/src/forge/screens/match/views/VAssignDamage.java b/forge-m-base/src/forge/screens/match/views/VAssignDamage.java new file mode 100644 index 00000000000..5f1832111f9 --- /dev/null +++ b/forge-m-base/src/forge/screens/match/views/VAssignDamage.java @@ -0,0 +1,438 @@ +/* + * 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.screens.match.views; + +import forge.game.GameEntity; +import forge.game.card.Card; +import forge.game.card.CounterType; +import forge.game.player.Player; +import forge.toolbox.FButton; +import forge.toolbox.FCardPanel; +import forge.toolbox.FLabel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Assembles Swing components of assign damage dialog. + * + * This needs a JDialog to maintain a modal state. + * Without the modal state, the PhaseHandler automatically + * moves forward to phase Main2 without assigning damage. + * + *

(V at beginning of class name denotes a view class.) + */ +public class VAssignDamage { + // Width and height of blocker dialog + private final int wDlg = 700; + private final int hDlg = 500; + //private final FDialog dlg = new FDialog(); + + // Damage storage + private final int totalDamageToAssign; + + private boolean attackerHasDeathtouch = false; + private boolean attackerHasTrample = false; + private boolean attackerHasInfect = false; + private boolean overrideCombatantOrder = false; + + private final GameEntity defender; + + private final FLabel lblTotalDamage = new FLabel.Builder().text("Available damage points: Unknown").build(); + private final FLabel lblAssignRemaining = new FLabel.Builder().text("Distribute the remaining damage points among lethally wounded entities").build(); + // Label Buttons + private final FButton btnOK = new FButton("OK"); + private final FButton btnReset = new FButton("Reset"); + private final FButton btnAuto = new FButton("Auto"); + + + private static class DamageTarget { + public final Card card; + public final FLabel label; + public int damage; + + public DamageTarget(Card entity0, FLabel lbl) { + card = entity0; + label = lbl; + } + } + + // Indexes of defenders correspond to their indexes in the damage list and labels. + private final List defenders = new ArrayList(); // NULL in this map means defender + private final Map damage = new HashMap(); // NULL in this map means defender + + private boolean canAssignTo(Card card) { + for(DamageTarget dt : defenders) { + if (dt.card == card) return true; + if (getDamageToKill(dt.card) > dt.damage) + return false; + } + throw new RuntimeException("Asking to assign damage to object which is not present in defenders list"); + } + + // Mouse actions + /*private final MouseAdapter mad = new MouseAdapter() { + @Override + public void pressed(float x, float y) { + Card source = ((FCardPanel) evt.getSource()).getCard(); + if (!damage.containsKey(source)) source = null; // to get player instead of fake card + + FSkin.Colors brdrColor = VAssignDamage.this.canAssignTo(source) ? FSkin.Colors.CLR_ACTIVE : FSkin.Colors.CLR_INACTIVE; + ((FCardPanel) evt.getSource()).setBorder(new FSkin.LineSkinBorder(FSkin.getColor(brdrColor), 2)); + } + + @Override + public void released(float x, float y) { + //((FCardPanel) evt.getSource()).setBorder((Border)null); + } + + @Override + public void tap(float x, float y, int count) { + Card source = ((FCardPanel) evt.getSource()).getCard(); // will be NULL for player + + boolean meta = evt.isControlDown(); + boolean isLMB = SwingUtilities.isLeftMouseButton(evt); + boolean isRMB = SwingUtilities.isRightMouseButton(evt); + + if (isLMB || isRMB) + assignDamageTo(source, meta, isLMB); + } + };*/ + + /** Constructor. + * + * @param attacker0 {@link forge.game.card.Card} + * @param defenderCards List<{@link forge.game.card.Card}> + * @param damage0 int + * @param defender GameEntity that's bein attacked + * @param overrideOrder override combatant order + + */ + public VAssignDamage(final Card attacker0, final List defenderCards, final int damage0, final GameEntity defender, boolean overrideOrder) { + // Set damage storage vars + this.totalDamageToAssign = damage0; + this.defender = defender; + this.attackerHasDeathtouch = attacker0.hasKeyword("Deathtouch"); + this.attackerHasInfect = attacker0.hasKeyword("Infect"); + this.attackerHasTrample = defender != null && attacker0.hasKeyword("Trample"); + this.overrideCombatantOrder = overrideOrder; + + // Top-level UI stuff + /*final SkinnedPanel pnlMain = new SkinnedPanel(); + pnlMain.setBackground(FSkin.getColor(FSkin.Colors.CLR_THEME2)); + + // Attacker area + final FCardPanel pnlAttacker = new FCardPanel(attacker0); + pnlAttacker.setOpaque(false); + pnlAttacker.setCardBounds(0, 0, 105, 150); + + final JPanel pnlInfo = new JPanel(new MigLayout("insets 0, gap 0, wrap")); + pnlInfo.setOpaque(false); + pnlInfo.add(lblTotalDamage, "gap 0 0 20px 5px"); + pnlInfo.add(new FLabel.Builder().text("Left click: Assign 1 damage. (Left Click + Control): Assign remaining damage up to lethal").build(), "gap 0 0 0 5px"); + pnlInfo.add(new FLabel.Builder().text("Right click: Unassign 1 damage. (Right Click + Control): Unassign all damage.").build(), "gap 0 0 0 5px"); + + // Defenders area + final JPanel pnlDefenders = new JPanel(); + pnlDefenders.setOpaque(false); + int cols = attackerHasTrample ? defenderCards.size() + 1 : defenderCards.size(); + final String wrap = "wrap " + Integer.toString(cols); + pnlDefenders.setLayout(new MigLayout("insets 0, gap 0, ax center, " + wrap)); + + final FScrollPane scrDefenders = new FScrollPane(pnlDefenders, false); + + // Top row of cards... + for (final Card c : defenderCards) { + DamageTarget dt = new DamageTarget(c, new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build()); + this.damage.put(c, dt); + this.defenders.add(dt); + addPanelForDefender(pnlDefenders, c); + } + + if (attackerHasTrample) { + DamageTarget dt = new DamageTarget(null, new FLabel.Builder().text("0").fontSize(18).fontAlign(SwingConstants.CENTER).build()); + this.damage.put(null, dt); + this.defenders.add(dt); + Card fakeCard; + if (defender instanceof Card) + fakeCard = (Card)defender; + else if (defender instanceof Player) { + fakeCard = new Card(-1); + fakeCard.setName(this.defender.getName()); + fakeCard.setOwner((Player)defender); + Player p = (Player)defender; + fakeCard.setImageKey(p.getLobbyPlayer().getIconImageKey()); + } else { + fakeCard = new Card(-2); + fakeCard.setName(this.defender.getName()); + } + addPanelForDefender(pnlDefenders, fakeCard); + } + + // Add "opponent placeholder" card if trample allowed + // If trample allowed, make card placeholder + + // ... bottom row of labels. + for (DamageTarget l : defenders) { + pnlDefenders.add(l.label, "w 145px!, h 30px!, gap 5px 5px 0 5px"); + } + + btnOK.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { finish(); } }); + btnReset.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { resetAssignedDamage(); initialAssignDamage(false); } }); + btnAuto.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent arg0) { resetAssignedDamage(); initialAssignDamage(true); finish(); } }); + + // Final UI layout + pnlMain.setLayout(new MigLayout("insets 0, gap 0, wrap 2, ax center")); + pnlMain.add(pnlAttacker, "w 125px!, h 160px!, gap 50px 0 0 15px"); + pnlMain.add(pnlInfo, "gap 20px 0 0 15px"); + pnlMain.add(scrDefenders, "w 96%!, gap 2% 0 0 0, pushy, growy, ax center, span 2"); + pnlMain.add(lblAssignRemaining, "w 96%!, gap 2% 0 0 0, ax center, span 2"); + + JPanel pnlButtons = new JPanel(new MigLayout("insets 0, gap 0, ax center")); + pnlButtons.setOpaque(false); + pnlButtons.add(btnAuto, "w 110px!, h 30px!, gap 0 10px 0 0"); + pnlButtons.add(btnOK, "w 110px!, h 30px!, gap 0 10px 0 0"); + pnlButtons.add(btnReset, "w 110px!, h 30px!"); + + pnlMain.add(pnlButtons, "ax center, w 350px!, gap 10px 10px 10px 10px, span 2"); + overlay.add(pnlMain); + + pnlMain.getRootPane().setDefaultButton(btnOK); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + btnAuto.requestFocusInWindow(); + } + }); + + initialAssignDamage(false); + SOverlayUtils.showOverlay(); + + this.dlg.setUndecorated(true); + this.dlg.setContentPane(pnlMain); + this.dlg.setSize(new Dimension(wDlg, hDlg)); + this.dlg.setLocation((overlay.getWidth() - wDlg) / 2, (overlay.getHeight() - hDlg) / 2); + this.dlg.setModalityType(ModalityType.APPLICATION_MODAL); + this.dlg.setVisible(true);*/ + } + + /** + * TODO: Write javadoc for this method. + * @param pnlDefenders + * @param defender + */ + private void addPanelForDefender(final Card defender) { + final FCardPanel cp = new FCardPanel(defender); + cp.setBounds(0, 0, 145, 170); + } + + /** + * TODO: Write javadoc for this method. + * @param source + * @param meta + * @param isLMB + */ + private void assignDamageTo(Card source, boolean meta, boolean isAdding) { + if (!damage.containsKey(source)) + source = null; + + // If trying to assign to the defender, follow the normal assignment rules + // No need to check for "active" creature assignee when overiding combatant order + if ((source == null || source == this.defender || !this.overrideCombatantOrder) && isAdding && + !VAssignDamage.this.canAssignTo(source)) { + return; + } + + // If lethal damage has already been assigned just act like it's 0. + int lethalDamage = getDamageToKill(source); + int damageItHad = damage.get(source).damage; + int leftToKill = Math.max(0, lethalDamage - damageItHad); + + int damageToAdd = isAdding ? 1 : -1; + + int leftToAssign = getRemainingDamage(); + // Left click adds damage, right click substracts damage. + // Hold Ctrl to assign lethal damage, Ctrl-click again on a creature with lethal damage to assign all available damage to it + if (meta) { + if (isAdding) { + damageToAdd = leftToKill > 0 ? leftToKill : leftToAssign; + } + else { + damageToAdd = damageItHad > lethalDamage ? lethalDamage - damageItHad : -damageItHad; + } + } + + if (damageToAdd > leftToAssign) { + damageToAdd = leftToAssign; + } + + // cannot assign first blocker less than lethal damage except when overriding order + boolean isFirstBlocker = defenders.get(0).card == source; + if (!this.overrideCombatantOrder && isFirstBlocker && damageToAdd + damageItHad < lethalDamage) { + return; + } + + if (damageToAdd == 0 || damageToAdd + damageItHad < 0) { + return; + } + + addDamage(source, damageToAdd); + checkDamageQueue(); + updateLabels(); + } + + private void checkDamageQueue() { + // Clear out any Damage that shouldn't be assigned to other combatants + boolean hasAliveEnemy = false; + for (DamageTarget dt : defenders) { + int lethal = getDamageToKill(dt.card); + int damage = dt.damage; + // If overriding combatant order, make sure everything has lethal if defender has damage assigned to it + // Otherwise, follow normal combatant order + if (hasAliveEnemy && (!this.overrideCombatantOrder || dt.card == null || dt.card == this.defender)) { + dt.damage = 0; + } + else { + hasAliveEnemy |= damage < lethal; + } + } + } + + // will assign all damage to defenders and rest to player, if present + private void initialAssignDamage(boolean toAllBlockers) { + if (!toAllBlockers && this.overrideCombatantOrder) { + // Don't auto assign the first damage when overriding combatant order + updateLabels(); + return; + } + + int dmgLeft = totalDamageToAssign; + DamageTarget dtLast = null; + for(DamageTarget dt : defenders) { // MUST NOT RUN WITH EMPTY collection + int lethal = getDamageToKill(dt.card); + int damage = Math.min(lethal, dmgLeft); + addDamage(dt.card, damage); + dmgLeft -= damage; + dtLast = dt; + if (dmgLeft <= 0 || !toAllBlockers) { break; } + } + if (dmgLeft < 0) { + throw new RuntimeException("initialAssignDamage managed to assign more damage than it could"); + } + if (toAllBlockers && dmgLeft > 0) { + // flush the remaining damage into last defender if assigning all damage + addDamage(dtLast.card, dmgLeft); + } + updateLabels(); + } + + /** Reset Assign Damage back to how it was at the beginning. */ + private void resetAssignedDamage() { + for(DamageTarget dt : defenders) + dt.damage = 0; + } + + private void addDamage(final Card card, int addedDamage) { + // If we don't have enough left or we're trying to unassign too much return + int canAssign = getRemainingDamage(); + if (canAssign < addedDamage) { + addedDamage = canAssign; + } + + DamageTarget dt = damage.get(card); + dt.damage = Math.max(0, addedDamage + dt.damage); + } + + private int getRemainingDamage() { + int spent = 0; + for (DamageTarget dt : defenders) { + spent += dt.damage; + } + return totalDamageToAssign - spent; + } + + /** Updates labels and other UI elements. + * @param index index of the last assigned damage*/ + private void updateLabels() { + int damageLeft = totalDamageToAssign; + boolean allHaveLethal = true; + + for (DamageTarget dt : defenders) { + int dmg = dt.damage; + damageLeft -= dmg; + int lethal = getDamageToKill(dt.card); + int overkill = dmg - lethal; + StringBuilder sb = new StringBuilder(); + sb.append(dmg); + if(overkill >= 0) { + sb.append(" (Lethal"); + if(overkill > 0) + sb.append(" +").append(overkill); + sb.append(")"); + } + allHaveLethal &= dmg >= lethal; + dt.label.setText(sb.toString()); + } + + this.lblTotalDamage.setText(String.format("Available damage points: %d (of %d)", damageLeft, this.totalDamageToAssign)); + btnOK.setEnabled(damageLeft == 0); + lblAssignRemaining.setVisible(allHaveLethal && damageLeft > 0); + } + + // Dumps damage onto cards. Damage must be stored first, because if it is + // assigned dynamically, the cards die off and further damage to them can't + // be modified. + private void finish() { + if (getRemainingDamage() > 0) { + return; + } + + //dlg.dispose(); + } + + private int getDamageToKill(Card source) { + int lethalDamage = 0; + if (source == null) { + if (defender instanceof Player) { + Player p = (Player)defender; + lethalDamage = attackerHasInfect ? p.getGame().getRules().getPoisonCountersToLose() - p.getPoisonCounters() : p.getLife(); + } + else if (defender instanceof Card) { // planeswalker + Card pw = (Card)defender; + lethalDamage = pw.getCounters(CounterType.LOYALTY); + } + } + else { + lethalDamage = VAssignDamage.this.attackerHasDeathtouch ? 1 : Math.max(0, source.getLethalDamage()); + } + return lethalDamage; + } + + public Map getDamageMap() { + Map result = new HashMap(); + for (DamageTarget dt : defenders) { + result.put(dt.card, dt.damage); + } + return result; + } +} diff --git a/forge-m-base/src/forge/screens/match/views/VField.java b/forge-m-base/src/forge/screens/match/views/VField.java index 8e1146438ec..8bf78f5e970 100644 --- a/forge-m-base/src/forge/screens/match/views/VField.java +++ b/forge-m-base/src/forge/screens/match/views/VField.java @@ -1,21 +1,14 @@ package forge.screens.match.views; -import java.util.ArrayList; -import java.util.List; - import forge.game.card.Card; +import forge.game.zone.ZoneType; import forge.toolbox.FCardPanel; -import forge.toolbox.FDisplayObject; -import forge.toolbox.FScrollPane; -public class VField extends FScrollPane { +public class VField extends VZoneDisplay { private boolean flipped; - private final List creatures = new ArrayList(); - private final List lands = new ArrayList(); - private final List otherPermanents = new ArrayList(); - public VField() { + super(ZoneType.Battlefield); } public boolean isFlipped() { @@ -35,29 +28,29 @@ public class VField extends FScrollPane { @Override protected void doLayout(float width, float height) { - float x = 0; - float y = 0; + float x, y; + float x1 = 0; + float x2 = 0; + float y1 = 0; float cardSize = height / 2; - - for (FCardPanel cardPanel : creatures) { - cardPanel.setBounds(x, y, cardSize, cardSize); - x += cardSize; - } - x = 0; - y += cardSize; - for (FCardPanel cardPanel : lands) { - cardPanel.setBounds(x, y, cardSize, cardSize); - x += cardSize; - } - for (FCardPanel cardPanel : otherPermanents) { - cardPanel.setBounds(x, y, cardSize, cardSize); - x += cardSize; + float y2 = cardSize; + if (flipped) { + y1 = y2; + y2 = 0; } - if (flipped) { //flip all positions across x-axis if needed - for (FDisplayObject child : getChildren()) { - child.setTop(height - child.getBottom()); + for (FCardPanel cardPanel : cardPanels) { + if (cardPanel.getCard().isCreature()) { + x = x1; + y = y1; + x1 += cardSize; } + else { + x = x2; + y = y2; + x2 += cardSize; + } + cardPanel.setBounds(x, y, cardSize, cardSize); } } } diff --git a/forge-m-base/src/forge/screens/match/views/VPrompt.java b/forge-m-base/src/forge/screens/match/views/VPrompt.java index 2941012872e..f1ad1c31ae5 100644 --- a/forge-m-base/src/forge/screens/match/views/VPrompt.java +++ b/forge-m-base/src/forge/screens/match/views/VPrompt.java @@ -4,14 +4,11 @@ import org.apache.commons.lang3.StringUtils; import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment; -import forge.FThreads; import forge.Forge.Graphics; import forge.assets.FSkinColor; import forge.assets.FSkinFont; import forge.assets.FSkinColor.Colors; import forge.game.Game; -import forge.game.GameRules; -import forge.game.Match; import forge.screens.match.input.InputProxy; import forge.toolbox.FButton; import forge.toolbox.FContainer; diff --git a/forge-m-base/src/forge/screens/match/views/VZoneDisplay.java b/forge-m-base/src/forge/screens/match/views/VZoneDisplay.java index 1756b5cc14c..98cbe0c8989 100644 --- a/forge-m-base/src/forge/screens/match/views/VZoneDisplay.java +++ b/forge-m-base/src/forge/screens/match/views/VZoneDisplay.java @@ -9,7 +9,7 @@ import forge.toolbox.FScrollPane; public class VZoneDisplay extends FScrollPane { private ZoneType zoneType; - private final List cards = new ArrayList(); + protected final List cardPanels = new ArrayList(); public VZoneDisplay(ZoneType zoneType0) { zoneType = zoneType0; @@ -19,8 +19,12 @@ public class VZoneDisplay extends FScrollPane { return zoneType; } + public Iterable getCardPanels() { + return cardPanels; + } + public int getCount() { - return 99; //TODO + return cardPanels.size(); } public void update() { @@ -34,7 +38,7 @@ public class VZoneDisplay extends FScrollPane { float cardHeight = height; float cardWidth = ((cardHeight - 2 * FCardPanel.PADDING) / FCardPanel.ASPECT_RATIO) + 2 * FCardPanel.PADDING; //ensure aspect ratio maintained after padding applied - for (FCardPanel cardPanel : cards) { + for (FCardPanel cardPanel : cardPanels) { cardPanel.setBounds(x, y, cardWidth, cardHeight); x += cardWidth; } diff --git a/forge-m-base/src/forge/toolbox/FCardPanel.java b/forge-m-base/src/forge/toolbox/FCardPanel.java index 4345b253795..746ffb69a30 100644 --- a/forge-m-base/src/forge/toolbox/FCardPanel.java +++ b/forge-m-base/src/forge/toolbox/FCardPanel.java @@ -6,14 +6,26 @@ import forge.game.card.Card; public class FCardPanel extends FDisplayObject { public static final float ASPECT_RATIO = 3.5f / 2.5f; - public static final float PADDING = 3; //scale to leave vertical space between + public static final float PADDING = 2; //scale to leave vertical space between private final Card card; + private boolean highlighted; public FCardPanel(Card card0) { card = card0; } + public Card getCard() { + return card; + } + + public boolean isHighlighted() { + return highlighted; + } + public void setHighlighted(boolean highlighted0) { + highlighted = highlighted0; + } + @Override public void draw(Graphics g) { float x = PADDING; diff --git a/forge-m-base/src/forge/utils/GuiDisplayUtil.java b/forge-m-base/src/forge/utils/GuiDisplayUtil.java new file mode 100644 index 00000000000..12546c87829 --- /dev/null +++ b/forge-m-base/src/forge/utils/GuiDisplayUtil.java @@ -0,0 +1,485 @@ +/* + * 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.utils; + +import com.google.common.base.Predicates; +import com.google.common.collect.Lists; + +import forge.card.CardCharacteristicName; +import forge.game.Game; +import forge.game.GameType; +import forge.game.PlanarDice; +import forge.game.card.Card; +import forge.game.card.CardLists; +import forge.game.card.CardPredicates; +import forge.game.card.CounterType; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.spellability.AbilityManaPart; +import forge.game.spellability.SpellAbility; +import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; +import forge.item.IPaperCard; +import forge.item.PaperCard; +import forge.model.FModel; +import forge.player.HumanPlay; +import forge.screens.match.FControl; +import forge.screens.match.input.InputSelectCardsFromList; +import forge.toolbox.FOptionPane; +import forge.toolbox.GuiChoose; +import forge.toolbox.GuiDialog; +import forge.utils.ForgePreferences.FPref; + +import javax.swing.*; + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; + +public final class GuiDisplayUtil { + private GuiDisplayUtil() { + throw new AssertionError(); + } + + public static void devModeGenerateMana() { + final Card dummy = new Card(-777777); + dummy.setOwner(getGame().getPhaseHandler().getPriorityPlayer()); + Map produced = new HashMap(); + produced.put("Produced", "W W W W W W W U U U U U U U B B B B B B B G G G G G G G R R R R R R R 7"); + final AbilityManaPart abMana = new AbilityManaPart(dummy, produced); + getGame().getAction().invoke(new Runnable() { + @Override public void run() { abMana.produceMana(null); } + }); + } + + public static void devSetupGameState() { + int humanLife = -1; + int computerLife = -1; + + final Map humanCardTexts = new EnumMap(ZoneType.class); + final Map aiCardTexts = new EnumMap(ZoneType.class); + + String tChangePlayer = "NONE"; + String tChangePhase = "NONE"; + + final String wd = "."; + final JFileChooser fc = new JFileChooser(wd); + final int rc = fc.showDialog(null, "Select Game State File"); + if (rc != JFileChooser.APPROVE_OPTION) { + return; + } + + try { + final FileInputStream fstream = new FileInputStream(fc.getSelectedFile().getAbsolutePath()); + final DataInputStream in = new DataInputStream(fstream); + final BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + String temp = ""; + + while ((temp = br.readLine()) != null) { + + final String[] tempData = temp.split("="); + if (tempData.length < 2 || temp.charAt(0) == '#') { + continue; + } + + final String categoryName = tempData[0].toLowerCase(); + final String categoryValue = tempData[1]; + + if (categoryName.equals("humanlife")) humanLife = Integer.parseInt(categoryValue); + else if (categoryName.equals("ailife")) computerLife = Integer.parseInt(categoryValue); + + else if (categoryName.equals("activeplayer")) tChangePlayer = categoryValue.trim().toLowerCase(); + else if (categoryName.equals("activephase")) tChangePhase = categoryValue; + + else if (categoryName.equals("humancardsinplay")) humanCardTexts.put(ZoneType.Battlefield, categoryValue); + else if (categoryName.equals("aicardsinplay")) aiCardTexts.put(ZoneType.Battlefield, categoryValue); + else if (categoryName.equals("humancardsinhand")) humanCardTexts.put(ZoneType.Hand, categoryValue); + else if (categoryName.equals("aicardsinhand")) aiCardTexts.put(ZoneType.Hand, categoryValue); + else if (categoryName.equals("humancardsingraveyard")) humanCardTexts.put(ZoneType.Graveyard, categoryValue); + else if (categoryName.equals("aicardsingraveyard")) aiCardTexts.put(ZoneType.Graveyard, categoryValue); + else if (categoryName.equals("humancardsinlibrary")) humanCardTexts.put(ZoneType.Library, categoryValue); + else if (categoryName.equals("aicardsinlibrary")) aiCardTexts.put(ZoneType.Library, categoryValue); + else if (categoryName.equals("humancardsinexile")) humanCardTexts.put(ZoneType.Exile, categoryValue); + else if (categoryName.equals("aicardsinexile")) aiCardTexts.put(ZoneType.Exile, categoryValue); + + } + + in.close(); + } + catch (final FileNotFoundException fnfe) { + FOptionPane.showErrorDialog("File not found: " + fc.getSelectedFile().getAbsolutePath()); + } + catch (final Exception e) { + FOptionPane.showErrorDialog("Error loading battle setup file!"); + return; + } + + setupGameState(humanLife, computerLife, humanCardTexts, aiCardTexts, tChangePlayer, tChangePhase); + } + + private static void setupGameState(final int humanLife, final int computerLife, final Map humanCardTexts, + final Map aiCardTexts, final String tChangePlayer, final String tChangePhase) { + + final Game game = getGame(); + game.getAction().invoke(new Runnable() { + @Override + public void run() { + final Player human = game.getPlayers().get(0); + final Player ai = game.getPlayers().get(1); + + Player newPlayerTurn = tChangePlayer.equals("human") ? newPlayerTurn = human : tChangePlayer.equals("ai") ? newPlayerTurn = ai : null; + PhaseType newPhase = tChangePhase.trim().equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tChangePhase); + + game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn); + + + game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); + + devSetupPlayerState(humanLife, humanCardTexts, human); + devSetupPlayerState(computerLife, aiCardTexts, ai); + + game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + + game.getAction().checkStaticAbilities(); + } + }); + } + + private static void devSetupPlayerState(int life, Map cardTexts, final Player p) { + Map> humanCards = new EnumMap>(ZoneType.class); + for(Entry kv : cardTexts.entrySet()) { + humanCards.put(kv.getKey(), GuiDisplayUtil.devProcessCardsForZone(kv.getValue().split(";"), p)); + } + + if (life > 0) p.setLife(life, null); + for (Entry> kv : humanCards.entrySet()) { + if (kv.getKey() == ZoneType.Battlefield) { + for (final Card c : kv.getValue()) { + p.getZone(ZoneType.Hand).add(c); + p.getGame().getAction().moveToPlay(c); + c.setSickness(false); + } + } else { + p.getZone(kv.getKey()).setCards(kv.getValue()); + } + } + } + + /** + *

+ * devProcessCardsForZone. + *

+ * + * @param data + * an array of {@link java.lang.String} objects. + * @param player + * a {@link forge.game.player.Player} object. + * @return a {@link forge.CardList} object. + */ + private static List devProcessCardsForZone(final String[] data, final Player player) { + final List cl = new ArrayList(); + for (final String element : data) { + final String[] cardinfo = element.trim().split("\\|"); + + final Card c = Card.fromPaperCard(FModel.getMagicDb().getCommonCards().getCard(cardinfo[0]), player); + + boolean hasSetCurSet = false; + for (final String info : cardinfo) { + if (info.startsWith("Set:")) { + c.setCurSetCode(info.substring(info.indexOf(':') + 1)); + hasSetCurSet = true; + } else if (info.equalsIgnoreCase("Tapped:True")) { + c.tap(); + } else if (info.startsWith("Counters:")) { + final String[] counterStrings = info.substring(info.indexOf(':') + 1).split(","); + for (final String counter : counterStrings) { + c.addCounter(CounterType.valueOf(counter), 1, true); + } + } else if (info.equalsIgnoreCase("SummonSick:True")) { + c.setSickness(true); + } else if (info.equalsIgnoreCase("FaceDown:True")) { + c.setState(CardCharacteristicName.FaceDown); + } + } + + if (!hasSetCurSet) { + c.setCurSetCode(c.getMostRecentSet()); + } + + cl.add(c); + } + return cl; + } + + /** + *

+ * devModeTutor. + *

+ * + * @since 1.0.15 + */ + public static void devModeTutor() { + Player pPriority = getGame().getPhaseHandler().getPriorityPlayer(); + if (pPriority == null) { + GuiDialog.message("No player has priority now, can't tutor from their deck at the moment"); + return; + } + final List lib = pPriority.getCardsIn(ZoneType.Library); + final Card card = GuiChoose.oneOrNone("Choose a card", lib); + if (card == null) { return; } + + getGame().getAction().invoke(new Runnable() { + @Override + public void run() { + getGame().getAction().moveToHand(card); + } + }); + } + + /** + *

+ * devModeAddCounter. + *

+ * + * @since 1.0.15 + */ + public static void devModeAddCounter() { + final Card card = GuiChoose.oneOrNone("Add counters to which card?", getGame().getCardsIn(ZoneType.Battlefield)); + if (card == null) { return; } + + final CounterType counter = GuiChoose.oneOrNone("Which type of counter?", CounterType.values()); + if (counter == null) { return; } + + final Integer count = GuiChoose.getInteger("How many counters?", 1, Integer.MAX_VALUE, 10); + if (count == null) { return; } + + card.addCounter(counter, count, false); + } + + /** + *

+ * devModeTapPerm. + *

+ * + * @since 1.0.15 + */ + public static void devModeTapPerm() { + final Game game = getGame(); + game.getAction().invoke(new Runnable() { + @Override + public void run() { + final List untapped = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Predicates.not(CardPredicates.Presets.TAPPED)); + InputSelectCardsFromList inp = new InputSelectCardsFromList(0, Integer.MAX_VALUE, untapped); + inp.setCancelAllowed(true); + inp.setMessage("Choose permanents to tap"); + inp.showAndWait(); + if (!inp.hasCancelled()) { + for (Card c : inp.getSelected()) { + c.tap(); + } + } + } + }); + } + + /** + *

+ * devModeUntapPerm. + *

+ * + * @since 1.0.15 + */ + public static void devModeUntapPerm() { + final Game game = getGame(); + + + + game.getAction().invoke(new Runnable() { + @Override + public void run() { + final List tapped = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.TAPPED); + InputSelectCardsFromList inp = new InputSelectCardsFromList(0, Integer.MAX_VALUE, tapped); + inp.setCancelAllowed(true); + inp.setMessage("Choose permanents to untap"); + inp.showAndWait(); + if( !inp.hasCancelled() ) + for(Card c : inp.getSelected()) + c.untap(); + } + }); + } + + + /** + *

+ * devModeSetLife. + *

+ * + * @since 1.1.3 + */ + public static void devModeSetLife() { + final List players = getGame().getPlayers(); + final Player player = GuiChoose.oneOrNone("Set life for which player?", players); + if (player == null) { return; } + + final Integer life = GuiChoose.getInteger("Set life to what?", 0); + if (life == null) { return; } + + player.setLife(life, null); + } + + /** + *

+ * devModeTutorAnyCard. + *

+ * + * @since 1.2.7 + */ + public static void devModeCardToHand() { + final List players = getGame().getPlayers(); + final Player p = GuiChoose.oneOrNone("Put card in hand for which player?", players); + if (null == p) { + return; + } + + final List cards = Lists.newArrayList(FModel.getMagicDb().getCommonCards().getUniqueCards()); + Collections.sort(cards); + + // use standard forge's list selection dialog + final IPaperCard c = GuiChoose.oneOrNone("Name the card", cards); + if (c == null) { + return; + } + + getGame().getAction().invoke(new Runnable() { @Override public void run() { + getGame().getAction().moveToHand(Card.fromPaperCard(c, p)); + }}); + } + + public static void devModeCardToBattlefield() { + final List players = getGame().getPlayers(); + final Player p = GuiChoose.oneOrNone("Put card in play for which player?", players); + if (null == p) { + return; + } + + final List cards = Lists.newArrayList(FModel.getMagicDb().getCommonCards().getUniqueCards()); + Collections.sort(cards); + + // use standard forge's list selection dialog + final IPaperCard c = GuiChoose.oneOrNone("Name the card", cards); + if (c == null) { + return; + } + + final Game game = getGame(); + game.getAction().invoke(new Runnable() { + @Override public void run() { + final Card forgeCard = Card.fromPaperCard(c, p); + + if (c.getRules().getType().isLand()) { + game.getAction().moveToPlay(forgeCard); + } else { + final List choices = forgeCard.getBasicSpells(); + if (choices.isEmpty()) { + return; // when would it happen? + } + + final SpellAbility sa = choices.size() == 1 ? choices.get(0) : GuiChoose.oneOrNone("Choose", choices); + if (sa == null) { + return; // happens if cancelled + } + + game.getAction().moveToHand(forgeCard); // this is really needed (for rollbacks at least) + // Human player is choosing targets for an ability controlled by chosen player. + sa.setActivatingPlayer(p); + HumanPlay.playSaWithoutPayingManaCost(game, sa, true); + } + game.getStack().addAllTirggeredAbilitiesToStack(); // playSa could fire some triggers + } + }); + } + + public static void devModeRiggedPlanarRoll() { + final List players = getGame().getPlayers(); + final Player player = GuiChoose.oneOrNone("Which player should roll?", players); + if (player == null) { return; } + + final PlanarDice res = GuiChoose.oneOrNone("Choose result", PlanarDice.values()); + if (res == null) { return; } + + System.out.println("Rigging planar dice roll: " + res.toString()); + + //DBG + //System.out.println("ActivePlanes: " + getGame().getActivePlanes()); + //System.out.println("CommandPlanes: " + getGame().getCardsIn(ZoneType.Command)); + + + + getGame().getAction().invoke(new Runnable() { + @Override + public void run() { + PlanarDice.roll(player, res); + } + }); + } + + public static void devModePlaneswalkTo() { + final Game game = getGame(); + if (!game.getRules().hasAppliedVariant(GameType.Planechase)) { return; } + final Player p = game.getPhaseHandler().getPlayerTurn(); + + final List allPlanars = new ArrayList(); + for (PaperCard c : FModel.getMagicDb().getVariantCards().getAllCards()) { + if (c.getRules().getType().isPlane() || c.getRules().getType().isPhenomenon()) { + allPlanars.add(c); + } + } + Collections.sort(allPlanars); + + // use standard forge's list selection dialog + final IPaperCard c = GuiChoose.oneOrNone("Name the card", allPlanars); + if (c == null) { return; } + final Card forgeCard = Card.fromPaperCard(c, p); + + forgeCard.setOwner(p); + getGame().getAction().invoke(new Runnable() { + @Override + public void run() { + getGame().getAction().changeZone(null, p.getZone(ZoneType.PlanarDeck), forgeCard, 0); + PlanarDice.roll(p, PlanarDice.Planeswalk); + } + }); + } + + private static Game getGame() { + return FControl.getGame(); + } + + public static String getPlayerName() { + return FModel.getPreferences().getPref(FPref.PLAYER_NAME); + } + + public static String personalizeHuman(String text) { + String playerName = FModel.getPreferences().getPref(FPref.PLAYER_NAME); + return text.replaceAll("(?i)human", playerName); + } + + +} // end class GuiDisplayUtil diff --git a/forge-m-base/src/forge/utils/GuiUtils.java b/forge-m-base/src/forge/utils/GuiUtils.java new file mode 100644 index 00000000000..e2f3ada3ad3 --- /dev/null +++ b/forge-m-base/src/forge/utils/GuiUtils.java @@ -0,0 +1,139 @@ +/* + * 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.utils; + +import forge.game.card.Card; +import forge.screens.match.FControl; +import forge.screens.match.views.VPlayerPanel; +import forge.toolbox.FCardPanel; + +import javax.swing.*; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; + +/** + *

+ * GuiUtils class. + *

+ * + * @author Forge + * @version $Id: GuiUtils.java 24769 2014-02-09 13:56:04Z Hellfish $ + */ +public final class GuiUtils { + private GuiUtils() { + throw new AssertionError(); + } + + /** + * Attempts to create a font from a filename. Concise error reported if + * exceptions found. + * + * @param filename + * String + * @return Font + */ + public static Font newFont(final String filename) { + final File file = new File(filename); + Font ttf = null; + + try { + ttf = Font.createFont(Font.TRUETYPE_FONT, file); + } catch (final FontFormatException e) { + System.err.println("GuiUtils > newFont: bad font format \"" + filename + "\""); + } catch (final IOException e) { + System.err.println("GuiUtils > newFont: can't find \"" + filename + "\""); + } + return ttf; + } + + + + private static final int minItemWidth = 100; + private static final int itemHeight = 25; + + public static void setMenuItemSize(JMenuItem item) { + item.setPreferredSize(new Dimension(Math.max(item.getPreferredSize().width, minItemWidth), itemHeight)); + } + + public static JMenu createMenu(String label) { + if (label.startsWith("")) { //adjust label if HTML + label = "" + "
" + label.substring(6, label.length() - 7) + "
"; + } + JMenu menu = new JMenu(label); + setMenuItemSize(menu); + return menu; + } + + public static JMenuItem createMenuItem(String label, KeyStroke accelerator, final Runnable onClick, boolean enabled, boolean bold) { + if (label.startsWith("")) { //adjust label if HTML + label = "" + "
" + label.substring(6, label.length() - 7) + "
"; + } + JMenuItem item = new JMenuItem(label); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + if (null != onClick) { + onClick.run(); + } + } + }); + item.setEnabled(enabled); + item.setAccelerator(accelerator); + if (bold) { + item.setFont(item.getFont().deriveFont(Font.BOLD)); + } + setMenuItemSize(item); + return item; + } + + public static void addMenuItem(JPopupMenu parent, String label, KeyStroke accelerator, Runnable onClick) { + parent.add(createMenuItem(label, accelerator, onClick, true, false)); + } + + public static void addMenuItem(JMenuItem parent, String label, KeyStroke accelerator, Runnable onClick) { + parent.add(createMenuItem(label, accelerator, onClick, true, false)); + } + + public static void addMenuItem(JPopupMenu parent, String label, KeyStroke accelerator, Runnable onClick, boolean enabled) { + parent.add(createMenuItem(label, accelerator, onClick, enabled, false)); + } + + public static void addMenuItem(JMenuItem parent, String label, KeyStroke accelerator, Runnable onClick, boolean enabled) { + parent.add(createMenuItem(label, accelerator, onClick, enabled, false)); + } + + public static void addMenuItem(JPopupMenu parent, String label, KeyStroke accelerator, Runnable onClick, boolean enabled, boolean bold) { + parent.add(createMenuItem(label, accelerator, onClick, enabled, bold)); + } + + public static void addMenuItem(JMenuItem parent, String label, KeyStroke accelerator, Runnable onClick, boolean enabled, boolean bold) { + parent.add(createMenuItem(label, accelerator, onClick, enabled, bold)); + } + + public static void addSeparator(JPopupMenu parent) { + parent.add(new JSeparator()); + } + + public static void addSeparator(JMenuItem parent) { + parent.add(new JSeparator()); + } +}