diff --git a/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java b/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java index 5b32df43cee..57d6d2577e4 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ImmediateTriggerAi.java @@ -1,11 +1,8 @@ package forge.ai.ability; -import forge.ai.AiController; -import forge.ai.AiPlayDecision; -import forge.ai.ComputerUtilCost; -import forge.ai.PlayerControllerAi; -import forge.ai.SpellAbilityAi; -import forge.ai.SpellApiToAi; +import forge.ai.*; +import forge.card.mana.ManaCost; +import forge.game.mana.ManaCostBeingPaid; import forge.game.player.Player; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; @@ -47,6 +44,19 @@ public class ImmediateTriggerAi extends SpellAbilityAi { sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai)); } + if ("NumTimes".equals(sa.getParam("Announce"))) { // e.g. the Adversary cycle + ManaCost mkCost = sa.getPayCosts().getTotalMana(); + ManaCost mCost = ManaCost.ZERO; + for (int i = 0; i < 10; i++) { + mCost = ManaCost.combine(mCost, mkCost); + ManaCostBeingPaid mcbp = new ManaCostBeingPaid(mCost); + if (!ComputerUtilMana.canPayManaCost(mcbp, sa, ai)) { + sa.getHostCard().setSVar("NumTimes", "Number$" + i); + break; + } + } + } + AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); trigsa.setActivatingPlayer(ai); diff --git a/forge-game/src/main/java/forge/game/card/CounterEnumType.java b/forge-game/src/main/java/forge/game/card/CounterEnumType.java index 9aa676078bc..38dbafdf64e 100644 --- a/forge-game/src/main/java/forge/game/card/CounterEnumType.java +++ b/forge-game/src/main/java/forge/game/card/CounterEnumType.java @@ -321,6 +321,8 @@ public enum CounterEnumType { UNITY("UNITY", 242, 156, 255), + VALOR("VALOR", 252, 250, 222), + VELOCITY("VELO", 255, 95, 138), VERSE("VERSE", 0, 237, 155), diff --git a/forge-game/src/main/java/forge/game/cost/CostPartMana.java b/forge-game/src/main/java/forge/game/cost/CostPartMana.java index e36f2780aee..c8d198efbf2 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPartMana.java +++ b/forge-game/src/main/java/forge/game/cost/CostPartMana.java @@ -18,7 +18,9 @@ package forge.game.cost; import forge.card.mana.ManaCost; +import forge.game.ability.AbilityUtils; import forge.game.mana.ManaConversionMatrix; +import forge.game.mana.ManaCostBeingPaid; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -35,6 +37,7 @@ public class CostPartMana extends CostPart { private boolean xCantBe0 = false; private boolean isExiledCreatureCost = false; private boolean isEnchantedCreatureCost = false; + private boolean isCostPayAnyNumberOfTimes = false; private final String restriction; private ManaConversionMatrix cardMatrix = null; @@ -53,7 +56,8 @@ public class CostPartMana extends CostPart { this.xCantBe0 = "XCantBe0".equals(restriction); this.isExiledCreatureCost = "Exiled".equalsIgnoreCase(restriction); this.isEnchantedCreatureCost = "EnchantedCost".equalsIgnoreCase(restriction); - this.restriction = xCantBe0 || isExiledCreatureCost || isEnchantedCreatureCost? null : restriction; + this.isCostPayAnyNumberOfTimes = "NumTimes".equalsIgnoreCase(restriction); + this.restriction = xCantBe0 || isExiledCreatureCost || isEnchantedCreatureCost || isCostPayAnyNumberOfTimes ? null : restriction; } // This version of the constructor allows to explicitly set exiledCreatureCost/enchantedCreatureCost, used only when copying costs @@ -62,7 +66,7 @@ public class CostPartMana extends CostPart { this.xCantBe0 = XCantBe0; this.isExiledCreatureCost = exiledCreatureCost; this.isEnchantedCreatureCost = enchantedCreatureCost; - this.restriction = xCantBe0 || isExiledCreatureCost || isEnchantedCreatureCost? null : restriction; + this.restriction = xCantBe0 || isExiledCreatureCost || isEnchantedCreatureCost || isCostPayAnyNumberOfTimes ? null : restriction; } /** @@ -140,6 +144,18 @@ public class CostPartMana extends CostPart { if (isEnchantedCreatureCost() && sa.getHostCard().getEnchantingCard() != null) { return sa.getHostCard().getEnchantingCard().getManaCost(); } + if (isCostPayAnyNumberOfTimes) { + int timesToPay = AbilityUtils.calculateAmount(sa.getHostCard(), sa.getSVar("NumTimes"), sa); + if (timesToPay == 0) { + return null; + } else { + ManaCostBeingPaid totalMana = new ManaCostBeingPaid(getManaToPay()); + for (int i = 1; i < timesToPay; i++) { + totalMana.addManaCost(getManaToPay()); + } + return totalMana.toManaCost(); + } + } return getManaToPay(); } diff --git a/forge-gui/res/cardsfolder/upcoming/bloodthirsty_adversary.txt b/forge-gui/res/cardsfolder/upcoming/bloodthirsty_adversary.txt new file mode 100644 index 00000000000..bb93d833bf1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bloodthirsty_adversary.txt @@ -0,0 +1,15 @@ +Name:Bloodthirsty Adversary +ManaCost:1 R +Types:Creature Vampire +PT:2/2 +K:Haste +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {2}{R} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 R\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBExile +SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ NumTimes | ValidTgts$ Instant.cmcLE3,Sorcery.cmcLE3 | TgtPrompt$ Select up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard | RememberChanged$ True | SubAbility$ DBCopyCast +SVar:DBCopyCast:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | Controller$ You | CopyCard$ True | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | Amount$ All | SubAbility$ ExileMe +SVar:ExileMe:DB$ ChangeZoneAll | Origin$ Stack | Destination$ Exile | ChangeType$ Card.Self | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:NumTimes:Number$0 +DeckHas:Ability$Counters +Oracle:Haste\nWhen Bloodthirsty Adversary enters the battlefield, you may pay {2}{R} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Bloodthirsty Adversary, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. diff --git a/forge-gui/res/cardsfolder/upcoming/intrepid_adversary.txt b/forge-gui/res/cardsfolder/upcoming/intrepid_adversary.txt new file mode 100644 index 00000000000..fa8dea9ae8d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/intrepid_adversary.txt @@ -0,0 +1,13 @@ +Name:Intrepid Adversary +ManaCost:1 W +Types:Creature Human Scout +PT:3/1 +K:Lifelink +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{W} any number of times. When you pay this cost one or more times, put that many valor counters on CARDNAME. +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 W\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many valor counters on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ VALOR | CounterNum$ NumTimes +SVar:NumTimes:Number$0 +S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ Z | AddToughness$ Z | Description$ Creatures you control get +1/+1 for each valor counter on CARDNAME. +SVar:Z:Count$CardCounters.VALOR +DeckHas:Ability$LifeGain & Ability$Counters +Oracle:Lifelink\nWhen Intrepid Adversary enters the battlefield, you may pay {1}{W} any number of times. When you pay this cost one or more times, put that many valor counters on Intrepid Adversary.\nCreatures you control get +1/+1 for each valor counter on Intrepid Adversary. diff --git a/forge-gui/res/cardsfolder/upcoming/primal_adversary.txt b/forge-gui/res/cardsfolder/upcoming/primal_adversary.txt new file mode 100644 index 00000000000..2d2ce76556d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/primal_adversary.txt @@ -0,0 +1,12 @@ +Name:Primal Adversary +ManaCost:2 G +Types:Creature Wolf +PT:4/5 +K:Trample +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{G} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 G\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | TargetMin$ 0 | TargetMax$ NumTimes | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select up to that many target lands you control | Power$ 3 | Toughness$ 3 | Types$ Wolf,Creature | Keywords$ Haste | Duration$ Permanent +SVar:NumTimes:Number$0 +DeckHas:Ability$Counters +Oracle:Trample\nWhen Primal Adversary enters the battlefield, you may pay {1}{G} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Primal Adversary, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. diff --git a/forge-gui/res/cardsfolder/upcoming/spectral_adversary.txt b/forge-gui/res/cardsfolder/upcoming/spectral_adversary.txt new file mode 100644 index 00000000000..063ddcf9bd9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/spectral_adversary.txt @@ -0,0 +1,13 @@ +Name:Spectral Adversary +ManaCost:1 U +Types:Creature Spirit +PT:2/1 +K:Flash +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{U} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many other target artifacts, creatures, and/or enchantments phase out. +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 U\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many other target artifacts, creatures, and/or enchantments phase out. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBPhases +SVar:DBPhases:DB$ Phases | ValidTgts$ Creature.Other,Artifact.Other,Enchantment.Other | TgtPrompt$ Select up to that many other target artifacts, creatures, and/or enchantments | TargetMin$ 0 | TargetMax$ NumTimes +SVar:NumTimes:Number$0 +DeckHas:Ability$Counters +Oracle:Flash\nFlying\nWhen Spectral Adversary enters the battlefield, you may pay {1}{U} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Spectral Adversary, then up to that many other target artifacts, creatures, and/or enchantments phase out. diff --git a/forge-gui/res/cardsfolder/upcoming/tainted_adversary.txt b/forge-gui/res/cardsfolder/upcoming/tainted_adversary.txt new file mode 100644 index 00000000000..ca6f8d1eff0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tainted_adversary.txt @@ -0,0 +1,13 @@ +Name:Tainted Adversary +ManaCost:1 B +Types:Creature Zombie +PT:2/3 +K:Deathtouch +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {2}{B} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 B\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenScript$ b_2_2_zombie_decayed | TokenAmount$ TwiceNumTimes +SVar:NumTimes:Number$0 +SVar:TwiceNumTimes:SVar$NumTimes/Twice +DeckHas:Ability$Token & Ability$Counters +Oracle:Deathtouch\nWhen Tainted Adversary enters the battlefield, you may pay {2}{B} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Tainted Adversary, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 4c97ebe0c83..a396aef000d 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1356,6 +1356,7 @@ lblErrorPleaseCheckID=Fehler: Prüfe IDs und ob sie durch Leerzeichen und/oder K lblErrorEntityWithId=Fehler: Objekt mit ID lblNotFound=nicht gefunden lblChooseAnnounceForCard=Wähle {0} für {1} +lblHowManyTimesToPay=How many times do you want to pay {0} for {1}? lblSacrifice=Opfern lblLookCardInPlayerZone=Schaue nach Karten in {0} {1} lblPlayerZone={0} {1} diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index acb8b55aeb4..8ebdd1238cc 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1358,6 +1358,7 @@ lblErrorPleaseCheckID=Error: Check IDs and ensure they''re separated by spaces a lblErrorEntityWithId=Error: Entity with ID lblNotFound=not found lblChooseAnnounceForCard=Choose {0} for {1} +lblHowManyTimesToPay=How many times do you want to pay {0} for {1}? lblSacrifice=Sacrifice lblLookCardInPlayerZone=Looking at cards in {0} {1} lblPlayerZone={0} {1} diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index ef943ac80e2..0e5679f7692 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1356,6 +1356,7 @@ lblErrorPleaseCheckID=Error: Comprueba los IDs y asegúrate de que están separa lblErrorEntityWithId=Error: Entidad con ID lblNotFound=no encontrado lblChooseAnnounceForCard=Selecciona {0} para {1} +lblHowManyTimesToPay=¿Cuántas veces quieres pagar {0} para {1}? lblSacrifice=Sacrificio lblLookCardInPlayerZone=Mirando las cartas en {1} {0} lblPlayerZone={0} {1} diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 2af8a634cce..5b0275db731 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1355,7 +1355,8 @@ lblRestartingActionSequence=Riavvio sequenza di azioni. lblErrorPleaseCheckID=Errore: Controlla gli ID e assicurati che siano separati da spazi e/o virgole.. lblErrorEntityWithId=Errore: Entità con ID lblNotFound=non trovato -lblChooseAnnounceForCard=Scegli{0} per {1} +lblChooseAnnounceForCard=Scegli {0} per {1} +lblHowManyTimesToPay=How many times do you want to pay {0} for {1}? lblSacrifice=Sacrifica lblLookCardInPlayerZone=Stai guardando le carte in {1} di {0} lblPlayerZone={1} di {0} diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index bb83712589c..643c20aca9a 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -1357,6 +1357,7 @@ lblErrorPleaseCheckID=エラー: IDをスペースや「,」で分けてくだ lblErrorEntityWithId=エラー: オブジェクト ID lblNotFound=が見つかりません lblChooseAnnounceForCard={1}の{0}を選ぶ +lblHowManyTimesToPay=How many times do you want to pay {0} for {1}? lblSacrifice=生け贄 lblLookCardInPlayerZone={0} {1}のカードを見る lblPlayerZone={0} {1} diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 2dab37db94e..8b3ab7e04a2 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1358,6 +1358,7 @@ lblErrorPleaseCheckID=错误:请检查ID,并确保他们之间用空格和/ lblErrorEntityWithId=错误:实体的ID lblNotFound=没有找到 lblChooseAnnounceForCard=为{1}选择{0} +lblHowManyTimesToPay=How many times do you want to pay {0} for {1}? lblSacrifice=牺牲 lblLookCardInPlayerZone=查看{0}的{1}中的牌 lblPlayerZone={0}的{1} diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index c4c20bfd096..d0cd893146e 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -237,7 +237,7 @@ public class HumanPlaySpellAbility { } private boolean announceValuesLikeX() { - if (ability.isCopied()) { return true; } //don't re-announce for spell copies + if (ability.isCopied() || ability.isWrapper()) { return true; } //don't re-announce for spell copies boolean needX = true; final Cost cost = ability.getPayCosts(); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 27dd70665c9..e57e29ba23c 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -469,8 +469,13 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont return chooseNumber(ability, localizer.getMessage("lblChooseAnnounceForCard", announce, CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max); } else { - return getGui().getInteger(localizer.getMessage("lblChooseAnnounceForCard", announce, - CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max, min + 9); + if ("NumTimes".equals(announce)) { + return getGui().getInteger(localizer.getMessage("lblHowManyTimesToPay", ability.getPayCosts().getTotalMana(), + CardTranslation.getTranslatedName(ability.getHostCard().getName())), min, max, min + 9); + } else { + return getGui().getInteger(localizer.getMessage("lblChooseAnnounceForCard", announce, + CardTranslation.getTranslatedName(ability.getHostCard().getName())), min, max, min + 9); + } } }