Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
55 changes: 45 additions & 10 deletions forge-ai/src/main/java/forge/ai/ability/AttachAi.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import forge.game.cost.CostPart;
import forge.game.cost.CostSacrifice;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
Expand Down Expand Up @@ -570,28 +571,46 @@ private static Card attachAIReanimatePreference(final SpellAbility sa, final Lis
final Card attachSource) {
// AI For choosing a Card to Animate.
final Player ai = sa.getActivatingPlayer();
final Card attachSourceLki = CardCopyService.getLKICopy(attachSource);
Card attachSourceLki = null;
for (Trigger t : attachSource.getTriggers()) {
if (!t.getMode().equals(TriggerType.ChangesZone)) {
continue;
}
if (!"Battlefield".equals(t.getParam("Destination"))) {
continue;
}
if (!"Card.Self".equals(t.getParam("ValidCard"))) {
continue;
}
SpellAbility trigSa = t.ensureAbility();
SpellAbility animateSa = trigSa.findSubAbilityByType(ApiType.Animate);
if (animateSa == null) {
continue;
}
animateSa.setActivatingPlayer(sa.getActivatingPlayer());
attachSourceLki = AnimateAi.becomeAnimated(attachSource, animateSa);
}
if (attachSourceLki == null) {
return null;
}
attachSourceLki.setLastKnownZone(ai.getZone(ZoneType.Battlefield));
Comment thread
tool4ever marked this conversation as resolved.
// Suppress original attach Spell to replace it with another
attachSourceLki.getFirstAttachSpell().setSuppressed(true);
final Card finalAttachSourceLki = attachSourceLki;

//TODO for Reanimate Auras i need the new Attach Spell, in later versions it might be part of the Enchant Keyword
attachSourceLki.addSpellAbility(AbilityFactory.getAbility(attachSourceLki, "NewAttach"));
List<Card> betterList = CardLists.filter(list, c -> {
final Card lki = CardCopyService.getLKICopy(c);
// need to fake it as if lki would be on the battlefield
lki.setLastKnownZone(ai.getZone(ZoneType.Battlefield));

// Reanimate Auras use "Enchant creature put onto the battlefield with CARDNAME" with Remembered
attachSourceLki.clearRemembered();
attachSourceLki.addRemembered(lki);
finalAttachSourceLki.clearRemembered();
finalAttachSourceLki.addRemembered(lki);

// need to check what the cards would be on the battlefield
// do not attach yet, that would cause Events
CardCollection preList = new CardCollection(lki);
preList.add(attachSourceLki);
preList.add(finalAttachSourceLki);
c.getGame().getAction().checkStaticAbilities(false, Sets.newHashSet(preList), preList);
boolean result = lki.canBeAttached(attachSourceLki, null);
boolean result = lki.canBeAttached(finalAttachSourceLki, null);

//reset static abilities
c.getGame().getAction().checkStaticAbilities(false);
Expand Down Expand Up @@ -965,7 +984,23 @@ private static boolean isAuraSpell(final SpellAbility sa) {
*/
private static boolean attachPreference(final SpellAbility sa, final TargetRestrictions tgt, final boolean mandatory) {
GameObject o;
if (tgt.canTgtPlayer()) {
boolean spellCanTargetPlayer = false;
if (isAuraSpell(sa)) {
Card source = sa.getHostCard();
if (!source.hasKeyword(Keyword.ENCHANT)) {
return false;
}
for (KeywordInterface ki : source.getKeywords(Keyword.ENCHANT)) {
String ko = ki.getOriginal();
String m[] = ko.split(":");
String v = m[1];
if (v.contains("Player") || v.contains("Opponent")) {
spellCanTargetPlayer = true;
break;
}
}
}
if (tgt.canTgtPlayer() && (!isAuraSpell(sa) || spellCanTargetPlayer)) {
List<Player> targetable = new ArrayList<>();
for (final Player player : sa.getHostCard().getGame().getPlayers()) {
if (sa.canTarget(player)) {
Expand Down
24 changes: 19 additions & 5 deletions forge-game/src/main/java/forge/game/GameAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityPredicates;
import forge.game.spellability.SpellPermanent;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityCantAttackBlock;
import forge.game.staticability.StaticAbilityContinuous;
Expand Down Expand Up @@ -2778,16 +2777,33 @@ public static boolean attachAuraOnIndirectEnterBattlefield(final Card source, Ma
// When an Aura ETB without being cast you can choose a valid card to
// attach it to
final SpellAbility aura = source.getFirstAttachSpell();
if (!source.hasKeyword(Keyword.ENCHANT)) {
return false;
}

if (aura == null) {
return false;
}
aura.setActivatingPlayer(source.getController());
final Game game = source.getGame();
final TargetRestrictions tgt = aura.getTargetRestrictions();

Set<ZoneType> zones = EnumSet.noneOf(ZoneType.class);
boolean canTargetPlayer = false;
for (KeywordInterface ki : source.getKeywords(Keyword.ENCHANT)) {
String o = ki.getOriginal();
String m[] = o.split(":");
String v = m[1];
if (v.contains("inZone")) { // currently the only other zone is Graveyard
zones.add(ZoneType.Graveyard);
} else {
zones.add(ZoneType.Battlefield);
}
if (v.startsWith("Player") || v.startsWith("Opponent")) {
canTargetPlayer = true;
}
}
Player p = source.getController();
if (tgt.canTgtPlayer()) {
if (canTargetPlayer) {
final FCollection<Player> players = game.getPlayers().filter(PlayerPredicates.canBeAttached(source, aura));

final Player pa = p.getController().chooseSingleEntityForEffect(players, aura,
Expand All @@ -2797,9 +2813,7 @@ public static boolean attachAuraOnIndirectEnterBattlefield(final Card source, Ma
return true;
}
} else {
List<ZoneType> zones = Lists.newArrayList(tgt.getZone());
CardCollection list = new CardCollection();

if (params != null) {
if (zones.contains(ZoneType.Battlefield)) {
list.addAll((CardCollectionView) params.get(AbilityKey.LastStateBattlefield));
Expand Down
21 changes: 12 additions & 9 deletions forge-game/src/main/java/forge/game/GameEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@
import forge.game.card.CounterType;
import forge.game.event.GameEventCardAttachment;
import forge.game.keyword.Keyword;
import forge.game.keyword.KeywordInterface;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbilityCantAttach;
import forge.game.zone.ZoneType;

Expand Down Expand Up @@ -267,15 +267,18 @@ protected boolean canBeFortifiedBy(final Card aura) {
}

protected boolean canBeEnchantedBy(final Card aura) {
// TODO need to check for multiple Enchant Keywords

SpellAbility sa = aura.getFirstAttachSpell();
TargetRestrictions tgt = null;
if (sa != null) {
tgt = sa.getTargetRestrictions();
if (!aura.hasKeyword(Keyword.ENCHANT)) {
return false;
}

return tgt != null && isValid(tgt.getValidTgts(), aura.getController(), aura, sa);
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
String k = ki.getOriginal();
String m[] = k.split(":");
String v = m[1];
if (!isValid(v.split(","), aura.getController(), aura, null)) {
return false;
}
}
return true;
}

public boolean hasCounters() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,8 @@ public static void doAnimate(final Card c, final SpellAbility sa, final Integer

// remove abilities
final List<SpellAbility> removedAbilities = Lists.newArrayList();
boolean clearSpells = sa.hasParam("OverwriteSpells");

if (clearSpells) {
removedAbilities.addAll(Lists.newArrayList(c.getSpells()));
}

if (sa.hasParam("RemoveThisAbility") && !removedAbilities.contains(sa)) {
if (sa.hasParam("RemoveThisAbility")) {
removedAbilities.add(sa);
}

Expand Down
47 changes: 23 additions & 24 deletions forge-game/src/main/java/forge/game/card/Card.java
Original file line number Diff line number Diff line change
Expand Up @@ -2420,9 +2420,17 @@ public final String keywordsToText(final Collection<KeywordInterface> keywords)
final String[] k = keyword.split(":");
sbLong.append(k[2]).append("\r\n");
} else if (keyword.startsWith("Enchant")) {
String k = keyword;
k = TextUtil.fastReplace(k, "Curse", "");
sbLong.append(k).append("\r\n");
String m[] = keyword.split(":");
String desc;
if (m.length > 2) {
desc = m[2];
} else {
desc = m[1];
if (CardType.isACardType(desc) || "Permanent".equals(desc) || "Player".equals(desc) || "Opponent".equals(desc)) {
desc = desc.toLowerCase();
}
}
sbLong.append("Enchant ").append(desc).append("\r\n");
} else if (keyword.startsWith("Ripple")) {
sbLong.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n");
} else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph")
Expand Down Expand Up @@ -6902,7 +6910,7 @@ public final void animateBestow(final boolean updateView) {
addChangedCardTypes(new CardType(Collections.singletonList("Aura"), true),
new CardType(Collections.singletonList("Creature"), true),
false, EnumSet.of(RemoveType.EnchantmentTypes), bestowTimestamp, 0, updateView, false);
addChangedCardKeywords(Collections.singletonList("Enchant creature"), Lists.newArrayList(),
addChangedCardKeywords(Collections.singletonList("Enchant:Creature"), Lists.newArrayList(),
false, bestowTimestamp, null, updateView);
}

Expand Down Expand Up @@ -7098,30 +7106,21 @@ public final boolean canBeControlledBy(final Player newController) {

@Override
protected final boolean canBeEnchantedBy(final Card aura) {
SpellAbility sa = aura.getFirstAttachSpell();
TargetRestrictions tgt = null;
if (sa != null) {
tgt = sa.getTargetRestrictions();
}

if (tgt != null) {
boolean zoneValid = false;
// check the zone types
for (final ZoneType zt : tgt.getZone()) {
if (isInZone(zt)) {
zoneValid = true;
break;
}
if (!aura.hasKeyword(Keyword.ENCHANT)) {
return false;
}
for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) {
String k = ki.getOriginal();
String m[] = k.split(":");
String v = m[1];
if (!isValid(v.split(","), aura.getController(), aura, null)) {
return false;
}
if (!zoneValid) {
if (!v.contains("inZone") && !isInPlay()) {
return false;
}

// check valid
return isValid(tgt.getValidTgts(), aura.getController(), aura, sa);
}

return false;
return true;
}

@Override
Expand Down
32 changes: 31 additions & 1 deletion forge-game/src/main/java/forge/game/card/CardFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,37 @@ private static void readCardFace(Card c, ICardFace face) {
SpellAbility sa = new LandAbility(c);
sa.setCardState(c.getCurrentState());
c.addSpellAbility(sa);
} else if (c.isPermanent() && !c.isAura()) {
} else if (c.isAura()) {
String desc = "";
String extra = "";
for (KeywordInterface ki : c.getKeywords(Keyword.ENCHANT)) {
String o = ki.getOriginal();
String m[] = o.split(":");
if (m.length > 2) {
desc = m[2];
} else {
desc = m[1];
if (CardType.isACardType(desc) || "Permanent".equals(desc) || "Player".equals(desc) || "Opponent".equals(desc)) {
desc = desc.toLowerCase();
}
}
break;
}
if (c.hasSVar("AttachAITgts")) {
extra += " | AITgts$ " + c.getSVar("AttachAITgts");
}
if (c.hasSVar("AttachAILogic")) {
extra += " | AILogic$ " + c.getSVar("AttachAILogic");
}
if (c.hasSVar("AttachAIValid")) { // TODO combine with AttachAITgts
extra += " | AIValid$ " + c.getSVar("AttachAIValid");
}
String st = "SP$ Attach | ValidTgts$ Card.CanBeEnchantedBy,Player.CanBeEnchantedBy | TgtZone$ Battlefield,Graveyard | TgtPrompt$ Select target " + desc + extra;
SpellAbility sa = AbilityFactory.getAbility(st, c);
sa.setIntrinsic(true);
sa.setCardState(c.getCurrentState());
c.addSpellAbility(sa);
} else if (c.isPermanent()) {
// this is the "default" spell for permanents like creatures and artifacts
SpellAbility sa = new SpellPermanent(c);
sa.setCardState(c.getCurrentState());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ public static boolean playerHasProperty(Player player, String property, Player s
if (!player.hasBlessing()) {
return false;
}
} else if (property.equals("CanBeEnchantedBy")) {
if (!player.canBeAttached(source, null)) {
return false;
}
} else if (property.startsWith("damageDoneSingleSource")) {
String props = property.split(" ")[1];
List<Integer> sourceDmg = game.getDamageDoneThisTurn(null, false, "Card.YouCtrl", null, source, sourceController, spellAbility);
Expand Down
4 changes: 2 additions & 2 deletions forge-gui/res/cardsfolder/a/abduction.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Name:Abduction
ManaCost:2 U U
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | ValidTgts$ Creature | AILogic$ GainControl
K:Enchant:Creature
SVar:AttachAILogic:GainControl
S:Mode$ Continuous | Affected$ Card.EnchantedBy | GainControl$ You | Description$ You control enchanted creature.
T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigUntap | TriggerDescription$ When CARDNAME enters, untap enchanted creature.
SVar:TrigUntap:DB$ Untap | Defined$ Enchanted
Expand Down
4 changes: 2 additions & 2 deletions forge-gui/res/cardsfolder/a/aboshans_desire.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Name:Aboshan's Desire
ManaCost:U
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | ValidTgts$ Creature | AILogic$ Pump
K:Enchant:Creature
SVar:AttachAILogic:Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Flying | Description$ Enchanted creature has flying.
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Shroud | Condition$ Threshold | Description$ Threshold — Enchanted creature has shroud as long as seven or more cards are in your graveyard. (It can't be the target of spells or abilities.)
Oracle:Enchant creature\nEnchanted creature has flying.\nThreshold — Enchanted creature has shroud as long as seven or more cards are in your graveyard. (It can't be the target of spells or abilities.)
4 changes: 2 additions & 2 deletions forge-gui/res/cardsfolder/a/abundant_growth.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Name:Abundant Growth
ManaCost:G
Types:Enchantment Aura
K:Enchant land
A:SP$ Attach | ValidTgts$ Land | AILogic$ Pump
K:Enchant:Land
SVar:AttachAILogic:Pump
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ FreeCard | TriggerDescription$ When CARDNAME enters, draw a card.
SVar:FreeCard:DB$ Draw | Defined$ You | NumCards$ 1
S:Mode$ Continuous | Affected$ Card.EnchantedBy | AddAbility$ AbundantGrowthTap | Description$ Enchanted land has "{T}: Add one mana of any color."
Expand Down
4 changes: 2 additions & 2 deletions forge-gui/res/cardsfolder/a/abzan_runemark.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Name:Abzan Runemark
ManaCost:2 W
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | ValidTgts$ Creature | AILogic$ Pump
K:Enchant:Creature
SVar:AttachAILogic:Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | Description$ Enchanted creature gets +2/+2.
S:Mode$ Continuous | Affected$ Card.EnchantedBy | AddKeyword$ Vigilance | IsPresent$ Permanent.Black+YouCtrl,Permanent.Green+YouCtrl | Description$ Enchanted creature has vigilance as long as you control a black or green permanent.
SVar:BuffedBy:Permanent.Black,Permanent.Green
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ Name:Infectious Curse
ManaCost:no cost
Colors:black
Types:Enchantment Aura Curse
K:Enchant player
A:SP$ Attach | ValidTgts$ Player
K:Enchant:Player
S:Mode$ ReduceCost | ValidTarget$ Player.EnchantedBy | Activator$ You | Type$ Spell | Amount$ 1 | Description$ Spells you cast that target enchanted player cost {1} less to cast.
T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player.EnchantedBy | TriggerZones$ Battlefield | Execute$ TrigDrain | TriggerDescription$ At the beginning of enchanted player's upkeep, that player loses 1 life and you gain 1 life.
SVar:TrigDrain:DB$ LoseLife | Defined$ TriggeredPlayer | LifeAmount$ 1 | SubAbility$ DBGainLife
Expand Down
4 changes: 2 additions & 2 deletions forge-gui/res/cardsfolder/a/acquired_mutation.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Name:Acquired Mutation
ManaCost:2 R
Types:Enchantment Aura
K:Enchant creature
A:SP$ Attach | ValidTgts$ Creature | AILogic$ Pump
K:Enchant:Creature
SVar:AttachAILogic:Pump
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | Goad$ True | Description$ Enchanted creature gets +2/+2 and is goaded. (It attacks each combat if able and attacks a player other than you if able.)
T:Mode$ Attacks | ValidCard$ Creature.EnchantedBy | Execute$ TrigRadiation | TriggerDescription$ Whenever enchanted creature attacks, defending player gets two rad counters.
SVar:TrigRadiation:DB$ Radiation | Defined$ TriggeredDefendingPlayer | Num$ 2
Expand Down
4 changes: 2 additions & 2 deletions forge-gui/res/cardsfolder/a/aerial_modification.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Name:Aerial Modification
ManaCost:4 W
Types:Enchantment Aura
K:Enchant creature or Vehicle
A:SP$ Attach | ValidTgts$ Creature,Vehicle | AILogic$ Animate
K:Enchant:Creature,Vehicle:creature or Vehicle
SVar:AttachAILogic:Animate
S:Mode$ Continuous | Affected$ Vehicle.AttachedBy | AddType$ Creature | Description$ As long as enchanted permanent is a Vehicle, it's a creature in addition to its other types.
S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | AddKeyword$ Flying | Description$ Enchanted creature gets +2/+2 and has flying.
Oracle:Enchant creature or Vehicle\nAs long as enchanted permanent is a Vehicle, it's a creature in addition to its other types.\nEnchanted creature gets +2/+2 and has flying.
Loading