diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 703016b4c81..4c3d29267f7 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -975,7 +975,7 @@ public class ComputerUtil { playNow = false; break; } - if (!playNow && c.isCreature() && ComputerUtilCombat.canAttackNextTurn(c) && c.canBeEquippedBy(card)) { + if (!playNow && c.isCreature() && ComputerUtilCombat.canAttackNextTurn(c) && c.canBeAttachedBy(card)) { playNow = true; } } diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java index 6deb3089770..82cd94d0621 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -125,7 +125,7 @@ public class AttachAi extends SpellAbilityAi { return false; } } - + return true; } @@ -240,7 +240,7 @@ public class AttachAi extends SpellAbilityAi { /** * Acceptable choice. - * + * * @param c * the c * @param mandatory @@ -265,7 +265,7 @@ public class AttachAi extends SpellAbilityAi { /** * Choose unpreferred. - * + * * @param mandatory * the mandatory * @param list @@ -282,7 +282,7 @@ public class AttachAi extends SpellAbilityAi { /** * Choose less preferred. - * + * * @param mandatory * the mandatory * @param list @@ -299,7 +299,7 @@ public class AttachAi extends SpellAbilityAi { /** * Attach ai change type preference. - * + * * @param sa * the sa * @param list @@ -330,7 +330,7 @@ public class AttachAi extends SpellAbilityAi { } list = CardLists.getNotType(list, type); // Filter out Basic Lands that have the same type as the changing type - + // Don't target fetchlands list = CardLists.filter(list, new Predicate() { @Override @@ -371,7 +371,7 @@ public class AttachAi extends SpellAbilityAi { /** * Attach ai keep tapped preference. - * + * * @param sa * the sa * @param list @@ -499,7 +499,7 @@ public class AttachAi extends SpellAbilityAi { /** * Attach ai control preference. - * + * * @param sa * the sa * @param list @@ -570,7 +570,7 @@ public class AttachAi extends SpellAbilityAi { } c = ComputerUtilCard.getWorstAI(betterList); } - + // If Mandatory (brought directly into play without casting) gotta // choose something @@ -583,7 +583,7 @@ public class AttachAi extends SpellAbilityAi { /** * Attach ai reanimate preference. - * + * * @param sa * the sa * @param list @@ -670,7 +670,7 @@ public class AttachAi extends SpellAbilityAi { } /** * Attach ai specific card preference. - * + * * @param sa * the sa * @param list @@ -687,7 +687,7 @@ public class AttachAi extends SpellAbilityAi { final Player ai = sa.getActivatingPlayer(); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); Card chosen = null; - + if ("Guilty Conscience".equals(sourceName)) { chosen = SpecialCardAi.GuiltyConscience.getBestAttachTarget(ai, sa, list); } else if ("Bonds of Faith".equals(sourceName)) { @@ -746,7 +746,7 @@ public class AttachAi extends SpellAbilityAi { // Should generalize this code a bit since they all have similar structures /** * Attach ai control preference. - * + * * @param sa * the sa * @param list @@ -782,7 +782,7 @@ public class AttachAi extends SpellAbilityAi { /** * Attach ai highest evaluated preference. - * + * * @param list the initial valid list * @return the card */ @@ -792,7 +792,7 @@ public class AttachAi extends SpellAbilityAi { /** * Attach ai curse preference. - * + * * @param sa * the sa * @param list @@ -916,7 +916,7 @@ public class AttachAi extends SpellAbilityAi { * the sa * @param mandatory * the mandatory - * + * * @return true, if successful */ @Override @@ -986,7 +986,7 @@ public class AttachAi extends SpellAbilityAi { /** * Attach ai pump preference. - * + * * @param sa * the sa * @param list @@ -1066,7 +1066,7 @@ public class AttachAi extends SpellAbilityAi { list.removeAll(toRemove); if (magnetList != null) { - + // Look for Heroic triggers if (magnetList.isEmpty() && sa.isSpell()) { for (Card target : list) { @@ -1256,7 +1256,7 @@ public class AttachAi extends SpellAbilityAi { /** * Attach to card ai preferences. - * + * * @param sa * the sa * @param sa @@ -1275,6 +1275,12 @@ public class AttachAi extends SpellAbilityAi { if (attachSource.hasSVar("DontEquip")) { return null; } + + // is no attachment so no using attach + if (!attachSource.isAttachment()) { + return null; + } + // Don't fortify if already fortifying if (attachSource.getAttachingCard() != null && attachSource.getAttachingCard().getController() == aiPlayer) { return null; @@ -1286,11 +1292,7 @@ public class AttachAi extends SpellAbilityAi { } else { list = CardLists.getValidCards(aiPlayer.getGame().getCardsIn(tgt.getZone()), tgt.getValidTgts(), sa.getActivatingPlayer(), attachSource, sa); - if (attachSource.isAura()) { - list = CardLists.filter(list, CardPredicates.canBeEnchantedBy(attachSource)); - } else if (attachSource.isEquipment()) { - list = CardLists.filter(list, CardPredicates.canBeEquippedBy(attachSource)); - } + list = CardLists.filter(list, CardPredicates.canBeAttachedBy(attachSource)); // TODO If Attaching without casting, don't need to actually target. // I believe this is the only case where mandatory will be true, so just @@ -1314,7 +1316,7 @@ public class AttachAi extends SpellAbilityAi { Card c = attachGeneralAI(aiPlayer, sa, prefList, mandatory, attachSource, sa.getParam("AILogic")); AiController aic = ((PlayerControllerAi)aiPlayer.getController()).getAi(); - if (c != null && attachSource.isEquipment() + if (c != null && attachSource.isEquipment() && attachSource.isEquipping() && attachSource.getEquipping().getController() == aiPlayer) { if (c.equals(attachSource.getEquipping())) { @@ -1338,7 +1340,7 @@ public class AttachAi extends SpellAbilityAi { return null; } } - + // make sure to prioritize casting spells in main 2 (creatures, other equipment, etc.) rather than moving equipment around boolean decideMoveFromUseless = uselessCreature && aic.getBooleanProperty(AiProps.PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS); @@ -1352,7 +1354,7 @@ public class AttachAi extends SpellAbilityAi { // avoid randomly moving the equipment back and forth between several creatures in one turn if (AiCardMemory.isRememberedCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ATTACHED_THIS_TURN)) { return null; - } + } // do not equip if the new creature is not significantly better than the previous one (evaluates at least better by evalT) int evalT = aic.getIntProperty(AiProps.MOVE_EQUIPMENT_CREATURE_EVAL_THRESHOLD); @@ -1360,7 +1362,7 @@ public class AttachAi extends SpellAbilityAi { return null; } } - + AiCardMemory.rememberCard(aiPlayer, sa.getHostCard(), AiCardMemory.MemorySet.ATTACHED_THIS_TURN); if (c == null && mandatory) { @@ -1372,7 +1374,7 @@ public class AttachAi extends SpellAbilityAi { /** * Attach general ai. - * + * * @param sa * the sa * @param list @@ -1448,7 +1450,7 @@ public class AttachAi extends SpellAbilityAi { /** * Contains useful curse keyword. - * + * * @param keywords * the keywords * @param card @@ -1467,7 +1469,7 @@ public class AttachAi extends SpellAbilityAi { /** * Checks if is useful keyword. - * + * * @param keyword * the keyword * @param card @@ -1478,7 +1480,7 @@ public class AttachAi extends SpellAbilityAi { private static boolean isUsefulAttachKeyword(final String keyword, final Card card, final SpellAbility sa, final int powerBonus) { final Player ai = sa.getActivatingPlayer(); final PhaseHandler ph = ai.getGame().getPhaseHandler(); - + if (!CardUtil.isStackingKeyword(keyword) && card.hasKeyword(keyword)) { return false; } @@ -1597,7 +1599,7 @@ public class AttachAi extends SpellAbilityAi { /** * Checks if is useful curse keyword. - * + * * @param keyword * the keyword * @param card @@ -1650,15 +1652,15 @@ public class AttachAi extends SpellAbilityAi { /** * Checks if it is useful to execute the attach action given the current context. - * + * * @param c * the card * @param sa SpellAbility - * @return true, if the action is useful (beneficial) in the current minimal context (Card vs. Attach SpellAbility) + * @return true, if the action is useful (beneficial) in the current minimal context (Card vs. Attach SpellAbility) */ private static boolean isUsefulAttachAction(Player ai, Card c, SpellAbility sa) { if (c == null) { - return false; + return false; } if (sa.getHostCard() == null) { // FIXME: Not sure what should the resolution be if a SpellAbility has no host card. This should @@ -1716,12 +1718,12 @@ public class AttachAi extends SpellAbilityAi { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { return true; } - + @Override protected Card chooseSingleCard(Player ai, SpellAbility sa, Iterable options, boolean isOptional, Player targetedPlayer) { return attachToCardAIPreferences(ai, sa, true); } - + @Override protected Player chooseSinglePlayer(Player ai, SpellAbility sa, Iterable options) { return attachToPlayerAIPreferences(ai, sa, true); diff --git a/forge-ai/src/main/java/forge/ai/ability/ZoneExchangeAi.java b/forge-ai/src/main/java/forge/ai/ability/ZoneExchangeAi.java index 952314a35f4..709ceff6ac2 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ZoneExchangeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ZoneExchangeAi.java @@ -39,7 +39,7 @@ public class ZoneExchangeAi extends SpellAbilityAi { } if (type.equals("Aura")) { Card c = object1.getEnchantingCard(); - if (!c.canBeEnchantedBy(object2)) { + if (!c.canBeAttachedBy(object2)) { return false; } } diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java index e040040f2c6..ce2ddd3030b 100644 --- a/forge-game/src/main/java/forge/game/GameEntity.java +++ b/forge-game/src/main/java/forge/game/GameEntity.java @@ -29,7 +29,9 @@ import forge.game.keyword.Keyword; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; +import forge.game.staticability.StaticAbility; import forge.game.trigger.TriggerType; +import forge.game.zone.ZoneType; import forge.util.collect.FCollection; import java.util.Map; @@ -372,11 +374,20 @@ public abstract class GameEntity extends GameObject implements IIdentifiable { public boolean canBeAttachedBy(final Card attach, boolean checkSBA) { // master mode - if (!attach.isAttachment()) { + if (!attach.isAttachment() || attach.isCreature()) { return false; } - if (attach.isAura() && !canBeEnchantedBy(attach, checkSBA)) { + // CantTarget static abilities + for (final Card ca : getGame().getCardsIn(ZoneType.listValueOf("Battlefield,Command"))) { + for (final StaticAbility stAb : ca.getStaticAbilities()) { + if (stAb.applyAbility("CantAttach", attach, this)) { + return false; + } + } + } + + if (attach.isAura() && !canBeEnchantedBy(attach)) { return false; } if (attach.isEquipment() && !canBeEquippedBy(attach)) { @@ -385,21 +396,24 @@ public abstract class GameEntity extends GameObject implements IIdentifiable { if (attach.isFortification() && !canBeFortifiedBy(attach)) { return false; } + + // true for all + if (hasProtectionFrom(attach, checkSBA)) { + return false; + } + return true; } - public boolean canBeEquippedBy(final Card aura) { + protected boolean canBeEquippedBy(final Card aura) { return false; } - public boolean canBeFortifiedBy(final Card aura) { + protected boolean canBeFortifiedBy(final Card aura) { return false; } - public boolean canBeEnchantedBy(final Card aura, final boolean checkSBA) { - if (!aura.isAura()) { - return false; - } + protected boolean canBeEnchantedBy(final Card aura) { SpellAbility sa = aura.getFirstAttachSpell(); TargetRestrictions tgt = null; @@ -407,8 +421,7 @@ public abstract class GameEntity extends GameObject implements IIdentifiable { tgt = sa.getTargetRestrictions(); } - return !(hasProtectionFrom(aura, checkSBA) - || ((tgt != null) && !isValid(tgt.getValidTgts(), aura.getController(), aura, sa))); + return !((tgt != null) && !isValid(tgt.getValidTgts(), aura.getController(), aura, sa)); } public boolean hasProtectionFrom(final Card source) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java index 9c3d1a72cba..356c600c49f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AttachEffect.java @@ -101,7 +101,7 @@ public class AttachEffect extends SpellAbilityEffect { // Although honestly, I'm not sure if the three of those could // handle being scripted // 303.4h: If the card can't be enchanted, the aura doesn't move - if (c.canBeEnchantedBy(card)) { + if (c.canBeAttachedBy(card)) { handleAura(card, c); } } else { diff --git a/forge-game/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java index bef78d2e537..74fa45df551 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ZoneExchangeEffect.java @@ -77,7 +77,7 @@ public class ZoneExchangeEffect extends SpellAbilityEffect { Card c = null; if (type != null && type.equals("Aura") && object1.getEnchantingCard() != null) { c = object1.getEnchantingCard(); - if (!c.canBeEnchantedBy(object2)) { + if (!c.canBeAttachedBy(object2)) { return; } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index ae16fd3dfd3..66fc5aadb37 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -5162,26 +5162,14 @@ public class Card extends GameEntity implements Comparable { return !(hasKeyword("Other players can't gain control of CARDNAME.") && !getController().equals(newController)); } - public final boolean canBeEnchantedBy(final Card aura) { - return canBeEnchantedBy(aura, false); - } - - public final boolean canBeEnchantedBy(final Card aura, final boolean checkSBA) { + @Override + protected final boolean canBeEnchantedBy(final Card aura) { SpellAbility sa = aura.getFirstAttachSpell(); TargetRestrictions tgt = null; if (sa != null) { tgt = sa.getTargetRestrictions(); } - if (aura.isCreature()) { - return false; - } - - // phase check there - if (isPhasedOut() && !aura.isPhasedOut()) { - return false; - } - if (tgt != null) { boolean zoneValid = false; // check the zone types @@ -5201,19 +5189,14 @@ public class Card extends GameEntity implements Comparable { } } - return !(hasProtectionFrom(aura, checkSBA) - || (hasKeyword("CARDNAME can't be enchanted in the future.") && !isEnchantedBy(aura)) + return !((hasKeyword("CARDNAME can't be enchanted in the future.") && !isEnchantedBy(aura)) || (hasKeyword("CARDNAME can't be enchanted.") && !aura.getName().equals("Anti-Magic Aura") && !(aura.getName().equals("Consecrate Land") && aura.isInZone(ZoneType.Battlefield)))); } - public final boolean canBeEquippedBy(final Card equip) { - if (!isCreature() || !isInPlay() || equip.isCreature()) { - return false; - } - - // phase check there - if (isPhasedOut() && !equip.isPhasedOut()) { + @Override + protected final boolean canBeEquippedBy(final Card equip) { + if (!isCreature() || !isInPlay()) { return false; } @@ -5228,20 +5211,29 @@ public class Card extends GameEntity implements Comparable { return false; } } - return !(hasProtectionFrom(equip) - || hasKeyword("CARDNAME can't be equipped.")); + return true; } - public boolean canBeFortifiedBy(final Card fort) { - if (!isLand() || !isInPlay() || fort.isCreature() || fort.isLand()) { + @Override + protected boolean canBeFortifiedBy(final Card fort) { + if (!isLand() || !isInPlay() || fort.isLand()) { return false; } + return true; + } + + /* (non-Javadoc) + * @see forge.game.GameEntity#canBeAttachedBy(forge.game.card.Card, boolean) + */ + @Override + public boolean canBeAttachedBy(Card attach, boolean checkSBA) { // phase check there - if (isPhasedOut() && !fort.isPhasedOut()) { + if (isPhasedOut() && !attach.isPhasedOut()) { return false; } - return !hasProtectionFrom(fort); + + return super.canBeAttachedBy(attach, checkSBA); } public FCollectionView getReplacementEffects() { diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java index a3eeb8dd5b6..a733609abd0 100644 --- a/forge-game/src/main/java/forge/game/card/CardPredicates.java +++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java @@ -216,20 +216,11 @@ public final class CardPredicates { }; }; - public static final Predicate canBeEnchantedBy(final Card aura) { + public static final Predicate canBeAttachedBy(final Card aura) { return new Predicate() { @Override public boolean apply(final Card c) { - return c.canBeEnchantedBy(aura); - } - }; - }; - - public static final Predicate canBeEquippedBy(final Card eq) { - return new Predicate() { - @Override - public boolean apply(final Card c) { - return c.canBeEquippedBy(eq); + return c.canBeAttachedBy(aura); } }; }; diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index f58d8b63f95..32c55e45a86 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -508,11 +508,11 @@ public class CardProperty { final String restriction = property.substring(10); if (restriction.equals("Remembered")) { for (final Object rem : source.getRemembered()) { - if (!(rem instanceof Card) || !((Card) rem).canBeEnchantedBy(card)) + if (!(rem instanceof Card) || !((Card) rem).canBeAttachedBy(card)) return false; } } else if (restriction.equals("Source")) { - if (!source.canBeEnchantedBy(card)) return false; + if (!source.canBeAttachedBy(card)) return false; } } else if (property.startsWith("CanBeEnchantedBy")) { if (property.substring(16).equals("Targeted")) { @@ -520,7 +520,7 @@ public class CardProperty { final SpellAbility saTargeting = sa.getSATargetingCard(); if (saTargeting != null) { for (final Card c : saTargeting.getTargets().getTargetCards()) { - if (!card.canBeEnchantedBy(c)) { + if (!card.canBeAttachedBy(c)) { return false; } } @@ -530,13 +530,13 @@ public class CardProperty { for (final Object rem : source.getRemembered()) { if (rem instanceof Card) { final Card c = (Card) rem; - if (!card.canBeEnchantedBy(c)) { + if (!card.canBeAttachedBy(c)) { return false; } } } } else { - if (!card.canBeEnchantedBy(source)) { + if (!card.canBeAttachedBy(source)) { return false; } } @@ -566,8 +566,8 @@ public class CardProperty { if (!card.isFortifiedBy(source)) { return false; } - } else if (property.startsWith("CanBeEquippedBy")) { - if (!card.canBeEquippedBy(source)) { + } else if (property.startsWith("CanBeAttachedBy")) { + if (!card.canBeAttachedBy(source)) { return false; } } else if (property.startsWith("Equipped")) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index b12c30491a4..517f1f7b0db 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -462,6 +462,8 @@ public class StaticAbility extends CardTraitBase implements Comparable