From 3692c2b391f468328bebef7668f72715c98810b7 Mon Sep 17 00:00:00 2001 From: Alumi Date: Fri, 12 Mar 2021 10:48:31 +0000 Subject: [PATCH] Add Oracle en-Vec --- .../java/forge/ai/AiAttackController.java | 96 +++++++++++-------- .../src/main/java/forge/ai/AiController.java | 12 +++ .../main/java/forge/ai/ComputerUtilCard.java | 11 +++ .../java/forge/ai/ability/ChooseCardAi.java | 2 + .../ability/effects/ChooseCardEffect.java | 3 + forge-gui/res/cardsfolder/o/oracle_en_vec.txt | 13 +++ 6 files changed, 99 insertions(+), 38 deletions(-) create mode 100644 forge-gui/res/cardsfolder/o/oracle_en_vec.txt diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index e9247609619..87cd7afcfb3 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -69,6 +69,7 @@ public class AiAttackController { private Player defendingOpponent; private int aiAggression = 0; // added by Masher, how aggressive the ai is attack will be depending on circumstances + private final boolean nextTurn; /** @@ -78,18 +79,24 @@ public class AiAttackController { * */ public AiAttackController(final Player ai) { + this(ai, false); + } // constructor + + public AiAttackController(final Player ai, boolean nextTurn) { this.ai = ai; - this.defendingOpponent = choosePreferredDefenderPlayer(); + this.defendingOpponent = choosePreferredDefenderPlayer(); this.oppList = getOpponentCreatures(this.defendingOpponent); this.myList = ai.getCreaturesInPlay(); this.attackers = new ArrayList<>(); for (Card c : myList) { - if (CombatUtil.canAttack(c, this.defendingOpponent)) { + if (nextTurn && CombatUtil.canAttackNextTurn(c, this.defendingOpponent) || + CombatUtil.canAttack(c, this.defendingOpponent)) { attackers.add(c); } } this.blockers = getPossibleBlockers(oppList, this.attackers); - } // constructor + this.nextTurn = nextTurn; + } // overloaded constructor to evaluate attackers that should attack next turn public AiAttackController(final Player ai, Card attacker) { this.ai = ai; @@ -101,6 +108,7 @@ public class AiAttackController { attackers.add(attacker); } this.blockers = getPossibleBlockers(oppList, this.attackers); + this.nextTurn = false; } // overloaded constructor to evaluate single specified attacker public static List getOpponentCreatures(final Player defender) { @@ -132,6 +140,14 @@ public class AiAttackController { this.oppList.remove(blocker); } + private boolean canAttackWrapper(final Card attacker, final GameEntity defender) { + if (nextTurn) { + return CombatUtil.canAttackNextTurn(attacker, defender); + } else { + return CombatUtil.canAttack(attacker, defender); + } + } + /** Choose opponent for AI to attack here. Expand as necessary. */ private Player choosePreferredDefenderPlayer() { Player defender = ai.getWeakestOpponent(); //Gets opponent with the least life @@ -704,42 +720,46 @@ public class AiAttackController { // Attackers that don't really have a choice int numForcedAttackers = 0; - for (final Card attacker : this.attackers) { - if (!CombatUtil.canAttack(attacker, defender)) { - attackersLeft.remove(attacker); - continue; - } - boolean mustAttack = false; - if (attacker.isGoaded()) { - mustAttack = true; - } else if (attacker.getSVar("MustAttack").equals("True")) { - mustAttack = true; - } else if (attacker.hasSVar("EndOfTurnLeavePlay") - && isEffectiveAttacker(ai, attacker, combat)) { - mustAttack = true; - } else if (seasonOfTheWitch) { - // TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack - mustAttack = true; - } else { - for (KeywordInterface inst : attacker.getKeywords()) { - String s = inst.getOriginal(); - if (s.equals("CARDNAME attacks each turn if able.") - || s.startsWith("CARDNAME attacks specific player each combat if able") - || s.equals("CARDNAME attacks each combat if able.")) { - mustAttack = true; - break; + // nextTurn is now only used by effect from Oracle en-Vec, which can skip check must attack, + // because creatures not chosen can't attack. + if (!nextTurn) { + for (final Card attacker : this.attackers) { + if (!CombatUtil.canAttack(attacker, defender)) { + attackersLeft.remove(attacker); + continue; + } + boolean mustAttack = false; + if (attacker.isGoaded()) { + mustAttack = true; + } else if (attacker.getSVar("MustAttack").equals("True")) { + mustAttack = true; + } else if (attacker.hasSVar("EndOfTurnLeavePlay") + && isEffectiveAttacker(ai, attacker, combat)) { + mustAttack = true; + } else if (seasonOfTheWitch) { + // TODO: if there are other ways to tap this creature (like mana creature), then don't need to attack + mustAttack = true; + } else { + for (KeywordInterface inst : attacker.getKeywords()) { + String s = inst.getOriginal(); + if (s.equals("CARDNAME attacks each turn if able.") + || s.startsWith("CARDNAME attacks specific player each combat if able") + || s.equals("CARDNAME attacks each combat if able.")) { + mustAttack = true; + break; + } } } + if (mustAttack || attacker.getController().getMustAttackEntity() != null || attacker.getController().getMustAttackEntityThisTurn() != null) { + combat.addAttacker(attacker, defender); + attackersLeft.remove(attacker); + numForcedAttackers++; + } } - if (mustAttack || attacker.getController().getMustAttackEntity() != null || attacker.getController().getMustAttackEntityThisTurn() != null) { - combat.addAttacker(attacker, defender); - attackersLeft.remove(attacker); - numForcedAttackers++; + if (attackersLeft.isEmpty()) { + return; } } - if (attackersLeft.isEmpty()) { - return; - } // Lightmine Field: make sure the AI doesn't wipe out its own creatures if (lightmineField) { @@ -760,7 +780,7 @@ public class AiAttackController { if (attackMax != -1 && combat.getAttackers().size() >= attackMax) return; - if (CombatUtil.canAttack(attacker, defender) && this.isEffectiveAttacker(ai, attacker, combat)) { + if (canAttackWrapper(attacker, defender) && this.isEffectiveAttacker(ai, attacker, combat)) { combat.addAttacker(attacker, defender); } } @@ -801,7 +821,7 @@ public class AiAttackController { System.out.println("Exalted"); this.aiAggression = 6; for (Card attacker : this.attackers) { - if (CombatUtil.canAttack(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) { + if (canAttackWrapper(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) { combat.addAttacker(attacker, defender); return; } @@ -817,7 +837,7 @@ public class AiAttackController { // reached max, breakup if (attackMax != -1 && combat.getAttackers().size() >= attackMax) break; - if (CombatUtil.canAttack(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) { + if (canAttackWrapper(attacker, defender) && this.shouldAttack(ai, attacker, this.blockers, combat)) { combat.addAttacker(attacker, defender); } } @@ -1058,7 +1078,7 @@ public class AiAttackController { continue; } - if (this.shouldAttack(ai, attacker, this.blockers, combat) && CombatUtil.canAttack(attacker, defender)) { + if (this.shouldAttack(ai, attacker, this.blockers, combat) && canAttackWrapper(attacker, defender)) { combat.addAttacker(attacker, defender); // check if attackers are enough to finish the attacked planeswalker if (defender instanceof Card) { diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index bc9bcbaa1b9..eda967e4b29 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -79,6 +79,7 @@ public class AiController { private final Game game; private final AiCardMemory memory; private Combat predictedCombat; + private Combat predictedCombatNextTurn; private boolean cheatShuffle; private boolean useSimulation; private SpellAbilityPicker simPicker; @@ -123,6 +124,15 @@ public class AiController { return predictedCombat; } + public Combat getPredictedCombatNextTurn() { + if (predictedCombatNextTurn == null) { + AiAttackController aiAtk = new AiAttackController(player, true); + predictedCombatNextTurn = new Combat(player); + aiAtk.declareAttackers(predictedCombatNextTurn); + } + return predictedCombatNextTurn; + } + public AiController(final Player computerPlayer, final Game game0) { player = computerPlayer; game = game0; @@ -1364,6 +1374,8 @@ public class AiController { // Reset cached predicted combat, as it may be stale. It will be // re-created if needed and used for any AI logic that needs it. predictedCombat = null; + // Also reset predicted combat for next turn here + predictedCombatNextTurn = null; // Reset priority mana reservation that's meant to work for one spell only AiCardMemory.clearMemorySet(player, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_NEXT_SPELL); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 89fd93d1e92..2b97c368c26 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -309,6 +309,17 @@ public class ComputerUtilCard { return biggest; } + // For ability of Oracle en-Vec, return the first card that are going to attack next turn + public static Card getBestCreatureToAttackNextTurnAI(final Player aiPlayer, final Iterable list) { + AiController aic = ((PlayerControllerAi)aiPlayer.getController()).getAi(); + for(final Card card : list) { + if (aic.getPredictedCombatNextTurn().isAttacking(card)) { + return card; + } + } + return null; + } + /** *

* getWorstAI. diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java index e490305988b..a25c0e71424 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardAi.java @@ -289,6 +289,8 @@ public class ChooseCardAi extends SpellAbilityAi { // Choose the best (hopefully the fattest, whatever) creature so that hopefully it won't die too easily choice = ComputerUtilCard.getBestAI(creatures); } + } else if (logic.equals("NextTurnAttacker")) { + choice = ComputerUtilCard.getBestCreatureToAttackNextTurnAI(ai, options); } else { choice = ComputerUtilCard.getBestAI(options); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java index 8a5cdd94fcc..f425374b577 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java @@ -122,6 +122,9 @@ public class ChooseCardEffect extends SpellAbilityEffect { chosen.addAll(p.getController().chooseCardsForEffect(choices, sa, title, minAmount, validAmount, !sa.hasParam("Mandatory"), null)); } } + if (sa.hasParam("Reveal")) { + game.getAction().reveal(chosen, p, true, Localizer.getInstance().getMessage("lblChosenCards") + " "); + } } host.setChosenCards(chosen); if (sa.hasParam("RememberChosen")) { diff --git a/forge-gui/res/cardsfolder/o/oracle_en_vec.txt b/forge-gui/res/cardsfolder/o/oracle_en_vec.txt new file mode 100644 index 00000000000..255bf75e720 --- /dev/null +++ b/forge-gui/res/cardsfolder/o/oracle_en_vec.txt @@ -0,0 +1,13 @@ +Name:Oracle en-Vec +ManaCost:1 W +Types:Creature Human Wizard +PT:1/1 +A:AB$ ChooseCard | Cost$ T | ValidTgts$ Player.Opponent | MinAmount$ 0 | Amount$ X | Choices$ Creature | TargetControls$ True | ChoiceTitle$ Choose any number of creatures you control | PlayerTurn$ True | Reveal$ True | AILogic$ NextTurnAttacker | SubAbility$ DBOracleEffect | StackDescription$ SpellDescription | SpellDescription$ Target opponent chooses any number of creatures they control. During that player's next turn, the chosen creatures attack if able, and other creatures can't attack. At the beginning of that turn's end step, destroy each of the chosen creatures that didn't attack this turn. Activate this ability only during your turn. +SVar:X:Count$Valid Creature.TargetedPlayerCtrl +SVar:DBOracleEffect:DB$ Effect | EffectOwner$ TargetedPlayer | StaticAbilities$ ForceAttack,ForbidAttack | Triggers$ TrigDestroy | Duration$ UntilTheEndOfYourNextTurn | SubAbility$ DBCleanup +SVar:ForceAttack:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.YouCtrl+ChosenCard | AddHiddenKeyword$ CARDNAME attacks each combat if able. | Description$ During that player's next turn, the chosen creatures attack if able, and other creatures can't attack. +SVar:ForbidAttack:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.YouCtrl+nonChosenCard | AddHiddenKeyword$ CARDNAME can't attack. +SVar:TrigDestroy:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Command | Execute$ DBDestroy | TriggerDescription$ At the beginning of that turn's end step, destroy each of the chosen creatures that didn't attack this turn. +SVar:DBDestroy:DB$ DestroyAll | ValidCards$ Creature.ChosenCard+notAttackedThisTurn +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +Oracle:{T}: Target opponent chooses any number of creatures they control. During that player's next turn, the chosen creatures attack if able, and other creatures can't attack. At the beginning of that turn's end step, destroy each of the chosen creatures that didn't attack this turn. Activate this ability only during your turn.