mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 18:58:00 +00:00
- Account for random destruction (Wild Swing)
- Some minor reorganization/refactoring.
This commit is contained in:
@@ -2,6 +2,7 @@ package forge.ai.ability;
|
|||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import forge.game.ability.effects.CounterEffect;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
@@ -69,6 +70,8 @@ public class CounterAi extends SpellAbilityAi {
|
|||||||
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
|
|| ai.getAllies().contains(topSA.getActivatingPlayer())) {
|
||||||
// might as well check for player's friendliness
|
// might as well check for player's friendliness
|
||||||
return false;
|
return false;
|
||||||
|
} else if (sa.hasParam("ConditionWouldDestroy") && !CounterEffect.checkForConditionWouldDestroy(sa, topSA)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the top ability on the stack corresponds to the AI-specific targeting declaration, if provided
|
// check if the top ability on the stack corresponds to the AI-specific targeting declaration, if provided
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
package forge.game.ability.effects;
|
package forge.game.ability.effects;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import forge.game.Game;
|
import forge.game.Game;
|
||||||
import forge.game.GameLogEntryType;
|
import forge.game.GameLogEntryType;
|
||||||
import forge.game.ability.AbilityKey;
|
import forge.game.ability.AbilityKey;
|
||||||
|
import forge.game.ability.ApiType;
|
||||||
import forge.game.ability.SpellAbilityEffect;
|
import forge.game.ability.SpellAbilityEffect;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.*;
|
||||||
import forge.game.card.CardFactoryUtil;
|
|
||||||
import forge.game.card.CardZoneTable;
|
|
||||||
import forge.game.replacement.ReplacementResult;
|
import forge.game.replacement.ReplacementResult;
|
||||||
import forge.game.replacement.ReplacementType;
|
import forge.game.replacement.ReplacementType;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
@@ -20,8 +14,13 @@ import forge.game.spellability.SpellAbilityStackInstance;
|
|||||||
import forge.game.spellability.SpellPermanent;
|
import forge.game.spellability.SpellPermanent;
|
||||||
import forge.game.trigger.TriggerType;
|
import forge.game.trigger.TriggerType;
|
||||||
import forge.game.zone.Zone;
|
import forge.game.zone.Zone;
|
||||||
|
import forge.game.zone.ZoneType;
|
||||||
import forge.util.Localizer;
|
import forge.util.Localizer;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class CounterEffect extends SpellAbilityEffect {
|
public class CounterEffect extends SpellAbilityEffect {
|
||||||
@Override
|
@Override
|
||||||
protected String getStackDescription(SpellAbility sa) {
|
protected String getStackDescription(SpellAbility sa) {
|
||||||
@@ -141,6 +140,10 @@ public class CounterEffect extends SpellAbilityEffect {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sa.hasParam("ConditionWouldDestroy") && !checkForConditionWouldDestroy(sa, tgtSA)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
removeFromStack(tgtSA, sa, si, table);
|
removeFromStack(tgtSA, sa, si, table);
|
||||||
|
|
||||||
// Destroy Permanent may be able to be turned into a SubAbility
|
// Destroy Permanent may be able to be turned into a SubAbility
|
||||||
@@ -163,6 +166,122 @@ public class CounterEffect extends SpellAbilityEffect {
|
|||||||
table.triggerChangesZoneAll(game, sa);
|
table.triggerChangesZoneAll(game, sa);
|
||||||
} // end counterResolve
|
} // end counterResolve
|
||||||
|
|
||||||
|
public static boolean checkForConditionWouldDestroy(SpellAbility sa, SpellAbility tgtSA) {
|
||||||
|
List<SpellAbility> testChain = Lists.newArrayList();
|
||||||
|
|
||||||
|
// TODO: add anything that may be important for the test chain here
|
||||||
|
SpellAbility currentTgtSA = tgtSA;
|
||||||
|
while (currentTgtSA != null) {
|
||||||
|
testChain.add(currentTgtSA);
|
||||||
|
currentTgtSA = currentTgtSA.getSubAbility();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SpellAbility viableTgtSA : testChain) {
|
||||||
|
if (checkSingleSAForConditionWouldDestroy(sa, viableTgtSA)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkSingleSAForConditionWouldDestroy(SpellAbility sa, SpellAbility tgtSA) {
|
||||||
|
Game game = sa.getHostCard().getGame();
|
||||||
|
|
||||||
|
if (tgtSA.getApi() != ApiType.Destroy && tgtSA.getApi() != ApiType.DestroyAll) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String wouldDestroy = sa.getParam("ConditionWouldDestroy");
|
||||||
|
CardCollectionView cardsOTB = game.getCardsIn(ZoneType.Battlefield);
|
||||||
|
// Potential candidates that our condition (ConditionWouldDestroy) is checking for
|
||||||
|
CardCollection conditionCandidates = CardLists.getValidCards(cardsOTB, wouldDestroy, sa.getActivatingPlayer(), sa.getHostCard(), sa);
|
||||||
|
|
||||||
|
// Determine which cards will be affected by the target SA
|
||||||
|
CardCollection affected = new CardCollection();
|
||||||
|
if (tgtSA.hasParam("ValidTgts") || tgtSA.hasParam("Defined")) {
|
||||||
|
affected.addAll(getDefinedCardsOrTargeted(tgtSA));
|
||||||
|
} else if (tgtSA.hasParam("ValidCards")) {
|
||||||
|
affected.addAll(CardLists.getValidCards(cardsOTB, tgtSA.getParam("ValidCards"), tgtSA.getActivatingPlayer(), tgtSA.getHostCard(), tgtSA));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which of the condition-specific candidates are potentially affected with the target SA
|
||||||
|
CardCollection validAffected = new CardCollection();
|
||||||
|
for (Card cand : conditionCandidates) {
|
||||||
|
if (affected.contains(cand)) {
|
||||||
|
validAffected.add(cand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case: Wild Swing random destruction - only counter if all targets are valid and each can be destroyed (100% chance
|
||||||
|
// to destroy one of the owned lands)
|
||||||
|
// TODO: this is hacky... make the detection of this ability more robust and generic?
|
||||||
|
boolean isRandomDestruction = false;
|
||||||
|
if (validAffected.isEmpty() && tgtSA.getRootAbility().getApi() == ApiType.Pump
|
||||||
|
&& tgtSA.getRootAbility().hasParam("TargetMax")
|
||||||
|
&& tgtSA.getRootAbility().getSubAbility() != null
|
||||||
|
&& tgtSA.getRootAbility().getSubAbility().getApi() == ApiType.ChooseCard
|
||||||
|
&& tgtSA.getRootAbility().getSubAbility().hasParam("AtRandom")
|
||||||
|
&& "ChosenCard".equals(tgtSA.getParam("Defined"))) {
|
||||||
|
isRandomDestruction = true;
|
||||||
|
boolean allValid = true;
|
||||||
|
affected.addAll(getDefinedCardsOrTargeted(tgtSA.getRootAbility()));
|
||||||
|
for (Card cand : conditionCandidates) {
|
||||||
|
if (affected.contains(cand)) {
|
||||||
|
validAffected.add(cand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CardCollectionView rootTgts = tgtSA.getRootAbility().getTargets().getTargetCards();
|
||||||
|
for (Card rootTgt : rootTgts) {
|
||||||
|
if (!validAffected.contains(rootTgt)) {
|
||||||
|
allValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!allValid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validAffected.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
} else if (tgtSA.hasParam("Sacrifice")) {
|
||||||
|
return false; // Sacrifice doesn't count as Destroy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dry run Destroy on each validAffected to see if it can be destroyed at this moment
|
||||||
|
boolean willDestroyCondition = false;
|
||||||
|
final boolean noRegen = tgtSA.hasParam("NoRegen");
|
||||||
|
CardZoneTable testTable = new CardZoneTable();
|
||||||
|
Map<AbilityKey, Object> testParams = AbilityKey.newMap();
|
||||||
|
testParams.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield());
|
||||||
|
|
||||||
|
boolean willDestroyAll = true;
|
||||||
|
for (Card aff : validAffected) {
|
||||||
|
if (tgtSA.usesTargeting() && !aff.canBeTargetedBy(tgtSA)) {
|
||||||
|
willDestroyAll = false;
|
||||||
|
continue; // Should account for Protection/Hexproof/etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
Card toBeDestroyed = CardFactory.copyCard(aff, true);
|
||||||
|
|
||||||
|
game.getTriggerHandler().setSuppressAllTriggers(true);
|
||||||
|
boolean destroyed = game.getAction().destroy(toBeDestroyed, tgtSA, !noRegen, testTable, testParams);
|
||||||
|
game.getTriggerHandler().setSuppressAllTriggers(false);
|
||||||
|
|
||||||
|
if (destroyed) {
|
||||||
|
willDestroyCondition = true; // this should pick up replacement effects replacing Destroy
|
||||||
|
if (!isRandomDestruction) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
willDestroyAll = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isRandomDestruction ? willDestroyAll : willDestroyCondition;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* removeFromStack.
|
* removeFromStack.
|
||||||
|
|||||||
8
forge-gui/res/cardsfolder/e/equinox.txt
Normal file
8
forge-gui/res/cardsfolder/e/equinox.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Name:Equinox
|
||||||
|
ManaCost:W
|
||||||
|
Types:Enchantment Aura
|
||||||
|
K:Enchant land
|
||||||
|
A:SP$ Attach | Cost$ W | ValidTgts$ Land | TgtPrompt$ Select target land | AILogic$ Pump
|
||||||
|
S:Mode$ Continuous | Affected$ Card.EnchantedBy | AddAbility$ CounterDestroyLand | Description$ Enchanted land has "{T}: Counter target spell if it would destroy a land you control."
|
||||||
|
SVar:CounterDestroyLand:AB$ Counter | Cost$ T | TargetType$ Spell | ConditionWouldDestroy$ Land.YouCtrl | TgtPrompt$ Select target spell | ValidTgts$ Card | SpellDescription$ Counter target spell if it would destroy a land you control.
|
||||||
|
Oracle:Enchant land\nEnchanted land has "{T}: Counter target spell if it would destroy a land you control."
|
||||||
Reference in New Issue
Block a user