Merge branch 'oracle_en_vec' into 'master'

Add Oracle en-Vec

See merge request core-developers/forge!4181
This commit is contained in:
Michael Kamensky
2021-03-12 10:48:32 +00:00
6 changed files with 99 additions and 38 deletions

View File

@@ -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<Card> 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) {

View File

@@ -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);

View File

@@ -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<Card> list) {
AiController aic = ((PlayerControllerAi)aiPlayer.getController()).getAi();
for(final Card card : list) {
if (aic.getPredictedCombatNextTurn().isAttacking(card)) {
return card;
}
}
return null;
}
/**
* <p>
* getWorstAI.

View File

@@ -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);
}

View File

@@ -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")) {

View File

@@ -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.