mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 04:08:01 +00:00
- Expanded r28953 to support bouncing creatures to save them
This commit is contained in:
@@ -42,6 +42,7 @@ import forge.game.phase.PhaseType;
|
|||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.AbilityManaPart;
|
import forge.game.spellability.AbilityManaPart;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
|
import forge.game.spellability.SpellAbilityStackInstance;
|
||||||
import forge.game.spellability.TargetRestrictions;
|
import forge.game.spellability.TargetRestrictions;
|
||||||
import forge.game.staticability.StaticAbility;
|
import forge.game.staticability.StaticAbility;
|
||||||
import forge.game.trigger.Trigger;
|
import forge.game.trigger.Trigger;
|
||||||
@@ -1072,8 +1073,17 @@ public class ComputerUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check stack for something that will kill this
|
// check stack for something that will kill this
|
||||||
final SpellAbility topStack = game.getStack().peekAbility();
|
for (SpellAbilityStackInstance si : game.getStack()) {
|
||||||
Iterables.addAll(objects, ComputerUtil.predictThreatenedObjects(aiPlayer, sa, topStack));
|
// iterate from top of stack to find SpellAbility, including sub-abilities,
|
||||||
|
// that does not match "sa"
|
||||||
|
SpellAbility spell = si.getSpellAbility(true), sub = spell.getSubAbility();
|
||||||
|
while (sub != null && sub != sa) {
|
||||||
|
sub = sub.getSubAbility();
|
||||||
|
}
|
||||||
|
if (sa != spell && sa != sub) {
|
||||||
|
Iterables.addAll(objects, ComputerUtil.predictThreatenedObjects(aiPlayer, sa, spell));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return objects;
|
return objects;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return canBouncePermanent(ai, sa, list) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -740,12 +740,6 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// Narrow down the list:
|
// Narrow down the list:
|
||||||
if (origin.equals(ZoneType.Battlefield)) {
|
if (origin.equals(ZoneType.Battlefield)) {
|
||||||
// filter out untargetables
|
|
||||||
CardCollectionView aiPermanents = CardLists.filterControlledBy(list, ai);
|
|
||||||
|
|
||||||
// Don't blink cards that will die.
|
|
||||||
aiPermanents = ComputerUtil.getSafeTargets(ai, sa, aiPermanents);
|
|
||||||
|
|
||||||
if ("Polymorph".equals(sa.getParam("AILogic"))) {
|
if ("Polymorph".equals(sa.getParam("AILogic"))) {
|
||||||
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
list = CardLists.getTargetableCards(ai.getCardsIn(ZoneType.Battlefield), sa);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
@@ -808,39 +802,11 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
&& (subApi == ApiType.DelayedTrigger || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered")))))
|
&& (subApi == ApiType.DelayedTrigger || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered")))))
|
||||||
&& (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) {
|
&& (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) {
|
||||||
|
|
||||||
// check stack for something on the stack that will kill
|
Card tobounce = canBouncePermanent(ai, sa, list);
|
||||||
// anything i control
|
if (tobounce != null) {
|
||||||
if (!game.getStack().isEmpty()) {
|
sa.getTargets().add(tobounce);
|
||||||
final List<GameObject> objects = ComputerUtil.predictThreatenedObjects(ai, sa);
|
|
||||||
|
|
||||||
final List<Card> threatenedTargets = new ArrayList<Card>();
|
|
||||||
|
|
||||||
for (final Card c : aiPermanents) {
|
|
||||||
if (objects.contains(c)) {
|
|
||||||
threatenedTargets.add(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!threatenedTargets.isEmpty()) {
|
|
||||||
// Choose "best" of the remaining to save
|
|
||||||
sa.getTargets().add(ComputerUtilCard.getBestAI(threatenedTargets));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Save combatants
|
|
||||||
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
|
||||||
Combat combat = game.getCombat();
|
|
||||||
final CardCollection combatants = CardLists.filter(aiPermanents, CardPredicates.Presets.CREATURES);
|
|
||||||
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
|
||||||
|
|
||||||
for (final Card c : combatants) {
|
|
||||||
if (c.getShieldCount() == 0 && ComputerUtilCombat.combatantWouldBeDestroyed(ai, c, combat)
|
|
||||||
&& c.getOwner() == ai && !c.isToken()) {
|
|
||||||
sa.getTargets().add(c);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CardLists.getNotType(list, "Land").isEmpty()) {
|
if (!CardLists.getNotType(list, "Land").isEmpty()) {
|
||||||
// When bouncing opponents stuff other than lands, don't bounce cards with CMC 0
|
// When bouncing opponents stuff other than lands, don't bounce cards with CMC 0
|
||||||
@@ -1032,6 +998,57 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a permanent threatened by a stack ability or in combat can
|
||||||
|
* be saved by bouncing.
|
||||||
|
* @param ai controlling player
|
||||||
|
* @param sa ChangeZone ability
|
||||||
|
* @param list possible targets
|
||||||
|
* @return target to bounce, null if no good targets
|
||||||
|
*/
|
||||||
|
private static Card canBouncePermanent(final Player ai, SpellAbility sa, CardCollectionView list) {
|
||||||
|
Game game = ai.getGame();
|
||||||
|
// filter out untargetables
|
||||||
|
CardCollectionView aiPermanents = CardLists
|
||||||
|
.filterControlledBy(list, ai);
|
||||||
|
|
||||||
|
// Don't blink cards that will die.
|
||||||
|
aiPermanents = ComputerUtil.getSafeTargets(ai, sa, aiPermanents);
|
||||||
|
if (!game.getStack().isEmpty()) {
|
||||||
|
final List<GameObject> objects = ComputerUtil
|
||||||
|
.predictThreatenedObjects(ai, sa);
|
||||||
|
|
||||||
|
final List<Card> threatenedTargets = new ArrayList<Card>();
|
||||||
|
|
||||||
|
for (final Card c : aiPermanents) {
|
||||||
|
if (objects.contains(c)) {
|
||||||
|
threatenedTargets.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!threatenedTargets.isEmpty()) {
|
||||||
|
// Choose "best" of the remaining to save
|
||||||
|
return ComputerUtilCard.getBestAI(threatenedTargets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save combatants
|
||||||
|
else if (game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) {
|
||||||
|
Combat combat = game.getCombat();
|
||||||
|
final CardCollection combatants = CardLists.filter(aiPermanents,
|
||||||
|
CardPredicates.Presets.CREATURES);
|
||||||
|
ComputerUtilCard.sortByEvaluateCreature(combatants);
|
||||||
|
|
||||||
|
for (final Card c : combatants) {
|
||||||
|
if (c.getShieldCount() == 0
|
||||||
|
&& ComputerUtilCombat.combatantWouldBeDestroyed(ai, c,
|
||||||
|
combat) && c.getOwner() == ai && !c.isToken()) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isUnpreferredTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
private static boolean isUnpreferredTarget(final Player ai, final SpellAbility sa, final boolean mandatory) {
|
||||||
if (!mandatory) {
|
if (!mandatory) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1224,6 +1241,12 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
c = ComputerUtilCard.getBestAI(fetchList);
|
c = ComputerUtilCard.getBestAI(fetchList);
|
||||||
} else {
|
} else {
|
||||||
c = ComputerUtilCard.getWorstAI(fetchList);
|
c = ComputerUtilCard.getWorstAI(fetchList);
|
||||||
|
if (sa.getHostCard().getName().equals("Temur Sabertooth")) {
|
||||||
|
Card tobounce = canBouncePermanent(player, sa, fetchList);
|
||||||
|
if (tobounce != null) {
|
||||||
|
c = tobounce;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (origin.contains(ZoneType.Library) && (type.contains("Basic") || areAllBasics(type))) {
|
} else if (origin.contains(ZoneType.Library) && (type.contains("Basic") || areAllBasics(type))) {
|
||||||
c = basicManaFixing(decider, fetchList);
|
c = basicManaFixing(decider, fetchList);
|
||||||
|
|||||||
Reference in New Issue
Block a user