diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index c1a9cce568b..d4a568fb7d3 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -1877,6 +1877,8 @@ public class AiController { } else { return options.get(0); } + case ChooseNumber: + return Aggregates.random(options); default: return options.get(0); } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index 4e637aeb11c..db93f11e025 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -53,7 +53,7 @@ public final class AbilityFactory { public static final List additionalAbilityKeys = Lists.newArrayList( "WinSubAbility", "OtherwiseSubAbility", // Clash "BidSubAbility", // BidLifeEffect - "ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", // ChooseNumber + "ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", "GuessCorrect", "GuessWrong", // ChooseNumber "HeadsSubAbility", "TailsSubAbility", "LoseSubAbility", // FlipCoin "TrueSubAbility", "FalseSubAbility", // Branch "ChosenPile", "UnchosenPile", // MultiplePiles & TwoPiles diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java index 416d2c6642e..aca7c591281 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseNumberEffect.java @@ -1,5 +1,6 @@ package forge.game.ability.effects; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -15,6 +16,9 @@ import forge.game.spellability.SpellAbility; import forge.util.Lang; import forge.util.Localizer; import forge.util.MyRandom; +import forge.util.collect.FCollectionView; + +import org.apache.commons.lang3.tuple.Pair; public class ChooseNumberEffect extends SpellAbilityEffect { @@ -47,11 +51,17 @@ public class ChooseNumberEffect extends SpellAbilityEffect { final Map chooseMap = Maps.newHashMap(); + // defined guesser must try to guess the chosen - currently only on "The Toymaker's Trap" + boolean guessedCorrect = false; + Pair guessPair = null; + // may need future work to ensure chooser and guesser get same choices even in absence of RemoveChoices param + List choices = new ArrayList<>(); + for (final Player p : getTargetPlayers(sa)) { if (!p.isInGame()) { continue; } - int chosen; + Integer chosen; if (random) { chosen = MyRandom.getRandom().nextInt((max - min) + 1) + min; //TODO more useful notify for RepeatEach -> ChooseNumber with random @@ -61,6 +71,16 @@ public class ChooseNumberEffect extends SpellAbilityEffect { if (anyNumber) { Integer value = p.getController().announceRequirements(sa, title); chosen = value == null ? 0 : value; + } else if (sa.hasParam("RemoveChoices")) { + // currently we always remove remembered numbers, so the value is not really used yet + for (int i = min; i <= max; i++) { + choices.add(i); + } + for (Object o : card.getRemembered()) { + if (o instanceof Integer) choices.remove((Integer) o); + } + if (choices.isEmpty()) continue; + chosen = p.getController().chooseNumber(sa, title, choices, null); } else { chosen = p.getController().chooseNumber(sa, title, min, max); } @@ -72,15 +92,35 @@ public class ChooseNumberEffect extends SpellAbilityEffect { card.setChosenNumber(chosen); } if (sa.hasParam("Notify")) { - p.getGame().getAction().notifyOfValue(sa, card, Localizer.getInstance().getMessage("lblPlayerPickedChosen", p.getName(), chosen), p); + p.getGame().getAction().notifyOfValue(sa, card, Localizer.getInstance(). + getMessage("lblPlayerPickedChosen", p.getName(), chosen), p); + } + if (sa.hasParam("Guesser") && chosen != null) { // if nothing was chosen, there is nothing to guess + final FCollectionView gChoices = + AbilityUtils.getDefinedPlayers(card, sa.getParam("Guesser"), sa); + final Player guesser = choices.isEmpty() ? null : p.getController(). + chooseSingleEntityForEffect(gChoices, sa, Localizer.getInstance().getMessage("lblChoosePlayer"), + false, null); + if (guesser != null) { + guessPair = Pair.of(guesser, guesser.getController().chooseNumber(sa, + Localizer.getInstance().getMessage("lblChooseNumber"), choices, null)); + // if more complicated effects require this in the future it may be worth a unique message + if (chooseMap.containsValue(guessPair.getValue())) guessedCorrect = true; + } } } - if (secretlyChoose) { + + if (secretlyChoose && !chooseMap.isEmpty()) { StringBuilder sb = new StringBuilder(); List highestNum = Lists.newArrayList(); List lowestNum = Lists.newArrayList(); int highest = 0; int lowest = Integer.MAX_VALUE; + if (guessPair != null) { + sb.append(Localizer.getInstance().getMessage("lblPlayerGuessedNum", guessPair.getKey().getName(), + String.valueOf(guessPair.getValue()))); + sb.append("\r\n"); + } for (Entry ev : chooseMap.entrySet()) { int num = ev.getValue(); Player player = ev.getKey(); @@ -153,7 +193,24 @@ public class ChooseNumberEffect extends SpellAbilityEffect { card.addRemembered(highestNum); } } + + if (sa.hasParam("GuessCorrect") && guessedCorrect) { // correct guess doesn't use any chosen num yet + SpellAbility sub = sa.getAdditionalAbility("GuessCorrect"); + AbilityUtils.resolve(sub); + } + + if (sa.hasParam("GuessWrong") && guessPair != null && !guessedCorrect) { + SpellAbility sub = sa.getAdditionalAbility("GuessWrong"); + // wrong currently uses the guess, not the chosen + card.setChosenNumber(guessPair.getValue()); + card.addRemembered(guessPair.getKey()); + AbilityUtils.resolve(sub); + card.clearChosenNumber(); + card.removeRemembered(guessPair.getKey()); + } } + + if (sa.hasParam("RememberChosen")) card.addRemembered(chooseMap.values()); } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index e754f8e99a9..6a3633348c9 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -1830,6 +1830,11 @@ public class Card extends GameEntity implements Comparable, IHasSVars { view.updateChosenNumber(this); } + public final void clearChosenNumber() { + chosenNumber = null; + view.clearChosenNumber(); + } + public final Card getExiledWith() { return exiledWith; } diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 9aa8fb65c57..dda2778bdd1 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -387,6 +387,9 @@ public class CardView extends GameEntityView { void updateChosenNumber(Card c) { set(TrackableProperty.ChosenNumber, c.getChosenNumber().toString()); } + void clearChosenNumber() { + set(TrackableProperty.ChosenNumber, ""); + } public List getStoredRolls() { return get(TrackableProperty.StoredRolls); diff --git a/forge-gui/res/cardsfolder/upcoming/the_toymakers_trap.txt b/forge-gui/res/cardsfolder/upcoming/the_toymakers_trap.txt new file mode 100644 index 00000000000..98fbdc1e973 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_toymakers_trap.txt @@ -0,0 +1,12 @@ +Name:The Toymaker's Trap +ManaCost:2 B +Types:Enchantment +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigChooseNumber | TriggerDescription$ At the beginning of your upkeep, secretly choose a number between 1 and 5 that hasn't been chosen. If you do, an opponent guesses which number you chose, then you reveal the number you chose. If they guessed wrong, they lose life equal to the number they guessed and you draw a card. If they guessed right, sacrifice The Toymaker's Trap. +SVar:TrigChooseNumber:DB$ ChooseNumber | SecretlyChoose$ True | Min$ 1 | Max$ 5 | RemoveChoices$ Remembered | Guesser$ Opponent | GuessWrong$ DBLoseLife | GuessCorrect$ DBSac | RememberChosen$ True | SubAbility$ DBCleanup +SVar:DBLoseLife:DB$ LoseLife | Defined$ RememberedPlayer | LifeAmount$ Count$ChosenNumber | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw +SVar:DBSac:DB$ Sacrifice +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.Self | Execute$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Sacrifice +Oracle:At the beginning of your upkeep, secretly choose a number between 1 and 5 that hasn't been chosen. If you do, an opponent guesses which number you chose, then you reveal the number you chose. If they guessed wrong, they lose life equal to the number they guessed and you draw a card. If they guessed right, sacrifice The Toymaker's Trap. diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 4e7a080de11..a6a17e4aefb 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1907,6 +1907,7 @@ lblChooseOne=Wähle eines #ChooseNumberEffect.java lblChooseNumber=Wähle eine Zahl lblPlayerChoseNum={0} wähle {1} +lblPlayerGuessedNum={0} hat {1} erraten #ChoosePlayerEffect.java lblChoosePlayer=Wähle einen Spieler #ChooseSourceEffect.java diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 6ee5057c2cd..a73c82188e2 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1912,6 +1912,7 @@ lblChooseOne=Choose one #ChooseNumberEffect.java lblChooseNumber=Choose a number lblPlayerChoseNum={0} chose {1} +lblPlayerGuessedNum={0} guessed {1} #ChoosePlayerEffect.java lblChoosePlayer=Choose a player #ChooseSourceEffect.java diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 54f3e854074..9be9dbe495a 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1908,6 +1908,7 @@ lblChooseOne=Elige uno #ChooseNumberEffect.java lblChooseNumber=Elige un número lblPlayerChoseNum={0} eligió {1} +lblPlayerGuessedNum={0} adivinó {1} #ChoosePlayerEffect.java lblChoosePlayer=Elige un jugador #ChooseSourceEffect.java diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index 527b38529c8..1d8857801e2 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -1878,7 +1878,7 @@ lblLookingCardIn=Regarder les cartes dans lblDoYouWantPlayCard=Voulez-vous jouer à {0} ? lblDoYouWantPlayCardTransformed=Voulez-vous jouer à {0} transformée? lblSelectCardFromPlayerZone=Sélectionnez une carte de {0} {1} -lblSelectUpToNumCardFromPlayerZone=S\u00e9lectionnez jusqu''\u00e0 {0} cartes de {1} {2} +lblSelectUpToNumCardFromPlayerZone=Sélectionnez jusqu''à {0} cartes de {1} {2} lblSelectCardsFromPlayerZone=Sélectionner les cartes de {0} {1} lblCancelSearchUpToSelectNumCards=Annuler la recherche ? Jusqu''à {0} carte(s) supplémentaire(s) peuvent être sélectionnées. #ChangeZoneAllEffect.java @@ -1911,6 +1911,7 @@ lblChooseOne=Choisissez-en un #ChooseNumberEffect.java lblChooseNumber=Choisissez un nombre lblPlayerChoseNum={0} a choisi {1} +lblPlayerGuessedNum={0} deviné {1} #ChoosePlayerEffect.java lblChoosePlayer=Choisir un joueur #ChooseSourceEffect.java diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 06e4f6cbc57..1b5794c5ec0 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1908,6 +1908,7 @@ lblChooseOne=Scegli uno #ChooseNumberEffect.java lblChooseNumber=Scegli un numero lblPlayerChoseNum={0} ha scelto {1} +lblPlayerGuessedNum={0} ha indovinato {1} #ChoosePlayerEffect.java lblChoosePlayer=Scegli un giocatore #ChooseSourceEffect.java diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index a8cb512d86c..d11bbfcd3ea 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -1907,6 +1907,7 @@ lblChooseOne=以下から 1つを選ぶ #ChooseNumberEffect.java lblChooseNumber=数字を 1つ選ぶ lblPlayerChoseNum={0}が {1}を選んだ +lblPlayerGuessedNum={0}は {1}を推測しました #ChoosePlayerEffect.java lblChoosePlayer=プレイヤーを選ぶ #ChooseSourceEffect.java diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index eb9488076f7..bdce81cb132 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -1969,6 +1969,7 @@ lblChooseOne=Escolha um #ChooseNumberEffect.java lblChooseNumber=Escolha um número lblPlayerChoseNum={0} escolheu {1} +lblPlayerGuessedNum={0} adivinhou {1} #ChoosePlayerEffect.java lblChoosePlayer=Escolha um jogador #ChooseSourceEffect.java diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 7747156db03..1edb5de3e5f 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1912,6 +1912,7 @@ lblChooseOne=选择一个 #ChooseNumberEffect.java lblChooseNumber=选择一个数 lblPlayerChoseNum={0}已选择{1} +lblPlayerGuessedNum={0}猜了{1} #ChoosePlayerEffect.java lblChoosePlayer=选择一个牌手 #ChooseSourceEffect.java