diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index bc24e69de07..e864ce31dcd 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -1400,29 +1400,17 @@ public class AiController { final boolean topOwnedByAI = top != null && top.getActivatingPlayer().equals(player); if (topOwnedByAI) { + // AI's own spell: should probably let my stuff resolve first, but may want to copy the SA or respond to it + // in a scripted timed fashion. final boolean mustRespond = top.hasParam("AIRespondsToOwnAbility"); - final int chanceToCopy = getIntProperty(AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK); if (!mustRespond) { - if (chanceToCopy == 0) { - // The AI profile asks not to respond to own spells on stack (MustRespond is a script-defined exception). + saList = ComputerUtilAbility.getSpellAbilities(cards, player); // get the SA list early to check for copy SAs + if (ComputerUtilAbility.getFirstCopySASpell(saList) == null) { + // Nothing to copy the spell with, so do nothing. return null; - } else { - saList = ComputerUtilAbility.getSpellAbilities(cards, player); // get the SA list early to check for copy SAs - if (ComputerUtilAbility.getFirstCopySASpell(saList) == null) { - // The AI currently responds to its own spell on stack only with copy spells (e.g. Twincast). - // If there are none, don't respond. - return null; - } } } - - // if top of the stack is owned by me, probably should let my stuff resolve unless I want to copy my spell on stack - // or if there are special considerations (e.g. scripted response for Sensei's Divining Top) - boolean wantToRespondToStack = mustRespond || MyRandom.percentTrue(chanceToCopy); - if (!wantToRespondToStack) { - return null; - } } if (!game.getStack().isEmpty()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java index f3f32acf5f7..12879a36a9f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java @@ -1,9 +1,6 @@ package forge.ai.ability; -import forge.ai.AiPlayDecision; -import forge.ai.PlayerControllerAi; -import forge.ai.SpecialCardAi; -import forge.ai.SpellAbilityAi; +import forge.ai.*; import forge.game.Game; import forge.game.ability.ApiType; import forge.game.player.Player; @@ -12,6 +9,7 @@ import forge.game.spellability.AbilityActivated; import forge.game.spellability.Spell; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; +import forge.util.MyRandom; import java.util.List; import java.util.Map; @@ -21,14 +19,27 @@ public class CopySpellAbilityAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { Game game = aiPlayer.getGame(); - if (game.getStack().isEmpty()) { - return sa.isMandatory(); // FIXME: Are mandatory activations possible in the canPlayAI code path? + final int chance = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK); + + if (game.getStack().isEmpty() + || (!MyRandom.percentTrue(chance) && !"AlwaysIfViable".equals(sa.getParam("AILogic"))) + && !"OnceIfViable".equals(sa.getParam("AILogic"))) { + return false; + } + + if ("OnceIfViable".equals(sa.getParam("AILogic"))) { + if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) { + return false; + } } final TargetRestrictions tgt = sa.getTargetRestrictions(); if (tgt != null) { final SpellAbility top = game.getStack().peekAbility(); - if (top.getApi() == ApiType.CopySpellAbility) { + if (top.isWrapper() || !(top instanceof SpellAbility || top instanceof AbilityActivated)) { + // Should even try with triggered or wrapped abilities first, will crash + return false; + } else if (top.getApi() == ApiType.CopySpellAbility) { // Don't try to copy a copy ability, too complex for the AI to handle return false; } @@ -47,6 +58,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi { } if (decision == AiPlayDecision.WillPlay) { sa.getTargets().add(top); + AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ACTIVATED_THIS_TURN); return true; } } diff --git a/forge-gui/res/cardsfolder/i/izzet_guildmage.txt b/forge-gui/res/cardsfolder/i/izzet_guildmage.txt index 963e1177204..39c32abfe50 100644 --- a/forge-gui/res/cardsfolder/i/izzet_guildmage.txt +++ b/forge-gui/res/cardsfolder/i/izzet_guildmage.txt @@ -2,8 +2,7 @@ Name:Izzet Guildmage ManaCost:UR UR Types:Creature Human Wizard PT:2/2 -A:AB$ CopySpellAbility | Cost$ 2 U | ValidTgts$ Instant.YouCtrl+cmcLE2 | TargetType$ Spell | SpellDescription$ Copy target instant spell you control with converted mana cost 2 or less. You may choose new targets for the copy. -A:AB$ CopySpellAbility | Cost$ 2 R | ValidTgts$ Sorcery.YouCtrl+cmcLE2 | TargetType$ Spell | SpellDescription$ Copy target sorcery spell you control with converted mana cost 2 or less. You may choose new targets for the copy. -AI:RemoveDeck:All +A:AB$ CopySpellAbility | Cost$ 2 U | ValidTgts$ Instant.YouCtrl+cmcLE2 | TargetType$ Spell | AILogic$ OnceIfViable | SpellDescription$ Copy target instant spell you control with converted mana cost 2 or less. You may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ 2 R | ValidTgts$ Sorcery.YouCtrl+cmcLE2 | TargetType$ Spell | AILogic$ OnceIfViable | SpellDescription$ Copy target sorcery spell you control with converted mana cost 2 or less. You may choose new targets for the copy. SVar:Picture:http://www.wizards.com/global/images/magic/general/izzet_guildmage.jpg Oracle:{2}{U}: Copy target instant spell you control with converted mana cost 2 or less. You may choose new targets for the copy.\n{2}{R}: Copy target sorcery spell you control with converted mana cost 2 or less. You may choose new targets for the copy.