mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 02:38:02 +00:00
Fix AI casting suspended spells against Drannith Magistrate (#1098)
* Cleanup cards * Fix AI casting suspended spells against Drannith Magistrate Co-authored-by: tool4EvEr <tool4EvEr@192.168.0.59>
This commit is contained in:
@@ -250,6 +250,13 @@ public class ComputerUtil {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
|
|
||||||
|
Zone fromZone = game.getZoneOf(source);
|
||||||
|
int zonePosition = 0;
|
||||||
|
if (fromZone != null) {
|
||||||
|
zonePosition = fromZone.getCards().indexOf(source);
|
||||||
|
}
|
||||||
|
|
||||||
if (sa.isSpell() && !source.isCopiedSpell()) {
|
if (sa.isSpell() && !source.isCopiedSpell()) {
|
||||||
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
sa.setHostCard(game.getAction().moveToStack(source, sa));
|
||||||
}
|
}
|
||||||
@@ -257,11 +264,18 @@ public class ComputerUtil {
|
|||||||
sa = GameActionUtil.addExtraKeywordCost(sa);
|
sa = GameActionUtil.addExtraKeywordCost(sa);
|
||||||
|
|
||||||
final Cost cost = sa.getPayCosts();
|
final Cost cost = sa.getPayCosts();
|
||||||
|
final CostPayment pay = new CostPayment(cost, sa);
|
||||||
|
|
||||||
|
// do this after card got added to stack
|
||||||
|
if (!sa.checkRestrictions(ai)) {
|
||||||
|
GameActionUtil.rollbackAbility(sa, fromZone, zonePosition, pay, source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (cost == null) {
|
if (cost == null) {
|
||||||
ComputerUtilMana.payManaCost(ai, sa, false);
|
ComputerUtilMana.payManaCost(ai, sa, false);
|
||||||
game.getStack().add(sa);
|
game.getStack().add(sa);
|
||||||
} else {
|
} else {
|
||||||
final CostPayment pay = new CostPayment(cost, sa);
|
|
||||||
if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) {
|
if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) {
|
||||||
game.getStack().add(sa);
|
game.getStack().add(sa);
|
||||||
}
|
}
|
||||||
@@ -292,6 +306,13 @@ public class ComputerUtil {
|
|||||||
newSA = GameActionUtil.addExtraKeywordCost(newSA);
|
newSA = GameActionUtil.addExtraKeywordCost(newSA);
|
||||||
|
|
||||||
final Card source = newSA.getHostCard();
|
final Card source = newSA.getHostCard();
|
||||||
|
|
||||||
|
Zone fromZone = game.getZoneOf(source);
|
||||||
|
int zonePosition = 0;
|
||||||
|
if (fromZone != null) {
|
||||||
|
zonePosition = fromZone.getCards().indexOf(source);
|
||||||
|
}
|
||||||
|
|
||||||
if (newSA.isSpell() && !source.isCopiedSpell()) {
|
if (newSA.isSpell() && !source.isCopiedSpell()) {
|
||||||
newSA.setHostCard(game.getAction().moveToStack(source, newSA));
|
newSA.setHostCard(game.getAction().moveToStack(source, newSA));
|
||||||
|
|
||||||
@@ -303,6 +324,13 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
|
final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA);
|
||||||
|
|
||||||
|
// do this after card got added to stack
|
||||||
|
if (!sa.checkRestrictions(ai)) {
|
||||||
|
GameActionUtil.rollbackAbility(sa, fromZone, zonePosition, pay, source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
pay.payComputerCosts(new AiCostDecision(ai, newSA, false));
|
pay.payComputerCosts(new AiCostDecision(ai, newSA, false));
|
||||||
|
|
||||||
game.getStack().add(newSA);
|
game.getStack().add(newSA);
|
||||||
|
|||||||
@@ -1087,7 +1087,7 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell?
|
||||||
Spell spell = (Spell) tgtSA;
|
Spell spell = (Spell) tgtSA;
|
||||||
// TODO if mandatory AI is only forced to use mana when it's already in the pool
|
// TODO if mandatory AI is only forced to use mana when it's already in the pool
|
||||||
if (tgtSA.checkRestrictions(brains.getPlayer()) && (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional)) {
|
if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) {
|
||||||
if (noManaCost) {
|
if (noManaCost) {
|
||||||
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import forge.game.ability.AbilityUtils;
|
|||||||
import forge.game.ability.ApiType;
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.card.CardPlayOption.PayManaCost;
|
import forge.game.card.CardPlayOption.PayManaCost;
|
||||||
import forge.game.cost.Cost;
|
import forge.game.cost.Cost;
|
||||||
|
import forge.game.cost.CostPayment;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.keyword.KeywordInterface;
|
import forge.game.keyword.KeywordInterface;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
@@ -842,4 +843,46 @@ public final class GameActionUtil {
|
|||||||
c.getGame().getTriggerHandler().resetActiveTriggers();
|
c.getGame().getTriggerHandler().resetActiveTriggers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void rollbackAbility(SpellAbility ability, final Zone fromZone, final int zonePosition, CostPayment payment, Card oldCard) {
|
||||||
|
// cancel ability during target choosing
|
||||||
|
final Game game = ability.getActivatingPlayer().getGame();
|
||||||
|
|
||||||
|
if (fromZone != null) { // and not a copy
|
||||||
|
oldCard.setCastSA(null);
|
||||||
|
oldCard.setCastFrom(null);
|
||||||
|
// add back to where it came from, hopefully old state
|
||||||
|
// skip GameAction
|
||||||
|
oldCard.getZone().remove(oldCard);
|
||||||
|
fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null);
|
||||||
|
ability.setHostCard(oldCard);
|
||||||
|
ability.setXManaCostPaid(null);
|
||||||
|
ability.setSpendPhyrexianMana(false);
|
||||||
|
if (ability.hasParam("Announce")) {
|
||||||
|
for (final String aVar : ability.getParam("Announce").split(",")) {
|
||||||
|
final String varName = aVar.trim();
|
||||||
|
if (!varName.equals("X")) {
|
||||||
|
ability.setSVar(varName, "0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost)
|
||||||
|
for (SpellAbility sa : oldCard.getSpells()) {
|
||||||
|
sa.setHostCard(oldCard);
|
||||||
|
}
|
||||||
|
//for Chorus of the Conclave
|
||||||
|
ability.rollback();
|
||||||
|
|
||||||
|
oldCard.setBackSide(false);
|
||||||
|
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
||||||
|
oldCard.unanimateBestow();
|
||||||
|
}
|
||||||
|
|
||||||
|
ability.clearTargets();
|
||||||
|
|
||||||
|
ability.resetOnceResolved();
|
||||||
|
payment.refundPayment();
|
||||||
|
game.getStack().clearFrozen();
|
||||||
|
game.getTriggerHandler().clearWaitingTriggers();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ public class CountersPutEffect extends SpellAbilityEffect {
|
|||||||
// need to unfreeze tracker
|
// need to unfreeze tracker
|
||||||
game.getTracker().unfreeze();
|
game.getTracker().unfreeze();
|
||||||
|
|
||||||
// check if it can recive the Tribute
|
// check if it can receive the Tribute
|
||||||
if (abort) {
|
if (abort) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ Name:Djeru's Resolve
|
|||||||
ManaCost:W
|
ManaCost:W
|
||||||
Types:Instant
|
Types:Instant
|
||||||
A:SP$ Untap | Cost$ W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBPump | SpellDescription$ Untap target creature. Prevent all damage that would be dealt to it this turn.
|
A:SP$ Untap | Cost$ W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBPump | SpellDescription$ Untap target creature. Prevent all damage that would be dealt to it this turn.
|
||||||
SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Prevent all damage that would be dealt to CARDNAME.
|
SVar:DBPump:DB$ Effect | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield
|
||||||
|
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
|
||||||
K:Cycling:2
|
K:Cycling:2
|
||||||
Oracle:Untap target creature. Prevent all damage that would be dealt to it this turn.\nCycling {2} ({2}, Discard this card: Draw a card.)
|
Oracle:Untap target creature. Prevent all damage that would be dealt to it this turn.\nCycling {2} ({2}, Discard this card: Draw a card.)
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ Name:Godtoucher
|
|||||||
ManaCost:3 G
|
ManaCost:3 G
|
||||||
Types:Creature Elf Cleric
|
Types:Creature Elf Cleric
|
||||||
PT:2/2
|
PT:2/2
|
||||||
A:AB$ Pump | Cost$ 1 W T | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature.powerGE5 | TgtPrompt$ Select target creature with power 5 or greater | SpellDescription$ Prevent all damage that would be dealt to target creature with power 5 or greater this turn.
|
A:AB$ Effect | Cost$ 1 W T | ValidTgts$ Creature.powerGE5 | TgtPrompt$ Select target creature with power 5 or greater | ReplacementEffects$ RPrevent| RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature with power 5 or greater this turn.
|
||||||
|
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:All
|
||||||
AI:RemoveDeck:Random
|
AI:RemoveDeck:Random
|
||||||
Oracle:{1}{W}, {T}: Prevent all damage that would be dealt to target creature with power 5 or greater this turn.
|
Oracle:{1}{W}, {T}: Prevent all damage that would be dealt to target creature with power 5 or greater this turn.
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ ManaCost:2 W
|
|||||||
Types:Enchantment Creature Nymph
|
Types:Enchantment Creature Nymph
|
||||||
PT:2/3
|
PT:2/3
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self,Enchantment.Other+YouCtrl | Execute$ TrigPump | TriggerDescription$ Constellation — Whenever CARDNAME or another enchantment enters the battlefield under your control, prevent all damage that would be dealt to target creature this turn.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self,Enchantment.Other+YouCtrl | Execute$ TrigPump | TriggerDescription$ Constellation — Whenever CARDNAME or another enchantment enters the battlefield under your control, prevent all damage that would be dealt to target creature this turn.
|
||||||
SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Prevent all damage that would be dealt to CARDNAME.
|
SVar:TrigPump:DB$ Effect | Cost$ W | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield
|
||||||
|
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
|
||||||
SVar:PlayMain1:TRUE
|
SVar:PlayMain1:TRUE
|
||||||
SVar:BuffedBy:Enchantment
|
SVar:BuffedBy:Enchantment
|
||||||
Oracle:Constellation — Whenever Harvestguard Alseids or another enchantment enters the battlefield under your control, prevent all damage that would be dealt to target creature this turn.
|
Oracle:Constellation — Whenever Harvestguard Alseids or another enchantment enters the battlefield under your control, prevent all damage that would be dealt to target creature this turn.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
Name:Indestructible Aura
|
Name:Indestructible Aura
|
||||||
ManaCost:W
|
ManaCost:W
|
||||||
Types:Instant
|
Types:Instant
|
||||||
A:SP$ Pump | Cost$ W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Prevent all damage that would be dealt to target creature this turn.
|
A:SP$ Effect | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature this turn.
|
||||||
|
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:All
|
||||||
Oracle:Prevent all damage that would be dealt to target creature this turn.
|
Oracle:Prevent all damage that would be dealt to target creature this turn.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Types:Creature Cyclops
|
|||||||
PT:4/3
|
PT:4/3
|
||||||
K:Defender
|
K:Defender
|
||||||
S:Mode$ CanAttackDefender | ValidCard$ Card.Self | CheckSVar$ X | Description$ As long as you've cast an instant or sorcery spell this turn, CARDNAME can attack as though it didn't have defender.
|
S:Mode$ CanAttackDefender | ValidCard$ Card.Self | CheckSVar$ X | Description$ As long as you've cast an instant or sorcery spell this turn, CARDNAME can attack as though it didn't have defender.
|
||||||
SVar:X:Count$ThisTurnCast_Instant.YouOwn,Sorcery.YouOwn
|
SVar:X:Count$ThisTurnCast_Instant.YouCtrl,Sorcery.YouCtrl
|
||||||
SVar:BuffedBy:Instant,Sorcery
|
SVar:BuffedBy:Instant,Sorcery
|
||||||
DeckHints:Type$Instant|Sorcery
|
DeckHints:Type$Instant|Sorcery
|
||||||
Oracle:Defender\nAs long as you've cast an instant or sorcery spell this turn, Piston-Fist Cyclops can attack as though it didn't have defender.
|
Oracle:Defender\nAs long as you've cast an instant or sorcery spell this turn, Piston-Fist Cyclops can attack as though it didn't have defender.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
Name:Shielded Passage
|
Name:Shielded Passage
|
||||||
ManaCost:W
|
ManaCost:W
|
||||||
Types:Instant
|
Types:Instant
|
||||||
A:SP$ Pump | Cost$ W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Prevent all damage that would be dealt to target creature this turn.
|
A:SP$ Effect | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature this turn.
|
||||||
|
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
|
||||||
AI:RemoveDeck:All
|
AI:RemoveDeck:All
|
||||||
Oracle:Prevent all damage that would be dealt to target creature this turn.
|
Oracle:Prevent all damage that would be dealt to target creature this turn.
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ Name:Wellgabber Apothecary
|
|||||||
ManaCost:4 W
|
ManaCost:4 W
|
||||||
Types:Creature Merfolk Cleric
|
Types:Creature Merfolk Cleric
|
||||||
PT:2/3
|
PT:2/3
|
||||||
A:AB$ Pump | Cost$ 1 W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature.Merfolk+tapped,Creature.Kithkin+tapped | TgtPrompt$ Select tapped Merfolk or Kithkin creature | SpellDescription$ Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn.
|
A:SP$ Effect | Cost$ 1 W | ValidTgts$ Creature.Merfolk+tapped,Creature.Kithkin+tapped | TgtPrompt$ Select tapped Merfolk or Kithkin creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn.
|
||||||
|
SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn.
|
||||||
Oracle:{1}{W}: Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn.
|
Oracle:{1}{W}: Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn.
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ public class HumanPlaySpellAbility {
|
|||||||
|
|
||||||
if (!prerequisitesMet) {
|
if (!prerequisitesMet) {
|
||||||
if (!ability.isTrigger()) {
|
if (!ability.isTrigger()) {
|
||||||
rollbackAbility(fromZone, zonePosition, payment, c);
|
GameActionUtil.rollbackAbility(ability, fromZone, zonePosition, payment, c);
|
||||||
if (ability.getHostCard().isMadness()) {
|
if (ability.getHostCard().isMadness()) {
|
||||||
// if a player failed to play madness cost, move the card to graveyard
|
// if a player failed to play madness cost, move the card to graveyard
|
||||||
Card newCard = game.getAction().moveToGraveyard(c, null);
|
Card newCard = game.getAction().moveToGraveyard(c, null);
|
||||||
@@ -209,48 +209,6 @@ public class HumanPlaySpellAbility {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rollbackAbility(final Zone fromZone, final int zonePosition, CostPayment payment, Card oldCard) {
|
|
||||||
// cancel ability during target choosing
|
|
||||||
final Game game = ability.getActivatingPlayer().getGame();
|
|
||||||
|
|
||||||
if (fromZone != null) { // and not a copy
|
|
||||||
oldCard.setCastSA(null);
|
|
||||||
oldCard.setCastFrom(null);
|
|
||||||
// add back to where it came from, hopefully old state
|
|
||||||
// skip GameAction
|
|
||||||
oldCard.getZone().remove(oldCard);
|
|
||||||
fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null);
|
|
||||||
ability.setHostCard(oldCard);
|
|
||||||
ability.setXManaCostPaid(null);
|
|
||||||
ability.setSpendPhyrexianMana(false);
|
|
||||||
if (ability.hasParam("Announce")) {
|
|
||||||
for (final String aVar : ability.getParam("Announce").split(",")) {
|
|
||||||
final String varName = aVar.trim();
|
|
||||||
if (!varName.equals("X")) {
|
|
||||||
ability.setSVar(varName, "0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost)
|
|
||||||
for (SpellAbility sa : oldCard.getSpells()) {
|
|
||||||
sa.setHostCard(oldCard);
|
|
||||||
}
|
|
||||||
//for Chorus of the Conclave
|
|
||||||
ability.rollback();
|
|
||||||
|
|
||||||
oldCard.setBackSide(false);
|
|
||||||
oldCard.setState(oldCard.getFaceupCardStateName(), true);
|
|
||||||
oldCard.unanimateBestow();
|
|
||||||
}
|
|
||||||
|
|
||||||
ability.clearTargets();
|
|
||||||
|
|
||||||
ability.resetOnceResolved();
|
|
||||||
payment.refundPayment();
|
|
||||||
game.getStack().clearFrozen();
|
|
||||||
game.getTriggerHandler().clearWaitingTriggers();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean announceValuesLikeX() {
|
private boolean announceValuesLikeX() {
|
||||||
if (ability.isCopied() || ability.isWrapper()) { return true; } //don't re-announce for spell copies
|
if (ability.isCopied() || ability.isWrapper()) { return true; } //don't re-announce for spell copies
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user