From ed556ba2a76d596276f8accac747f7a3ab168e01 Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 27 Nov 2018 10:53:44 +0300 Subject: [PATCH 1/9] - Implemented logic for CopySpellAbilityAi. --- .../src/main/java/forge/ai/AiController.java | 45 ++++++++++++++++--- forge-ai/src/main/java/forge/ai/AiProps.java | 1 + .../java/forge/ai/ComputerUtilAbility.java | 11 +++++ .../forge/ai/ability/CopySpellAbilityAi.java | 45 +++++++++++++++++-- forge-gui/res/ai/Cautious.ai | 4 ++ forge-gui/res/ai/Default.ai | 4 ++ forge-gui/res/ai/Experimental.ai | 4 ++ forge-gui/res/ai/Reckless.ai | 4 ++ 8 files changed, 107 insertions(+), 11 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 3a6febb3944..bc24e69de07 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -1390,15 +1390,40 @@ public class AiController { } private final SpellAbility getSpellAbilityToPlay() { - // if top of stack is owned by me - if (!game.getStack().isEmpty() && game.getStack().peekAbility().getActivatingPlayer().equals(player)) { - boolean canRespondToSelf = game.getStack().peekAbility().hasParam("AIRespondsToOwnAbility"); - if (!canRespondToSelf) { - // probably should let my stuff resolve + final CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player); + List saList = Lists.newArrayList(); + + SpellAbility top = null; + if (!game.getStack().isEmpty()) { + top = game.getStack().peekAbility(); + } + final boolean topOwnedByAI = top != null && top.getActivatingPlayer().equals(player); + + if (topOwnedByAI) { + 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). + 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; } } - final CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player); if (!game.getStack().isEmpty()) { SpellAbility counter = chooseCounterSpell(getPlayableCounters(cards)); @@ -1409,7 +1434,13 @@ public class AiController { return counterETB; } - return chooseSpellAbilityToPlayFromList(ComputerUtilAbility.getSpellAbilities(cards, player), true); + if (saList.isEmpty()) { + saList = ComputerUtilAbility.getSpellAbilities(cards, player); + } + + SpellAbility chosenSa = chooseSpellAbilityToPlayFromList(saList, true); + + return chosenSa; } private SpellAbility chooseSpellAbilityToPlayFromList(final List all, boolean skipCounter) { diff --git a/forge-ai/src/main/java/forge/ai/AiProps.java b/forge-ai/src/main/java/forge/ai/AiProps.java index 50a8413dff6..dd43168d764 100644 --- a/forge-ai/src/main/java/forge/ai/AiProps.java +++ b/forge-ai/src/main/java/forge/ai/AiProps.java @@ -69,6 +69,7 @@ public enum AiProps { /** */ ALWAYS_COUNTER_PUMP_SPELLS ("true"), /** */ ALWAYS_COUNTER_AURAS ("true"), /** */ ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */ + CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK ("30"), /** */ ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */ ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */ DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */ diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java index b4c88471be3..b1df18b97ce 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilAbility.java @@ -128,6 +128,17 @@ public class ComputerUtilAbility { return tgtSA; } + public static SpellAbility getFirstCopySASpell(List spells) { + SpellAbility sa = null; + for (SpellAbility spell : spells) { + if (spell.getApi() == ApiType.CopySpellAbility) { + sa = spell; + break; + } + } + return sa; + } + public static Card getAbilitySource(SpellAbility sa) { return sa.getOriginalHost() != null ? sa.getOriginalHost() : sa.getHostCard(); } 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 55a0804a562..f3f32acf5f7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java @@ -1,10 +1,17 @@ package forge.ai.ability; +import forge.ai.AiPlayDecision; +import forge.ai.PlayerControllerAi; import forge.ai.SpecialCardAi; import forge.ai.SpellAbilityAi; +import forge.game.Game; +import forge.game.ability.ApiType; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; +import forge.game.spellability.AbilityActivated; +import forge.game.spellability.Spell; import forge.game.spellability.SpellAbility; +import forge.game.spellability.TargetRestrictions; import java.util.List; import java.util.Map; @@ -13,7 +20,39 @@ public class CopySpellAbilityAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { - // the AI should not miss mandatory activations (e.g. Precursor Golem trigger) + Game game = aiPlayer.getGame(); + if (game.getStack().isEmpty()) { + return sa.isMandatory(); // FIXME: Are mandatory activations possible in the canPlayAI code path? + } + + final TargetRestrictions tgt = sa.getTargetRestrictions(); + if (tgt != null) { + final SpellAbility top = game.getStack().peekAbility(); + if (top.getApi() == ApiType.CopySpellAbility) { + // Don't try to copy a copy ability, too complex for the AI to handle + return false; + } + + // A copy is necessary to properly test the SA before targeting the copied spell, otherwise the copy SA will fizzle. + final SpellAbility topCopy = top.copy(aiPlayer); + topCopy.resetTargets(); + + if (top.canBeTargetedBy(sa)) { + AiPlayDecision decision = AiPlayDecision.CantPlaySa; + if (top instanceof Spell) { + decision = ((PlayerControllerAi) aiPlayer.getController()).getAi().canPlayFromEffectAI((Spell) topCopy, true, true); + } else if (top instanceof AbilityActivated && top.getActivatingPlayer().equals(aiPlayer) + && "CopyActivatedAbilities".equals(sa.getParam("AILogic"))) { + decision = AiPlayDecision.WillPlay; // FIXME: we activated it once, why not again? Or bad idea? + } + if (decision == AiPlayDecision.WillPlay) { + sa.getTargets().add(top); + return true; + } + } + } + + // the AI should not miss mandatory activations return sa.isMandatory() || "Always".equals(sa.getParam("AILogic")); } @@ -25,15 +64,13 @@ public class CopySpellAbilityAi extends SpellAbilityAi { @Override public boolean chkAIDrawback(final SpellAbility sa, final Player aiPlayer) { - // NOTE: Other SAs that use CopySpellAbilityAi (e.g. Chain Lightning) are currently routed through - // generic method SpellAbilityAi#chkDrawbackWithSubs and are handled there. if ("ChainOfSmog".equals(sa.getParam("AILogic"))) { return SpecialCardAi.ChainOfSmog.consider(aiPlayer, sa); } else if ("ChainOfAcid".equals(sa.getParam("AILogic"))) { return SpecialCardAi.ChainOfAcid.consider(aiPlayer, sa); } - return super.chkAIDrawback(sa, aiPlayer); + return canPlayAI(aiPlayer, sa); } @Override diff --git a/forge-gui/res/ai/Cautious.ai b/forge-gui/res/ai/Cautious.ai index 6c765eeb1a7..5a350cd968c 100644 --- a/forge-gui/res/ai/Cautious.ai +++ b/forge-gui/res/ai/Cautious.ai @@ -133,6 +133,10 @@ ALWAYS_COUNTER_PUMP_SPELLS=false ALWAYS_COUNTER_AURAS=true ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS=None +# Copy spell on stack logic +# The chance that the AI will copy its own stack right after placing it there while it has priority +CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK=0 + # Storm spell logic PRIORITY_REDUCTION_FOR_STORM_SPELLS=9 MIN_COUNT_FOR_STORM_SPELLS=1 diff --git a/forge-gui/res/ai/Default.ai b/forge-gui/res/ai/Default.ai index e4844b9eeac..61dc79aa939 100644 --- a/forge-gui/res/ai/Default.ai +++ b/forge-gui/res/ai/Default.ai @@ -133,6 +133,10 @@ ALWAYS_COUNTER_PUMP_SPELLS=true ALWAYS_COUNTER_AURAS=true ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS=None +# Copy spell on stack logic +# The chance that the AI will copy its own stack right after placing it there while it has priority +CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK=30 + # Storm spell logic PRIORITY_REDUCTION_FOR_STORM_SPELLS=0 MIN_COUNT_FOR_STORM_SPELLS=0 diff --git a/forge-gui/res/ai/Experimental.ai b/forge-gui/res/ai/Experimental.ai index aa49163c7bb..ba82a3d6ae3 100644 --- a/forge-gui/res/ai/Experimental.ai +++ b/forge-gui/res/ai/Experimental.ai @@ -133,6 +133,10 @@ ALWAYS_COUNTER_PUMP_SPELLS=true ALWAYS_COUNTER_AURAS=true ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS=None +# Copy spell on stack logic +# The chance that the AI will copy its own stack right after placing it there while it has priority +CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK=30 + # Storm spell logic PRIORITY_REDUCTION_FOR_STORM_SPELLS=9 MIN_COUNT_FOR_STORM_SPELLS=1 diff --git a/forge-gui/res/ai/Reckless.ai b/forge-gui/res/ai/Reckless.ai index 186c1cf1c7e..b5b308473a4 100644 --- a/forge-gui/res/ai/Reckless.ai +++ b/forge-gui/res/ai/Reckless.ai @@ -133,6 +133,10 @@ ALWAYS_COUNTER_PUMP_SPELLS=true ALWAYS_COUNTER_AURAS=true ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS=None +# Copy spell on stack logic +# The chance that the AI will copy its own stack right after placing it there while it has priority +CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK=50 + # Storm spell logic PRIORITY_REDUCTION_FOR_STORM_SPELLS=0 MIN_COUNT_FOR_STORM_SPELLS=0 From 2c106e9f04b0e2cd6524f8bdbd300d37b2ba183f Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 27 Nov 2018 11:34:41 +0300 Subject: [PATCH 2/9] - Move the chance-based logic out of AiController, implement several AI logic flags for cards. --- .../src/main/java/forge/ai/AiController.java | 22 ++++------------ .../forge/ai/ability/CopySpellAbilityAi.java | 26 ++++++++++++++----- .../res/cardsfolder/i/izzet_guildmage.txt | 5 ++-- 3 files changed, 26 insertions(+), 27 deletions(-) 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. From 8b90c787aafd17026d2104e70efe58e03b712e72 Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 27 Nov 2018 12:18:17 +0300 Subject: [PATCH 3/9] - Marking the relevant cards as AI-playable. --- .../main/java/forge/ai/ability/CopySpellAbilityAi.java | 8 ++++++++ forge-gui/res/cardsfolder/d/dual_casting.txt | 1 - forge-gui/res/cardsfolder/e/echo_mage.txt | 1 - forge-gui/res/cardsfolder/f/fork.txt | 1 - forge-gui/res/cardsfolder/i/increasing_vengeance.txt | 3 +-- forge-gui/res/cardsfolder/m/meletis_charlatan.txt | 1 - forge-gui/res/cardsfolder/n/nivix_guildmage.txt | 2 +- forge-gui/res/cardsfolder/r/radiate.txt | 2 +- forge-gui/res/cardsfolder/r/refuse_cooperate.txt | 1 - forge-gui/res/cardsfolder/r/reiterate.txt | 1 - forge-gui/res/cardsfolder/r/reverberate.txt | 1 - forge-gui/res/cardsfolder/s/sigil_tracer.txt | 3 +-- forge-gui/res/cardsfolder/t/twincast.txt | 1 - forge-gui/res/cardsfolder/u/uyo_silent_prophet.txt | 2 +- 14 files changed, 13 insertions(+), 15 deletions(-) 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 12879a36a9f..8cd24cadbf2 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java @@ -36,6 +36,14 @@ public class CopySpellAbilityAi extends SpellAbilityAi { final TargetRestrictions tgt = sa.getTargetRestrictions(); if (tgt != null) { final SpellAbility top = game.getStack().peekAbility(); + + // Filter AI-specific targets if provided + if ("OnlyOwned".equals(sa.getParam("AITgts"))) { + if (!top.getActivatingPlayer().equals(aiPlayer)) { + return false; + } + } + if (top.isWrapper() || !(top instanceof SpellAbility || top instanceof AbilityActivated)) { // Should even try with triggered or wrapped abilities first, will crash return false; diff --git a/forge-gui/res/cardsfolder/d/dual_casting.txt b/forge-gui/res/cardsfolder/d/dual_casting.txt index 769e07679a5..bf905034d4e 100644 --- a/forge-gui/res/cardsfolder/d/dual_casting.txt +++ b/forge-gui/res/cardsfolder/d/dual_casting.txt @@ -5,6 +5,5 @@ K:Enchant creature A:SP$ Attach | Cost$ 1 R | ValidTgts$ Creature | AILogic$ Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddAbility$ ForkStick | Description$ Enchanted creature has "{R}, {T}: Copy target instant or sorcery spell you control. You may choose new targets for the copy." SVar:ForkStick:AB$ CopySpellAbility | Cost$ R T | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | SpellDescription$ Copy target instant or sorcery spell you control. You may choose new targets for the copy. -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/dual_casting.jpg Oracle:Enchant creature\nEnchanted creature has "{R}, {T}: Copy target instant or sorcery spell you control. You may choose new targets for the copy." diff --git a/forge-gui/res/cardsfolder/e/echo_mage.txt b/forge-gui/res/cardsfolder/e/echo_mage.txt index 3159807fef8..1cd692a4dcb 100644 --- a/forge-gui/res/cardsfolder/e/echo_mage.txt +++ b/forge-gui/res/cardsfolder/e/echo_mage.txt @@ -10,6 +10,5 @@ SVar:CopyOnce:AB$CopySpellAbility | Cost$ U U T | ValidTgts$ Instant,Sorcery | S SVar:CopyTwice:AB$CopySpellAbility | Cost$ U U T | ValidTgts$ Instant,Sorcery | Amount$ 2 | SpellDescription$ Copy target instant or sorcery spell twice. You may choose new targets for the copies. SVar:X:Count$Valid Card.Self+counters_GE2_LEVEL+counters_LT4_LEVEL SVar:Y:Count$Valid Card.Self+counters_GE4_LEVEL -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/echo_mage.jpg Oracle:Level up {1}{U} ({1}{U}: Put a level counter on this. Level up only as a sorcery.)\nLEVEL 2-3\n2/4\n{U}{U}, {T}: Copy target instant or sorcery spell. You may choose new targets for the copy.\nLEVEL 4+\n2/5\n{U}{U}, {T}: Copy target instant or sorcery spell twice. You may choose new targets for the copies. diff --git a/forge-gui/res/cardsfolder/f/fork.txt b/forge-gui/res/cardsfolder/f/fork.txt index 9a3295a242f..84dd0f13fc4 100644 --- a/forge-gui/res/cardsfolder/f/fork.txt +++ b/forge-gui/res/cardsfolder/f/fork.txt @@ -2,6 +2,5 @@ Name:Fork ManaCost:R R Types:Instant A:SP$ CopySpellAbility | Cost$ R R | ValidTgts$ Instant,Sorcery | TargetType$ Spell | CopyIsColor$ Red | OverwriteColors$ True | SpellDescription$ Copy target instant or sorcery spell, except that the copy is red. You may choose new targets for the copy. -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/fork.jpg Oracle:Copy target instant or sorcery spell, except that the copy is red. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/i/increasing_vengeance.txt b/forge-gui/res/cardsfolder/i/increasing_vengeance.txt index ccbe03d5390..ae45627bd04 100644 --- a/forge-gui/res/cardsfolder/i/increasing_vengeance.txt +++ b/forge-gui/res/cardsfolder/i/increasing_vengeance.txt @@ -3,8 +3,7 @@ ManaCost:R R Types:Instant K:Flashback:3 R R A:SP$ CopySpellAbility | Cost$ R R | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TargetType$ Spell | SubAbility$ DBCopy2 | SpellDescription$ Copy target instant or sorcery spell you control. If CARDNAME was cast from a graveyard, copy that spell twice instead. You may choose new targets for the copies. -SVar:DBCopy2:DB$ CopySpellAbility | Defined$ Targeted | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | References$ X +SVar:DBCopy2:DB$ CopySpellAbility | Defined$ Targeted | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | References$ X | AILogic$ Always SVar:X:Count$wasCastFromGraveyard.1.0 -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/increasing_vengeance.jpg Oracle:Copy target instant or sorcery spell you control. If Increasing Vengeance was cast from a graveyard, copy that spell twice instead. You may choose new targets for the copies.\nFlashback {3}{R}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.) diff --git a/forge-gui/res/cardsfolder/m/meletis_charlatan.txt b/forge-gui/res/cardsfolder/m/meletis_charlatan.txt index d7029d6c6c1..02cf1a61ceb 100644 --- a/forge-gui/res/cardsfolder/m/meletis_charlatan.txt +++ b/forge-gui/res/cardsfolder/m/meletis_charlatan.txt @@ -3,6 +3,5 @@ ManaCost:2 U Types:Creature Human Wizard PT:2/3 A:AB$ CopySpellAbility | Cost$ 2 U T | ValidTgts$ Instant,Sorcery | TargetType$ Spell | Controller$ TargetedController | SpellDescription$ The controller of target instant or sorcery spell copies it. That player may choose new targets for the copy. -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/meletis_charlatan.jpg Oracle:{2}{U}, {T}: The controller of target instant or sorcery spell copies it. That player may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/n/nivix_guildmage.txt b/forge-gui/res/cardsfolder/n/nivix_guildmage.txt index f2612353836..4d82d00d093 100644 --- a/forge-gui/res/cardsfolder/n/nivix_guildmage.txt +++ b/forge-gui/res/cardsfolder/n/nivix_guildmage.txt @@ -4,7 +4,7 @@ Types:Creature Human Wizard PT:2/2 A:AB$ Draw | Cost$ 1 U R | NumCards$ 1 | SpellDescription$ Draw a card, then discard a card. | SubAbility$ DBDiscard SVar:DBDiscard:DB$Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose -A:AB$ CopySpellAbility | Cost$ 2 U R | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TargetType$ Spell | SpellDescription$ Copy target instant or sorcery spell you control. You may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ 2 U R | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TargetType$ Spell | AILogic$ OnceIfViable | SpellDescription$ Copy target instant or sorcery spell you control. You may choose new targets for the copy. DeckHints:Type$Instant|Sorcery SVar:Picture:http://www.wizards.com/global/images/magic/general/nivix_guildmage.jpg Oracle:{1}{U}{R}: Draw a card, then discard a card.\n{2}{U}{R}: Copy target instant or sorcery spell you control. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/r/radiate.txt b/forge-gui/res/cardsfolder/r/radiate.txt index 6d3f57c4da2..808ca8df4e8 100644 --- a/forge-gui/res/cardsfolder/r/radiate.txt +++ b/forge-gui/res/cardsfolder/r/radiate.txt @@ -2,6 +2,6 @@ Name:Radiate ManaCost:3 R R Types:Instant A:SP$ CopySpellAbility | Cost$ 3 R R | ValidTgts$ Instant,Sorcery | TargetType$ Spell | TargetsSingleTarget$ True | TargetValidTargeting$ Permanent,Player | Controller$ You | CopyForEachCanTarget$ True | CanTargetPlayer$ True | SpellDescription$ Choose target instant or sorcery spell that targets only a single permanent or player. Copy that spell for each other permanent or player the spell could target. Each copy targets a different one of those permanents and players. -AI:RemoveDeck:All +AI:RemoveDeck:Random SVar:Picture:http://www.wizards.com/global/images/magic/general/radiate.jpg Oracle:Choose target instant or sorcery spell that targets only a single permanent or player. Copy that spell for each other permanent or player the spell could target. Each copy targets a different one of those permanents and players. diff --git a/forge-gui/res/cardsfolder/r/refuse_cooperate.txt b/forge-gui/res/cardsfolder/r/refuse_cooperate.txt index 3faa79eb7d1..64ac6296b4c 100644 --- a/forge-gui/res/cardsfolder/r/refuse_cooperate.txt +++ b/forge-gui/res/cardsfolder/r/refuse_cooperate.txt @@ -6,7 +6,6 @@ A:SP$ Pump | Cost$ 3 R | ValidTgts$ Card | TgtZone$ Stack | TgtPrompt$ Select ta SVar:DBDmg:DB$ DealDamage | Defined$ TargetedController | NumDmg$ X | References$ X SVar:X:Targeted$CardManaCost -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/refuse_cooperate.jpg Oracle:Refuse deals damage to target spell's controller equal to that spell's converted mana cost. diff --git a/forge-gui/res/cardsfolder/r/reiterate.txt b/forge-gui/res/cardsfolder/r/reiterate.txt index 5636ba43cc9..3fcbd34eb41 100644 --- a/forge-gui/res/cardsfolder/r/reiterate.txt +++ b/forge-gui/res/cardsfolder/r/reiterate.txt @@ -3,6 +3,5 @@ ManaCost:1 R R Types:Instant K:Buyback:3 A:SP$ CopySpellAbility | Cost$ 1 R R | ValidTgts$ Instant,Sorcery | TargetType$ Spell | SpellDescription$ Copy target instant or sorcery spell. You may choose new targets for the copy. -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/reiterate.jpg Oracle:Buyback {3} (You may pay an additional {3} as you cast this spell. If you do, put this card into your hand as it resolves.)\nCopy target instant or sorcery spell. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/r/reverberate.txt b/forge-gui/res/cardsfolder/r/reverberate.txt index 7bc69bba3cc..2cea6ba32d5 100644 --- a/forge-gui/res/cardsfolder/r/reverberate.txt +++ b/forge-gui/res/cardsfolder/r/reverberate.txt @@ -2,6 +2,5 @@ Name:Reverberate ManaCost:R R Types:Instant A:SP$ CopySpellAbility | Cost$ R R | ValidTgts$ Instant,Sorcery | TargetType$ Spell | SpellDescription$ Copy target instant or sorcery spell. You may choose new targets for the copy. -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/reverberate.jpg Oracle:Copy target instant or sorcery spell. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/s/sigil_tracer.txt b/forge-gui/res/cardsfolder/s/sigil_tracer.txt index 5cc76b81d14..1cdca3526fe 100644 --- a/forge-gui/res/cardsfolder/s/sigil_tracer.txt +++ b/forge-gui/res/cardsfolder/s/sigil_tracer.txt @@ -2,7 +2,6 @@ Name:Sigil Tracer ManaCost:1 U U Types:Creature Merfolk Wizard PT:2/2 -A:AB$CopySpellAbility | Cost$ 1 U tapXType<2/Wizard> | ValidTgts$ Instant,Sorcery | SpellDescription$ Copy target instant or sorcery spell. You may choose new targets for the copy. -AI:RemoveDeck:All +A:AB$CopySpellAbility | Cost$ 1 U tapXType<2/Wizard> | ValidTgts$ Instant,Sorcery | AILogic$ OnceIfViable | SpellDescription$ Copy target instant or sorcery spell. You may choose new targets for the copy. SVar:Picture:http://www.wizards.com/global/images/magic/general/sigil_tracer.jpg Oracle:{1}{U}, Tap two untapped Wizards you control: Copy target instant or sorcery spell. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/t/twincast.txt b/forge-gui/res/cardsfolder/t/twincast.txt index e7b413c3a05..a7f995a1afa 100644 --- a/forge-gui/res/cardsfolder/t/twincast.txt +++ b/forge-gui/res/cardsfolder/t/twincast.txt @@ -2,6 +2,5 @@ Name:Twincast ManaCost:U U Types:Instant A:SP$ CopySpellAbility | Cost$ U U | ValidTgts$ Instant,Sorcery | TargetType$ Spell | SpellDescription$ Copy target instant or sorcery spell. You may choose new targets for the copy. -AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/twincast.jpg Oracle:Copy target instant or sorcery spell. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/u/uyo_silent_prophet.txt b/forge-gui/res/cardsfolder/u/uyo_silent_prophet.txt index 1f3cdf8c376..9474f574879 100644 --- a/forge-gui/res/cardsfolder/u/uyo_silent_prophet.txt +++ b/forge-gui/res/cardsfolder/u/uyo_silent_prophet.txt @@ -4,6 +4,6 @@ Types:Legendary Creature Moonfolk Wizard PT:4/4 K:Flying A:AB$CopySpellAbility | Cost$ 2 Return<2/Land> | ValidTgts$ Instant,Sorcery | SpellDescription$ Copy target instant or sorcery spell. You may choose new targets for the copy. -AI:RemoveDeck:All +AI:RemoveDeck:Random SVar:Picture:http://www.wizards.com/global/images/magic/general/uyo_silent_prophet.jpg Oracle:Flying\n{2}, Return two lands you control to their owner's hand: Copy target instant or sorcery spell. You may choose new targets for the copy. From 06b00c2dfdb8a644a64429913b3c24006569b315 Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 27 Nov 2018 12:22:46 +0300 Subject: [PATCH 4/9] - Refuse // Cooperate is not yet playable by the AI, can't target AF Pump on stack. --- forge-gui/res/cardsfolder/r/refuse_cooperate.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/r/refuse_cooperate.txt b/forge-gui/res/cardsfolder/r/refuse_cooperate.txt index 64ac6296b4c..3faa79eb7d1 100644 --- a/forge-gui/res/cardsfolder/r/refuse_cooperate.txt +++ b/forge-gui/res/cardsfolder/r/refuse_cooperate.txt @@ -6,6 +6,7 @@ A:SP$ Pump | Cost$ 3 R | ValidTgts$ Card | TgtZone$ Stack | TgtPrompt$ Select ta SVar:DBDmg:DB$ DealDamage | Defined$ TargetedController | NumDmg$ X | References$ X SVar:X:Targeted$CardManaCost +AI:RemoveDeck:All SVar:Picture:http://www.wizards.com/global/images/magic/general/refuse_cooperate.jpg Oracle:Refuse deals damage to target spell's controller equal to that spell's converted mana cost. From c9c5a443efe85798d309577a36ed2a9c392c6087 Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 27 Nov 2018 12:43:08 +0300 Subject: [PATCH 5/9] - More work on AI logic. --- forge-ai/src/main/java/forge/ai/AiProps.java | 1 + .../forge/ai/ability/CopySpellAbilityAi.java | 21 +++++++++++++++---- forge-gui/res/ai/Cautious.ai | 2 ++ forge-gui/res/ai/Default.ai | 2 ++ forge-gui/res/ai/Experimental.ai | 2 ++ forge-gui/res/ai/Reckless.ai | 2 ++ forge-gui/res/cardsfolder/e/echo_mage.txt | 4 ++-- forge-gui/res/cardsfolder/g/geistblast.txt | 3 ++- .../res/cardsfolder/l/league_guildmage.txt | 2 +- .../res/cardsfolder/m/meletis_charlatan.txt | 2 +- forge-gui/res/cardsfolder/m/mirror_sheen.txt | 2 +- .../cardsfolder/t/tawnos_urzas_apprentice.txt | 2 +- 12 files changed, 34 insertions(+), 11 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiProps.java b/forge-ai/src/main/java/forge/ai/AiProps.java index dd43168d764..23b3b791fe6 100644 --- a/forge-ai/src/main/java/forge/ai/AiProps.java +++ b/forge-ai/src/main/java/forge/ai/AiProps.java @@ -70,6 +70,7 @@ public enum AiProps { /** */ ALWAYS_COUNTER_AURAS ("true"), /** */ ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS (""), /** */ CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK ("30"), /** */ + ALWAYS_COPY_SPELL_IF_CMC_DIFF ("2"), /** */ ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS ("true"), /** */ ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE ("false"), /** */ DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD ("2"), /** */ 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 8cd24cadbf2..68243a9489c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java @@ -19,10 +19,24 @@ public class CopySpellAbilityAi extends SpellAbilityAi { @Override protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { Game game = aiPlayer.getGame(); - final int chance = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK); + int chance = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK); + int diff = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.ALWAYS_COPY_SPELL_IF_CMC_DIFF); - if (game.getStack().isEmpty() - || (!MyRandom.percentTrue(chance) && !"AlwaysIfViable".equals(sa.getParam("AILogic"))) + if (game.getStack().isEmpty()) { + return sa.isMandatory(); + } + + final SpellAbility top = game.getStack().peekAbility(); + if (top != null + && top.getPayCosts() != null && top.getPayCosts().getCostMana() != null + && sa.getPayCosts() != null && sa.getPayCosts().getCostMana() != null + && top.getPayCosts().getCostMana().getMana().getCMC() >= sa.getPayCosts().getCostMana().getMana().getCMC() + diff) { + // The copied spell has a significantly higher CMC than the copy spell, consider copying + chance = 100; + } + + if (!MyRandom.percentTrue(chance) + && !"AlwaysIfViable".equals(sa.getParam("AILogic")) && !"OnceIfViable".equals(sa.getParam("AILogic"))) { return false; } @@ -35,7 +49,6 @@ public class CopySpellAbilityAi extends SpellAbilityAi { final TargetRestrictions tgt = sa.getTargetRestrictions(); if (tgt != null) { - final SpellAbility top = game.getStack().peekAbility(); // Filter AI-specific targets if provided if ("OnlyOwned".equals(sa.getParam("AITgts"))) { diff --git a/forge-gui/res/ai/Cautious.ai b/forge-gui/res/ai/Cautious.ai index 5a350cd968c..0e732f97963 100644 --- a/forge-gui/res/ai/Cautious.ai +++ b/forge-gui/res/ai/Cautious.ai @@ -136,6 +136,8 @@ ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS=None # Copy spell on stack logic # The chance that the AI will copy its own stack right after placing it there while it has priority CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK=0 +# If the copied spell costs this much more than the copy spell, the chance to copy the spell will become 100 +ALWAYS_COPY_SPELL_IF_CMC_DIFF=4 # Storm spell logic PRIORITY_REDUCTION_FOR_STORM_SPELLS=9 diff --git a/forge-gui/res/ai/Default.ai b/forge-gui/res/ai/Default.ai index 61dc79aa939..8e03095a670 100644 --- a/forge-gui/res/ai/Default.ai +++ b/forge-gui/res/ai/Default.ai @@ -136,6 +136,8 @@ ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS=None # Copy spell on stack logic # The chance that the AI will copy its own stack right after placing it there while it has priority CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK=30 +# If the copied spell costs this much more than the copy spell, the chance to copy the spell will become 100 +ALWAYS_COPY_SPELL_IF_CMC_DIFF=2 # Storm spell logic PRIORITY_REDUCTION_FOR_STORM_SPELLS=0 diff --git a/forge-gui/res/ai/Experimental.ai b/forge-gui/res/ai/Experimental.ai index ba82a3d6ae3..b5f8624f8a2 100644 --- a/forge-gui/res/ai/Experimental.ai +++ b/forge-gui/res/ai/Experimental.ai @@ -136,6 +136,8 @@ ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS=None # Copy spell on stack logic # The chance that the AI will copy its own stack right after placing it there while it has priority CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK=30 +# If the copied spell costs this much more than the copy spell, the chance to copy the spell will become 100 +ALWAYS_COPY_SPELL_IF_CMC_DIFF=2 # Storm spell logic PRIORITY_REDUCTION_FOR_STORM_SPELLS=9 diff --git a/forge-gui/res/ai/Reckless.ai b/forge-gui/res/ai/Reckless.ai index b5b308473a4..188cf8199a9 100644 --- a/forge-gui/res/ai/Reckless.ai +++ b/forge-gui/res/ai/Reckless.ai @@ -136,6 +136,8 @@ ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS=None # Copy spell on stack logic # The chance that the AI will copy its own stack right after placing it there while it has priority CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK=50 +# If the copied spell costs this much more than the copy spell, the chance to copy the spell will become 100 +ALWAYS_COPY_SPELL_IF_CMC_DIFF=1 # Storm spell logic PRIORITY_REDUCTION_FOR_STORM_SPELLS=0 diff --git a/forge-gui/res/cardsfolder/e/echo_mage.txt b/forge-gui/res/cardsfolder/e/echo_mage.txt index 1cd692a4dcb..755a94da0ba 100644 --- a/forge-gui/res/cardsfolder/e/echo_mage.txt +++ b/forge-gui/res/cardsfolder/e/echo_mage.txt @@ -6,8 +6,8 @@ K:Level up:1 U SVar:maxLevel:4 S:Mode$ Continuous | Affected$ Card.Self | SetPower$ 2 | SetToughness$ 4 | AddAbility$ CopyOnce | CheckSVar$ X | SVarCompare$ EQ1 | Description$ LEVEL 2-3 2/4 CARDNAME gets U U,tap: Copy target instant or sorcery spell. You may choose new targets for the copy. S:Mode$ Continuous | Affected$ Card.Self | SetPower$ 2 | SetToughness$ 5 | AddAbility$ CopyTwice | CheckSVar$ Y | SVarCompare$ EQ1 | Description$ LEVEL 4+ 2/5 CARDNAME gets U U,tap:Copy target instant or sorcery spell twice. You may choose new targets for the copies. -SVar:CopyOnce:AB$CopySpellAbility | Cost$ U U T | ValidTgts$ Instant,Sorcery | SpellDescription$ Copy target instant or sorcery spell. You may choose new targets for the copy. -SVar:CopyTwice:AB$CopySpellAbility | Cost$ U U T | ValidTgts$ Instant,Sorcery | Amount$ 2 | SpellDescription$ Copy target instant or sorcery spell twice. You may choose new targets for the copies. +SVar:CopyOnce:AB$CopySpellAbility | Cost$ U U T | ValidTgts$ Instant,Sorcery | AILogic$ OnceIfViable | SpellDescription$ Copy target instant or sorcery spell. You may choose new targets for the copy. +SVar:CopyTwice:AB$CopySpellAbility | Cost$ U U T | ValidTgts$ Instant,Sorcery | Amount$ 2 | AILogic$ OnceIfViable | SpellDescription$ Copy target instant or sorcery spell twice. You may choose new targets for the copies. SVar:X:Count$Valid Card.Self+counters_GE2_LEVEL+counters_LT4_LEVEL SVar:Y:Count$Valid Card.Self+counters_GE4_LEVEL SVar:Picture:http://www.wizards.com/global/images/magic/general/echo_mage.jpg diff --git a/forge-gui/res/cardsfolder/g/geistblast.txt b/forge-gui/res/cardsfolder/g/geistblast.txt index f9771cd0695..4be13b50adf 100644 --- a/forge-gui/res/cardsfolder/g/geistblast.txt +++ b/forge-gui/res/cardsfolder/g/geistblast.txt @@ -2,6 +2,7 @@ Name:Geistblast ManaCost:2 R Types:Instant A:SP$ DealDamage | Cost$ 2 R | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to any target. -A:AB$ CopySpellAbility | Cost$ 2 U ExileFromGrave<1/CARDNAME> | ActivationZone$ Graveyard | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TargetType$ Spell | SpellDescription$ Copy target instant or sorcery spell you control. You may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ 2 U ExileFromGrave<1/CARDNAME> | ActivationZone$ Graveyard | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TargetType$ Spell | SpellDescription$ Copy target instant or sorcery spell you control. You may choose new targets for the copy. +SVar:AIPreference:ExileFromGraveCost$Card.Self SVar:Picture:http://www.wizards.com/global/images/magic/general/geistblast.jpg Oracle:Geistblast deals 2 damage to any target.\n{2}{U}, Exile Geistblast from your graveyard: Copy target instant or sorcery spell you control. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/l/league_guildmage.txt b/forge-gui/res/cardsfolder/l/league_guildmage.txt index c0cdc4c664c..d8b6669129b 100644 --- a/forge-gui/res/cardsfolder/l/league_guildmage.txt +++ b/forge-gui/res/cardsfolder/l/league_guildmage.txt @@ -3,7 +3,7 @@ ManaCost:U R Types:Creature Human Wizard PT:2/2 A:AB$ Draw | Cost$ 3 U T | NumCards$ 1 | SpellDescription$ Draw a card. -A:AB$ CopySpellAbility | Cost$ X R T | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TargetType$ Spell | SpellDescription$ Copy target instant or sorcery spell you control with converted mana cost X. You may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ X R T | ValidTgts$ Instant.YouCtrl,Sorcery.YouCtrl | TargetType$ Spell | AILogic$ OnceIfViable | SpellDescription$ Copy target instant or sorcery spell you control with converted mana cost X. You may choose new targets for the copy. SVar:X:Targeted$CardManaCost DeckHints:Type$Instant|Sorcery Oracle:{3}{U}, {T}: Draw a card.\n{X}{R}, {T}: Copy target instant or sorcery spell you control with converted mana cost X. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/m/meletis_charlatan.txt b/forge-gui/res/cardsfolder/m/meletis_charlatan.txt index 02cf1a61ceb..debe3fbbc08 100644 --- a/forge-gui/res/cardsfolder/m/meletis_charlatan.txt +++ b/forge-gui/res/cardsfolder/m/meletis_charlatan.txt @@ -2,6 +2,6 @@ Name:Meletis Charlatan ManaCost:2 U Types:Creature Human Wizard PT:2/3 -A:AB$ CopySpellAbility | Cost$ 2 U T | ValidTgts$ Instant,Sorcery | TargetType$ Spell | Controller$ TargetedController | SpellDescription$ The controller of target instant or sorcery spell copies it. That player may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ 2 U T | ValidTgts$ Instant,Sorcery | TargetType$ Spell | Controller$ TargetedController | AILogic$ OnceIfViable | AITgts$ OwnedOnly | SpellDescription$ The controller of target instant or sorcery spell copies it. That player may choose new targets for the copy. SVar:Picture:http://www.wizards.com/global/images/magic/general/meletis_charlatan.jpg Oracle:{2}{U}, {T}: The controller of target instant or sorcery spell copies it. That player may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/m/mirror_sheen.txt b/forge-gui/res/cardsfolder/m/mirror_sheen.txt index 4cf65e9c34c..490e40e5ba2 100644 --- a/forge-gui/res/cardsfolder/m/mirror_sheen.txt +++ b/forge-gui/res/cardsfolder/m/mirror_sheen.txt @@ -1,6 +1,6 @@ Name:Mirror Sheen ManaCost:1 UR UR Types:Enchantment -A:AB$ CopySpellAbility | Cost$ 1 UR UR | ValidTgts$ Instant,Sorcery | TargetType$ Spell | TargetValidTargeting$ You | SpellDescription$ Copy target instant or sorcery spell that targets you. You may choose new targets for the copy. +A:AB$ CopySpellAbility | Cost$ 1 UR UR | ValidTgts$ Instant,Sorcery | TargetType$ Spell | TargetValidTargeting$ You | AILogic$ AlwaysIfViable | SpellDescription$ Copy target instant or sorcery spell that targets you. You may choose new targets for the copy. SVar:Picture:http://www.wizards.com/global/images/magic/general/mirror_sheen.jpg Oracle:{1}{U/R}{U/R}: Copy target instant or sorcery spell that targets you. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/t/tawnos_urzas_apprentice.txt b/forge-gui/res/cardsfolder/t/tawnos_urzas_apprentice.txt index f370baa2447..5aa503852fe 100644 --- a/forge-gui/res/cardsfolder/t/tawnos_urzas_apprentice.txt +++ b/forge-gui/res/cardsfolder/t/tawnos_urzas_apprentice.txt @@ -3,5 +3,5 @@ ManaCost:U R Types:Legendary Creature Human Artificer PT:1/3 K:Haste -A:AB$ CopySpellAbility | Cost$ U R T | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Artifact | SpellDescription$ Copy target activated or triggered ability you control from an artifact source. You may choose new targets for the copy. (Mana abilities can't be targeted.) +A:AB$ CopySpellAbility | Cost$ U R T | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Artifact | AILogic$ OnceIfViable | SpellDescription$ Copy target activated or triggered ability you control from an artifact source. You may choose new targets for the copy. (Mana abilities can't be targeted.) Oracle:Haste\n{U}{R}, {T}: Copy target activated or triggered ability you control from an artifact source. You may choose new targets for the copy. (Mana abilities can't be targeted.) From 67e6e90f368bba6d7f3d6ca21595ace9531113b1 Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 27 Nov 2018 12:50:56 +0300 Subject: [PATCH 6/9] - Observe mandatory activations when checking via chkAIDrawback. --- .../src/main/java/forge/ai/ability/CopySpellAbilityAi.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 68243a9489c..25ef17b54c9 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java @@ -103,7 +103,9 @@ public class CopySpellAbilityAi extends SpellAbilityAi { return SpecialCardAi.ChainOfAcid.consider(aiPlayer, sa); } - return canPlayAI(aiPlayer, sa); + boolean wantToPlay = canPlayAI(aiPlayer, sa); + + return wantToPlay || (sa.isMandatory() && super.chkAIDrawback(sa, aiPlayer)); } @Override From 90cb927f6c18dc4ca0ec4f6612ad7ef96c0538ce Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 27 Nov 2018 12:52:07 +0300 Subject: [PATCH 7/9] - Simpler fix for mandatory activations. --- .../src/main/java/forge/ai/ability/CopySpellAbilityAi.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 25ef17b54c9..915713d5705 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java @@ -103,9 +103,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi { return SpecialCardAi.ChainOfAcid.consider(aiPlayer, sa); } - boolean wantToPlay = canPlayAI(aiPlayer, sa); - - return wantToPlay || (sa.isMandatory() && super.chkAIDrawback(sa, aiPlayer)); + return canPlayAI(aiPlayer, sa) || (sa.isMandatory() && super.chkAIDrawback(sa, aiPlayer)); } @Override From 1ad132e0a32ab5e7e75705c0b9aab21e06f72809 Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 27 Nov 2018 12:57:02 +0300 Subject: [PATCH 8/9] - More work on AI logic, fixed Tawnos, Urza's Apprentice. --- .../java/forge/ai/ability/CopySpellAbilityAi.java | 12 +++++++----- .../res/cardsfolder/t/tawnos_urzas_apprentice.txt | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) 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 915713d5705..872f4f7d38f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java @@ -21,6 +21,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi { Game game = aiPlayer.getGame(); int chance = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK); int diff = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.ALWAYS_COPY_SPELL_IF_CMC_DIFF); + String logic = sa.getParamOrDefault("AILogic", ""); if (game.getStack().isEmpty()) { return sa.isMandatory(); @@ -36,12 +37,13 @@ public class CopySpellAbilityAi extends SpellAbilityAi { } if (!MyRandom.percentTrue(chance) - && !"AlwaysIfViable".equals(sa.getParam("AILogic")) - && !"OnceIfViable".equals(sa.getParam("AILogic"))) { + && !"AlwaysIfViable".equals(logic) + && !"OnceIfViable".equals(logic) + && !"AlwaysCopyActivatedAbilities".equals(logic)) { return false; } - if ("OnceIfViable".equals(sa.getParam("AILogic"))) { + if ("OnceIfViable".equals(logic)) { if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ACTIVATED_THIS_TURN)) { return false; } @@ -74,7 +76,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi { if (top instanceof Spell) { decision = ((PlayerControllerAi) aiPlayer.getController()).getAi().canPlayFromEffectAI((Spell) topCopy, true, true); } else if (top instanceof AbilityActivated && top.getActivatingPlayer().equals(aiPlayer) - && "CopyActivatedAbilities".equals(sa.getParam("AILogic"))) { + && logic.contains("CopyActivatedAbilities")) { decision = AiPlayDecision.WillPlay; // FIXME: we activated it once, why not again? Or bad idea? } if (decision == AiPlayDecision.WillPlay) { @@ -86,7 +88,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi { } // the AI should not miss mandatory activations - return sa.isMandatory() || "Always".equals(sa.getParam("AILogic")); + return sa.isMandatory() || "Always".equals(logic); } @Override diff --git a/forge-gui/res/cardsfolder/t/tawnos_urzas_apprentice.txt b/forge-gui/res/cardsfolder/t/tawnos_urzas_apprentice.txt index 5aa503852fe..ced32fa9770 100644 --- a/forge-gui/res/cardsfolder/t/tawnos_urzas_apprentice.txt +++ b/forge-gui/res/cardsfolder/t/tawnos_urzas_apprentice.txt @@ -3,5 +3,5 @@ ManaCost:U R Types:Legendary Creature Human Artificer PT:1/3 K:Haste -A:AB$ CopySpellAbility | Cost$ U R T | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Artifact | AILogic$ OnceIfViable | SpellDescription$ Copy target activated or triggered ability you control from an artifact source. You may choose new targets for the copy. (Mana abilities can't be targeted.) +A:AB$ CopySpellAbility | Cost$ U R T | TargetType$ Activated.YouCtrl,Triggered.YouCtrl | ValidTgts$ Artifact | AILogic$ AlwaysCopyActivatedAbilities | SpellDescription$ Copy target activated or triggered ability you control from an artifact source. You may choose new targets for the copy. (Mana abilities can't be targeted.) Oracle:Haste\n{U}{R}, {T}: Copy target activated or triggered ability you control from an artifact source. You may choose new targets for the copy. (Mana abilities can't be targeted.) From 937ae087d9da1e4833a0f9621725e030a2526298 Mon Sep 17 00:00:00 2001 From: Agetian Date: Tue, 27 Nov 2018 15:24:56 +0300 Subject: [PATCH 9/9] - Updating CHANGES.txt. --- forge-gui/release-files/CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/release-files/CHANGES.txt b/forge-gui/release-files/CHANGES.txt index 2a0aa537709..12ab45e9203 100644 --- a/forge-gui/release-files/CHANGES.txt +++ b/forge-gui/release-files/CHANGES.txt @@ -1,5 +1,5 @@ - AI Improvements - -A new round of AI improvements went into the game, hopefully making the game a little more interesting and exciting to play. In particular, certain improvements regarding Flash were implemented, the AI is now able to use cards which reorder the top of the library, such as Ponder or Portent, and several logic-related bugs were fixed. Also, the AI has been optimized a little, hopefully making it a bit faster, especially on mobile platforms, in situations where there are a lot of cards on the battlefield but the AI has little or nothing to do. +A new round of AI improvements went into the game, hopefully making the game a little more interesting and exciting to play. In particular, certain improvements regarding Flash were implemented, the AI is now able to use cards which reorder the top of the library, such as Ponder or Portent, as well as cards which copy spells and/or activated abilities, such as Twincast or Fork. On top of that, several logic-related bugs were fixed. Also, the AI has been optimized a little, hopefully making it a bit faster, especially on mobile platforms, in situations where there are a lot of cards on the battlefield but the AI has little or nothing to do. - Planar Conquest: Guilds of Ravnica - Cards from Guilds of Ravnica are now available on the Ravnica plane in Planar Conquest. Several events on this plane have been updated or changed to feature Guilds of Ravnica cards as well. Please note that Planar Conquest is currently only available on Android and the macOS mobile backport.