Merge branch 'adversary' into 'master'

MID – Adversary cycle

See merge request core-developers/forge!5339
This commit is contained in:
Michael Kamensky
2021-09-21 04:35:13 +00:00
16 changed files with 117 additions and 12 deletions

View File

@@ -1,11 +1,8 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.AiController; import forge.ai.*;
import forge.ai.AiPlayDecision; import forge.card.mana.ManaCost;
import forge.ai.ComputerUtilCost; import forge.game.mana.ManaCostBeingPaid;
import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.ai.SpellApiToAi;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -47,6 +44,19 @@ public class ImmediateTriggerAi extends SpellAbilityAi {
sa.setXManaCostPaid(ComputerUtilCost.getMaxXValue(sa, ai)); 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(); AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
trigsa.setActivatingPlayer(ai); trigsa.setActivatingPlayer(ai);

View File

@@ -321,6 +321,8 @@ public enum CounterEnumType {
UNITY("UNITY", 242, 156, 255), UNITY("UNITY", 242, 156, 255),
VALOR("VALOR", 252, 250, 222),
VELOCITY("VELO", 255, 95, 138), VELOCITY("VELO", 255, 95, 138),
VERSE("VERSE", 0, 237, 155), VERSE("VERSE", 0, 237, 155),

View File

@@ -18,7 +18,9 @@
package forge.game.cost; package forge.game.cost;
import forge.card.mana.ManaCost; import forge.card.mana.ManaCost;
import forge.game.ability.AbilityUtils;
import forge.game.mana.ManaConversionMatrix; import forge.game.mana.ManaConversionMatrix;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -35,6 +37,7 @@ public class CostPartMana extends CostPart {
private boolean xCantBe0 = false; private boolean xCantBe0 = false;
private boolean isExiledCreatureCost = false; private boolean isExiledCreatureCost = false;
private boolean isEnchantedCreatureCost = false; private boolean isEnchantedCreatureCost = false;
private boolean isCostPayAnyNumberOfTimes = false;
private final String restriction; private final String restriction;
private ManaConversionMatrix cardMatrix = null; private ManaConversionMatrix cardMatrix = null;
@@ -53,7 +56,8 @@ public class CostPartMana extends CostPart {
this.xCantBe0 = "XCantBe0".equals(restriction); this.xCantBe0 = "XCantBe0".equals(restriction);
this.isExiledCreatureCost = "Exiled".equalsIgnoreCase(restriction); this.isExiledCreatureCost = "Exiled".equalsIgnoreCase(restriction);
this.isEnchantedCreatureCost = "EnchantedCost".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 // 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.xCantBe0 = XCantBe0;
this.isExiledCreatureCost = exiledCreatureCost; this.isExiledCreatureCost = exiledCreatureCost;
this.isEnchantedCreatureCost = enchantedCreatureCost; 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) { if (isEnchantedCreatureCost() && sa.getHostCard().getEnchantingCard() != null) {
return sa.getHostCard().getEnchantingCard().getManaCost(); 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(); return getManaToPay();
} }

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.)

View File

@@ -1356,6 +1356,7 @@ lblErrorPleaseCheckID=Fehler: Prüfe IDs und ob sie durch Leerzeichen und/oder K
lblErrorEntityWithId=Fehler: Objekt mit ID lblErrorEntityWithId=Fehler: Objekt mit ID
lblNotFound=nicht gefunden lblNotFound=nicht gefunden
lblChooseAnnounceForCard=Wähle {0} für {1} lblChooseAnnounceForCard=Wähle {0} für {1}
lblHowManyTimesToPay=How many times do you want to pay {0} for {1}?
lblSacrifice=Opfern lblSacrifice=Opfern
lblLookCardInPlayerZone=Schaue nach Karten in {0} {1} lblLookCardInPlayerZone=Schaue nach Karten in {0} {1}
lblPlayerZone={0} {1} lblPlayerZone={0} {1}

View File

@@ -1358,6 +1358,7 @@ lblErrorPleaseCheckID=Error: Check IDs and ensure they''re separated by spaces a
lblErrorEntityWithId=Error: Entity with ID lblErrorEntityWithId=Error: Entity with ID
lblNotFound=not found lblNotFound=not found
lblChooseAnnounceForCard=Choose {0} for {1} lblChooseAnnounceForCard=Choose {0} for {1}
lblHowManyTimesToPay=How many times do you want to pay {0} for {1}?
lblSacrifice=Sacrifice lblSacrifice=Sacrifice
lblLookCardInPlayerZone=Looking at cards in {0} {1} lblLookCardInPlayerZone=Looking at cards in {0} {1}
lblPlayerZone={0} {1} lblPlayerZone={0} {1}

View File

@@ -1356,6 +1356,7 @@ lblErrorPleaseCheckID=Error: Comprueba los IDs y asegúrate de que están separa
lblErrorEntityWithId=Error: Entidad con ID lblErrorEntityWithId=Error: Entidad con ID
lblNotFound=no encontrado lblNotFound=no encontrado
lblChooseAnnounceForCard=Selecciona {0} para {1} lblChooseAnnounceForCard=Selecciona {0} para {1}
lblHowManyTimesToPay=¿Cuántas veces quieres pagar {0} para {1}?
lblSacrifice=Sacrificio lblSacrifice=Sacrificio
lblLookCardInPlayerZone=Mirando las cartas en {1} {0} lblLookCardInPlayerZone=Mirando las cartas en {1} {0}
lblPlayerZone={0} {1} lblPlayerZone={0} {1}

View File

@@ -1355,7 +1355,8 @@ lblRestartingActionSequence=Riavvio sequenza di azioni.
lblErrorPleaseCheckID=Errore: Controlla gli ID e assicurati che siano separati da spazi e/o virgole.. lblErrorPleaseCheckID=Errore: Controlla gli ID e assicurati che siano separati da spazi e/o virgole..
lblErrorEntityWithId=Errore: Entità con ID lblErrorEntityWithId=Errore: Entità con ID
lblNotFound=non trovato 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 lblSacrifice=Sacrifica
lblLookCardInPlayerZone=Stai guardando le carte in {1} di {0} lblLookCardInPlayerZone=Stai guardando le carte in {1} di {0}
lblPlayerZone={1} di {0} lblPlayerZone={1} di {0}

View File

@@ -1357,6 +1357,7 @@ lblErrorPleaseCheckID=エラー: IDをスペースや「,」で分けてくだ
lblErrorEntityWithId=エラー: オブジェクト ID lblErrorEntityWithId=エラー: オブジェクト ID
lblNotFound=が見つかりません lblNotFound=が見つかりません
lblChooseAnnounceForCard={1}の{0}を選ぶ lblChooseAnnounceForCard={1}の{0}を選ぶ
lblHowManyTimesToPay=How many times do you want to pay {0} for {1}?
lblSacrifice=生け贄 lblSacrifice=生け贄
lblLookCardInPlayerZone={0} {1}のカードを見る lblLookCardInPlayerZone={0} {1}のカードを見る
lblPlayerZone={0} {1} lblPlayerZone={0} {1}

View File

@@ -1358,6 +1358,7 @@ lblErrorPleaseCheckID=错误请检查ID并确保他们之间用空格和/
lblErrorEntityWithId=错误实体的ID lblErrorEntityWithId=错误实体的ID
lblNotFound=没有找到 lblNotFound=没有找到
lblChooseAnnounceForCard=为{1}选择{0} lblChooseAnnounceForCard=为{1}选择{0}
lblHowManyTimesToPay=How many times do you want to pay {0} for {1}?
lblSacrifice=牺牲 lblSacrifice=牺牲
lblLookCardInPlayerZone=查看{0}的{1}中的牌 lblLookCardInPlayerZone=查看{0}的{1}中的牌
lblPlayerZone={0}的{1} lblPlayerZone={0}的{1}

View File

@@ -237,7 +237,7 @@ public class HumanPlaySpellAbility {
} }
private boolean announceValuesLikeX() { 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; boolean needX = true;
final Cost cost = ability.getPayCosts(); final Cost cost = ability.getPayCosts();

View File

@@ -469,8 +469,13 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont
return chooseNumber(ability, localizer.getMessage("lblChooseAnnounceForCard", announce, return chooseNumber(ability, localizer.getMessage("lblChooseAnnounceForCard", announce,
CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max); CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max);
} else { } else {
return getGui().getInteger(localizer.getMessage("lblChooseAnnounceForCard", announce, if ("NumTimes".equals(announce)) {
CardTranslation.getTranslatedName(ability.getHostCard().getName())) , min, max, min + 9); 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);
}
} }
} }