Merge branch 'moraug' into 'master'

Moraug: improve AI

See merge request core-developers/forge!5878
This commit is contained in:
Michael Kamensky
2021-11-25 12:14:25 +00:00
20 changed files with 35 additions and 50 deletions

View File

@@ -1622,6 +1622,7 @@ public class AiController {
Map<String, String> params = t.getMapParams(); Map<String, String> params = t.getMapParams();
if ("ChangesZone".equals(params.get("Mode")) if ("ChangesZone".equals(params.get("Mode"))
&& params.containsKey("ValidCard") && params.containsKey("ValidCard")
&& (!params.containsKey("AILogic") || !params.get("AILogic").equals("SafeToHold"))
&& !params.get("ValidCard").contains("nonLand") && !params.get("ValidCard").contains("nonLand")
&& ((params.get("ValidCard").contains("Land")) || (params.get("ValidCard").contains("Permanent"))) && ((params.get("ValidCard").contains("Land")) || (params.get("ValidCard").contains("Permanent")))
&& "Battlefield".equals(params.get("Destination"))) { && "Battlefield".equals(params.get("Destination"))) {

View File

@@ -14,8 +14,7 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
if (!sa.hasParam("AILogic")) { if (!sa.hasParam("AILogic")) {
return false; return false;
} }
TargetRestrictions tgt = sa.getTargetRestrictions(); if (sa.usesTargeting()) {
if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer); Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
@@ -34,4 +33,3 @@ public class ChooseEvenOddAi extends SpellAbilityAi {
} }
} }

View File

@@ -44,8 +44,7 @@ public class ChooseNumberAi extends SpellAbilityAi {
return ownCreatureCount > oppMaxCreatureCount + 2 || ownCreatureCount < Math.min(oppMaxCreatureCount, maxChoiceLimit); return ownCreatureCount > oppMaxCreatureCount + 2 || ownCreatureCount < Math.min(oppMaxCreatureCount, maxChoiceLimit);
} }
TargetRestrictions tgt = sa.getTargetRestrictions(); if (sa.usesTargeting()) {
if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer); Player opp = AiAttackController.choosePreferredDefenderPlayer(aiPlayer);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {

View File

@@ -52,8 +52,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
} }
} }
final TargetRestrictions tgt = sa.getTargetRestrictions(); if (sa.usesTargeting()) {
if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
Player opp = AiAttackController.choosePreferredDefenderPlayer(ai); Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {

View File

@@ -58,8 +58,7 @@ public class CopySpellAbilityAi extends SpellAbilityAi {
} }
} }
final TargetRestrictions tgt = sa.getTargetRestrictions(); if (sa.usesTargeting()) {
if (tgt != null) {
// Filter AI-specific targets if provided // Filter AI-specific targets if provided
if ("OnlyOwned".equals(sa.getParam("AITgts"))) { if ("OnlyOwned".equals(sa.getParam("AITgts"))) {
if (!top.getActivatingPlayer().equals(aiPlayer)) { if (!top.getActivatingPlayer().equals(aiPlayer)) {

View File

@@ -63,8 +63,7 @@ public class CounterAi extends SpellAbilityAi {
} }
} }
final TargetRestrictions tgt = sa.getTargetRestrictions(); if (sa.usesTargeting()) {
if (tgt != null) {
final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa); final SpellAbility topSA = ComputerUtilAbility.getTopSpellAbilityOnStack(game, sa);
if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai if (!CardFactoryUtil.isCounterableBy(topSA.getHostCard(), sa) || topSA.getActivatingPlayer() == ai
|| ai.getAllies().contains(topSA.getActivatingPlayer())) { || ai.getAllies().contains(topSA.getActivatingPlayer())) {
@@ -246,10 +245,9 @@ public class CounterAi extends SpellAbilityAi {
@Override @Override
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Game game = ai.getGame(); final Game game = ai.getGame();
if (tgt != null) { if (sa.usesTargeting()) {
if (game.getStack().isEmpty()) { if (game.getStack().isEmpty()) {
return false; return false;
} }

View File

@@ -91,8 +91,7 @@ public class DebuffAi extends SpellAbilityAi {
@Override @Override
public boolean chkAIDrawback(SpellAbility sa, Player ai) { public boolean chkAIDrawback(SpellAbility sa, Player ai) {
if (!sa.usesTargeting()) { if (!sa.usesTargeting()) {
// TODO - copied from AF_Pump.pumpDrawbackAI() - what should be // TODO - copied from AF_Pump.pumpDrawbackAI() - what should be here?
// here?
} else { } else {
return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false); return debuffTgtAI(ai, sa, sa.hasParam("Keywords") ? Arrays.asList(sa.getParam("Keywords").split(" & ")) : null, false);
} }

View File

@@ -57,19 +57,20 @@ public class PumpAllAi extends PumpAiBase {
} }
} }
final TargetRestrictions tgt = sa.getTargetRestrictions();
final Player opp = ai.getStrongestOpponent(); final Player opp = ai.getStrongestOpponent();
if (tgt != null && sa.canTarget(opp) && sa.isCurse()) { if (sa.usesTargeting()) {
sa.resetTargets(); if (sa.canTarget(opp) && sa.isCurse()) {
sa.getTargets().add(opp); sa.resetTargets();
return true; sa.getTargets().add(opp);
} return true;
}
if (tgt != null && sa.canTarget(ai) && !sa.isCurse()) {
sa.resetTargets(); if (sa.canTarget(ai) && !sa.isCurse()) {
sa.getTargets().add(ai); sa.resetTargets();
return true; sa.getTargets().add(ai);
return true;
}
} }
final int power = AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa); final int power = AbilityUtils.calculateAmount(source, sa.getParam("NumAtt"), sa);

View File

@@ -29,7 +29,7 @@ public class TapAllAi extends SpellAbilityAi {
// or during upkeep/begin combat? // or during upkeep/begin combat?
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final Player opp = ai.getWeakestOpponent(); final Player opp = ai.getStrongestOpponent();
final Game game = ai.getGame(); final Game game = ai.getGame();
if (game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN)) { if (game.getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN)) {

View File

@@ -28,8 +28,7 @@ public class TwoPilesAi extends SpellAbilityAi {
final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai); final Player opp = AiAttackController.choosePreferredDefenderPlayer(ai);
final TargetRestrictions tgt = sa.getTargetRestrictions(); if (sa.usesTargeting()) {
if (tgt != null) {
sa.resetTargets(); sa.resetTargets();
if (sa.canTarget(opp)) { if (sa.canTarget(opp)) {
sa.getTargets().add(opp); sa.getTargets().add(opp);

View File

@@ -48,7 +48,6 @@ public class UnattachAllAi extends SpellAbilityAi {
return chance; return chance;
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean) * @see forge.card.abilityfactory.SpellAiLogic#doTriggerAINoCost(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility, boolean)
*/ */
@@ -57,8 +56,7 @@ public class UnattachAllAi extends SpellAbilityAi {
final Card card = sa.getHostCard(); final Card card = sa.getHostCard();
// Check if there are any valid targets // Check if there are any valid targets
List<GameObject> targets = new ArrayList<>(); List<GameObject> targets = new ArrayList<>();
final TargetRestrictions tgt = sa.getTargetRestrictions(); if (!sa.usesTargeting()) {
if (tgt == null) {
targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa); targets = AbilityUtils.getDefinedObjects(sa.getHostCard(), sa.getParam("Defined"), sa);
} }

View File

@@ -125,11 +125,11 @@ public class UntapAi extends SpellAbilityAi {
private static boolean untapPrefTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) { private static boolean untapPrefTargeting(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
Player targetController = ai; final PlayerCollection targetController = new PlayerCollection();
if (sa.isCurse()) { if (sa.isCurse()) {
// TODO search through all opponents, may need to check if different controllers allowed targetController.addAll(ai.getOpponents());
targetController = AiAttackController.choosePreferredDefenderPlayer(ai); } else {
targetController.add(ai);
} }
CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa); CardCollection list = CardLists.getTargetableCards(targetController.getCardsIn(ZoneType.Battlefield), sa);

View File

@@ -1689,10 +1689,6 @@ public class AbilityUtils {
// accept straight numbers // accept straight numbers
if (l[0].startsWith("Number$")) { if (l[0].startsWith("Number$")) {
final String number = l[0].substring(7); final String number = l[0].substring(7);
if (number.equals("ChosenNumber")) { // TODO remove in favor of Count ChosenNumber
int x = c.getChosenNumber() == null ? 0 : c.getChosenNumber();
return doXMath(x, expr, c, ctb);
}
return doXMath(Integer.parseInt(number), expr, c, ctb); return doXMath(Integer.parseInt(number), expr, c, ctb);
} }

View File

@@ -272,8 +272,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
sb.append("."); sb.append(".");
} else if (origin.equals("Battlefield")) { } else if (origin.equals("Battlefield")) {
// TODO Expand on this Description as more cards use it // TODO Expand on this Description as more cards use it
// for the non-targeted SAs when you choose what is returned on // for the non-targeted SAs when you choose what is returned on resolution
// resolution
sb.append("Return ").append(num).append(" ").append(type).append(" card(s) "); sb.append("Return ").append(num).append(" ").append(type).append(" card(s) ");
sb.append(" to your ").append(destination); sb.append(" to your ").append(destination);
} }
@@ -361,8 +360,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
sb.append(fromGraveyard); sb.append(fromGraveyard);
} }
// this needs to be zero indexed. Top = 0, Third = 2, -1 = // this needs to be zero indexed. Top = 0, Third = 2, -1 = Bottom
// Bottom
final int libraryPosition = sa.hasParam("LibraryPosition") ? AbilityUtils.calculateAmount(host, sa.getParam("LibraryPosition"), sa) : 0; final int libraryPosition = sa.hasParam("LibraryPosition") ? AbilityUtils.calculateAmount(host, sa.getParam("LibraryPosition"), sa) : 0;
if (libraryPosition == -1) { if (libraryPosition == -1) {

View File

@@ -9,7 +9,7 @@ SVar:DBShuffle:DB$ Shuffle | Defined$ ParentTarget | SubAbility$ DBCleanup
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:TargetedPlayer$CardsInLibrary SVar:X:TargetedPlayer$CardsInLibrary
SVar:Y:Remembered$Valid Card.NamedCard SVar:Y:Remembered$Valid Card.NamedCard
SVar:Z:Number$ChosenNumber SVar:Z:Count$ChosenNumber
AI:RemoveDeck:All AI:RemoveDeck:All
AI:RemoveDeck:Random AI:RemoveDeck:Random
SVar:Picture:http://www.wizards.com/global/images/magic/general/mindblaze.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/mindblaze.jpg

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Minotaur Warrior
PT:6/6 PT:6/6
S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ AffectedX | Description$ Each creature you control gets +1/+0 for each time it has attacked this turn. S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ AffectedX | Description$ Each creature you control gets +1/+0 for each time it has attacked this turn.
SVar:AffectedX:Count$CardNumAttacksThisTurn SVar:AffectedX:Count$CardNumAttacksThisTurn
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | PlayerTurn$ True | Phase$ Main1,Main2 | Execute$ TrigAddPhase | TriggerDescription$ Landfall - Whenever a land enters the battlefield under your control, if it's your main phase, there's an additional combat phase after this phase. At the beginning of that combat, untap all creatures you control. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | AILogic$ SafeToHold | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | PlayerTurn$ True | Phase$ Main1,Main2 | Execute$ TrigAddPhase | TriggerDescription$ Landfall - Whenever a land enters the battlefield under your control, if it's your main phase, there's an additional combat phase after this phase. At the beginning of that combat, untap all creatures you control.
SVar:TrigAddPhase:DB$ AddPhase | ExtraPhase$ Combat | ConditionPhases$ Main1,Main2 | ExtraPhaseDelayedTrigger$ DelTrigUntap | ExtraPhaseDelayedTriggerExcute$ TrigUntapAll SVar:TrigAddPhase:DB$ AddPhase | ExtraPhase$ Combat | ConditionPhases$ Main1,Main2 | ExtraPhaseDelayedTrigger$ DelTrigUntap | ExtraPhaseDelayedTriggerExcute$ TrigUntapAll
SVar:DelTrigUntap:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerDescription$ At the beginning of that combat, untap all creatures you control. SVar:DelTrigUntap:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerDescription$ At the beginning of that combat, untap all creatures you control.
SVar:TrigUntapAll:DB$ UntapAll | ValidCards$ Creature.YouCtrl SVar:TrigUntapAll:DB$ UntapAll | ValidCards$ Creature.YouCtrl

View File

@@ -5,7 +5,7 @@ PT:2/2
K:ETBReplacement:Other:ChooseNumber K:ETBReplacement:Other:ChooseNumber
SVar:ChooseNumber:DB$ ChooseNumber | Defined$ You | SpellDescription$ As CARDNAME enters the battlefield, choose a number. SVar:ChooseNumber:DB$ ChooseNumber | Defined$ You | SpellDescription$ As CARDNAME enters the battlefield, choose a number.
S:Mode$ CantBeCast | ValidCard$ Card.nonCreature+cmcEQX | Description$ Noncreature spells with mana value equal to the chosen number can't be cast. S:Mode$ CantBeCast | ValidCard$ Card.nonCreature+cmcEQX | Description$ Noncreature spells with mana value equal to the chosen number can't be cast.
SVar:X:Number$ChosenNumber SVar:X:Count$ChosenNumber
AI:RemoveDeck:All AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/sanctum_prelate.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/sanctum_prelate.jpg
Oracle:As Sanctum Prelate enters the battlefield, choose a number.\nNoncreature spells with mana value equal to the chosen number can't be cast. Oracle:As Sanctum Prelate enters the battlefield, choose a number.\nNoncreature spells with mana value equal to the chosen number can't be cast.

View File

@@ -10,7 +10,7 @@ SVar:IncrementLoss:DB$ StoreSVar | SVar$ Loss | Type$ CountSVar | Expression$ Lo
SVar:SetFilpsDone:DB$ StoreSVar | SVar$ FlipsDone | Type$ CountSVar | Expression$ TimesToFlip SVar:SetFilpsDone:DB$ StoreSVar | SVar$ FlipsDone | Type$ CountSVar | Expression$ TimesToFlip
# Draw Cards # Draw Cards
SVar:DrawIfWin:DB$ Draw | Defined$ You | NumCards$ CardsToDraw | ConditionCheckSVar$ Loss | ConditionSVarCompare$ EQ0 SVar:DrawIfWin:DB$ Draw | Defined$ You | NumCards$ CardsToDraw | ConditionCheckSVar$ Loss | ConditionSVarCompare$ EQ0
SVar:TimesToFlip:Number$ChosenNumber SVar:TimesToFlip:Count$ChosenNumber
SVar:FlipsDone:Number$0 SVar:FlipsDone:Number$0
SVar:Loss:Number$0 SVar:Loss:Number$0
SVar:CardsToDraw:Count$ChosenNumber/Times.2 SVar:CardsToDraw:Count$ChosenNumber/Times.2

View File

@@ -4,7 +4,7 @@ Types:Sorcery
A:SP$ ChooseNumber | Cost$ 3 B R | SubAbility$ DBVoidDestroyAll | SpellDescription$ Choose a number. Destroy all artifacts and creatures with mana value equal to that number. Then target player reveals their hand and discards all nonland cards with mana value equal to the number. A:SP$ ChooseNumber | Cost$ 3 B R | SubAbility$ DBVoidDestroyAll | SpellDescription$ Choose a number. Destroy all artifacts and creatures with mana value equal to that number. Then target player reveals their hand and discards all nonland cards with mana value equal to the number.
SVar:DBVoidDestroyAll:DB$ DestroyAll | ValidCards$ Artifact.cmcEQX,Creature.cmcEQX | SubAbility$ DBVoidRevealDiscard SVar:DBVoidDestroyAll:DB$ DestroyAll | ValidCards$ Artifact.cmcEQX,Creature.cmcEQX | SubAbility$ DBVoidRevealDiscard
SVar:DBVoidRevealDiscard:DB$ Discard | ValidTgts$ Player | TgtPrompt$ Select target player | Mode$ RevealDiscardAll | DiscardValid$ Card.nonLand+cmcEQX SVar:DBVoidRevealDiscard:DB$ Discard | ValidTgts$ Player | TgtPrompt$ Select target player | Mode$ RevealDiscardAll | DiscardValid$ Card.nonLand+cmcEQX
SVar:X:Number$ChosenNumber SVar:X:Count$ChosenNumber
AI:RemoveDeck:All AI:RemoveDeck:All
SVar:Picture:http://www.wizards.com/global/images/magic/general/void.jpg SVar:Picture:http://www.wizards.com/global/images/magic/general/void.jpg
Oracle:Choose a number. Destroy all artifacts and creatures with mana value equal to that number. Then target player reveals their hand and discards all nonland cards with mana value equal to the number. Oracle:Choose a number. Destroy all artifacts and creatures with mana value equal to that number. Then target player reveals their hand and discards all nonland cards with mana value equal to the number.

View File

@@ -11,7 +11,7 @@ SVar:DBDamage:DB$ DealDamage | NumDmg$ Damage | Defined$ You
SVar:DBEffect:DB$ Effect | StaticAbilities$ MayPlay | Stackable$ False | ConditionCheckSVar$ Y | ConditionSVarCompare$ EQ5 | SubAbility$ DBCleanup SVar:DBEffect:DB$ Effect | StaticAbilities$ MayPlay | Stackable$ False | ConditionCheckSVar$ Y | ConditionSVarCompare$ EQ5 | SubAbility$ DBCleanup
SVar:MayPlay:Mode$ Continuous | EffectZone$ Command | Affected$ Card.nonLand+YouOwn | MayPlay$ True | MayPlayWithoutManaCost$ True | AffectedZone$ Hand | Description$ You may cast spells from your hand this turn without paying their mana costs. SVar:MayPlay:Mode$ Continuous | EffectZone$ Command | Affected$ Card.nonLand+YouOwn | MayPlay$ True | MayPlayWithoutManaCost$ True | AffectedZone$ Hand | Description$ You may cast spells from your hand this turn without paying their mana costs.
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
SVar:X:Number$ChosenNumber SVar:X:Count$ChosenNumber
SVar:Y:Count$RememberedNumber SVar:Y:Count$RememberedNumber
SVar:Damage:SVar$Losses/Times.2 SVar:Damage:SVar$Losses/Times.2
Oracle:Flying\nWhenever Yusri, Fortune's Flame attacks, choose a number between 1 and 5. Flip that many coins. For each flip you win, draw a card. For each flip you lose, Yusri deals 2 damage to you. If you won five flips this way, you may cast spells from your hand this turn without paying their mana costs. Oracle:Flying\nWhenever Yusri, Fortune's Flame attacks, choose a number between 1 and 5. Flip that many coins. For each flip you win, draw a card. For each flip you lose, Yusri deals 2 damage to you. If you won five flips this way, you may cast spells from your hand this turn without paying their mana costs.