CardFactoryUtil: make Haunt a better Effect, rework the Haunt Triggers in new format

Trigger now does has a helper to replace ABILITY in TriggerDescription with its ability
This commit is contained in:
Hanmac
2016-11-09 15:26:26 +00:00
parent 125ee2315e
commit ceb3fae152
14 changed files with 250 additions and 237 deletions

1
.gitattributes vendored
View File

@@ -402,6 +402,7 @@ forge-game/src/main/java/forge/game/ability/effects/FogEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/GameLossEffect.java -text forge-game/src/main/java/forge/game/ability/effects/GameLossEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/GameWinEffect.java -text forge-game/src/main/java/forge/game/ability/effects/GameWinEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/GoadEffect.java -text svneol=unset#text/plain forge-game/src/main/java/forge/game/ability/effects/GoadEffect.java -text svneol=unset#text/plain
forge-game/src/main/java/forge/game/ability/effects/HauntEffect.java -text svneol=unset#text/plain
forge-game/src/main/java/forge/game/ability/effects/LifeExchangeEffect.java -text forge-game/src/main/java/forge/game/ability/effects/LifeExchangeEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/LifeGainEffect.java -text forge-game/src/main/java/forge/game/ability/effects/LifeGainEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/LifeLoseEffect.java -text forge-game/src/main/java/forge/game/ability/effects/LifeLoseEffect.java -text

View File

@@ -1,6 +1,5 @@
package forge.ai; package forge.ai;
import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@@ -13,7 +12,7 @@ import forge.util.ReflectionUtil;
public enum SpellApiToAi { public enum SpellApiToAi {
Converter; Converter;
private final Map<ApiType, SpellAbilityAi> apiToInstance = new EnumMap<>(ApiType.class); private final Map<ApiType, SpellAbilityAi> apiToInstance = Maps.newEnumMap(ApiType.class);
// Do the extra copy to make an actual EnumMap (faster) // Do the extra copy to make an actual EnumMap (faster)
private final Map<ApiType, Class<? extends SpellAbilityAi>> apiToClass = Maps.newEnumMap(ImmutableMap private final Map<ApiType, Class<? extends SpellAbilityAi>> apiToClass = Maps.newEnumMap(ImmutableMap
@@ -79,6 +78,7 @@ public enum SpellApiToAi {
.put(ApiType.GainOwnership, CannotPlayAi.class) .put(ApiType.GainOwnership, CannotPlayAi.class)
.put(ApiType.GenericChoice, ChooseGenericEffectAi.class) .put(ApiType.GenericChoice, ChooseGenericEffectAi.class)
.put(ApiType.Goad, GoadAi.class) .put(ApiType.Goad, GoadAi.class)
.put(ApiType.Haunt, HauntAi.class)
.put(ApiType.LoseLife, LifeLoseAi.class) .put(ApiType.LoseLife, LifeLoseAi.class)
.put(ApiType.LosesGame, GameLossAi.class) .put(ApiType.LosesGame, GameLossAi.class)
.put(ApiType.Mana, ManaEffectAi.class) .put(ApiType.Mana, ManaEffectAi.class)
@@ -149,7 +149,6 @@ public enum SpellApiToAi {
.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class) .put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class)
.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class) .put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class)
.put(ApiType.InternalHaunt, HauntAi.class)
.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class) .put(ApiType.InternalIgnoreEffect, CannotPlayAi.class)
.build()); .build());

View File

@@ -1,29 +1,35 @@
package forge.ai.ability; package forge.ai.ability;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import java.util.List; import java.util.List;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType;
public class HauntAi extends SpellAbilityAi { public class HauntAi extends SpellAbilityAi {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override @Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
return false; // should not get here final Card card = sa.getHostCard();
final Game game = ai.getGame();
if (sa.usesTargeting() && !card.isToken()) {
final List<Card> creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield),
CardPredicates.Presets.CREATURES);
// nothing to haunt
if (creats.isEmpty()) {
return false;
} }
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable<Card> creats, boolean isOptional, Player targetedPlayer) {
final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents()); final List<Card> oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents());
return ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats); sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats));
}
return true;
} }
} }

View File

@@ -25,10 +25,11 @@ import forge.game.spellability.*;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.FileSection; import forge.util.FileSection;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.common.collect.Lists;
/** /**
* <p> * <p>
* AbilityFactory class. * AbilityFactory class.
@@ -354,7 +355,7 @@ public final class AbilityFactory {
} }
public static final void adjustChangeZoneTarget(final Map<String, String> params, final SpellAbility sa) { public static final void adjustChangeZoneTarget(final Map<String, String> params, final SpellAbility sa) {
List<ZoneType> origin = new ArrayList<ZoneType>(); List<ZoneType> origin = Lists.newArrayList();
if (params.containsKey("Origin")) { if (params.containsKey("Origin")) {
origin = ZoneType.listValueOf(params.get("Origin")); origin = ZoneType.listValueOf(params.get("Origin"));
} }

View File

@@ -5,7 +5,8 @@ import forge.game.ability.effects.*;
import forge.util.ReflectionUtil; import forge.util.ReflectionUtil;
import java.util.Map; import java.util.Map;
import java.util.TreeMap;
import com.google.common.collect.Maps;
/** /**
* TODO: Write javadoc for this type. * TODO: Write javadoc for this type.
@@ -69,11 +70,12 @@ public enum ApiType {
Fight (FightEffect.class), Fight (FightEffect.class),
FlipACoin (FlipCoinEffect.class), FlipACoin (FlipCoinEffect.class),
Fog (FogEffect.class), Fog (FogEffect.class),
Goad (GoadEffect.class),
GainControl (ControlGainEffect.class), GainControl (ControlGainEffect.class),
GainLife (LifeGainEffect.class), GainLife (LifeGainEffect.class),
GainOwnership (OwnershipGainEffect.class), GainOwnership (OwnershipGainEffect.class),
GenericChoice (ChooseGenericEffect.class), GenericChoice (ChooseGenericEffect.class),
Goad (GoadEffect.class),
Haunt (HauntEffect.class),
LookAt (LookAtEffect.class), LookAt (LookAtEffect.class),
LoseLife (LifeLoseEffect.class), LoseLife (LifeLoseEffect.class),
LosesGame (GameLossEffect.class), LosesGame (GameLossEffect.class),
@@ -146,14 +148,13 @@ public enum ApiType {
InternalEtbReplacement (ETBReplacementEffect.class), InternalEtbReplacement (ETBReplacementEffect.class),
InternalLegendaryRule (CharmEffect.class), InternalLegendaryRule (CharmEffect.class),
InternalHaunt (CharmEffect.class),
InternalIgnoreEffect (CharmEffect.class); InternalIgnoreEffect (CharmEffect.class);
private final SpellAbilityEffect instanceEffect; private final SpellAbilityEffect instanceEffect;
private final Class<? extends SpellAbilityEffect> clsEffect; private final Class<? extends SpellAbilityEffect> clsEffect;
private static final Map<String, ApiType> allValues = new TreeMap<String, ApiType>(String.CASE_INSENSITIVE_ORDER); private static final Map<String, ApiType> allValues = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
ApiType(Class<? extends SpellAbilityEffect> clsEf) { this(clsEf, true); } ApiType(Class<? extends SpellAbilityEffect> clsEf) { this(clsEf, true); }
ApiType(Class<? extends SpellAbilityEffect> clsEf, final boolean isStateLess) { ApiType(Class<? extends SpellAbilityEffect> clsEf, final boolean isStateLess) {

View File

@@ -0,0 +1,23 @@
package forge.game.ability.effects;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.spellability.SpellAbility;
public class HauntEffect extends SpellAbilityEffect {
@Override
public void resolve(SpellAbility sa) {
final Card card = sa.getHostCard();
if (sa.usesTargeting() && !card.isToken()) {
// haunt target but only if card is no token
final Card copy = card.getGame().getAction().exile(card);
sa.getTargets().getFirstTargetedCard().addHauntedBy(copy);
} else if (!sa.usesTargeting() && card.getHaunting() != null) {
// unhaunt
card.getHaunting().removeHauntedBy(card);
card.setHaunting(null);
}
}
}

View File

@@ -1686,7 +1686,7 @@ public class Card extends GameEntity implements Comparable<Card> {
// Triggered abilities // Triggered abilities
for (final Trigger trig : state.getTriggers()) { for (final Trigger trig : state.getTriggers()) {
if (!trig.isSecondary()) { if (!trig.isSecondary()) {
sb.append(trig.toString().replaceAll("\\\\r\\\\n", "\r\n")).append("\r\n"); sb.append(trig.replaceAbilityText(trig.toString(), null).replaceAll("\\\\r\\\\n", "\r\n")).append("\r\n");
} }
} }
@@ -1793,7 +1793,7 @@ public class Card extends GameEntity implements Comparable<Card> {
// Triggered abilities // Triggered abilities
for (final Trigger trig : state.getTriggers()) { for (final Trigger trig : state.getTriggers()) {
if (!trig.isSecondary()) { if (!trig.isSecondary()) {
sb.append(trig.toString()).append("\r\n"); sb.append(trig.replaceAbilityText(trig.toString(), null)).append("\r\n");
} }
} }
@@ -6269,12 +6269,15 @@ public class Card extends GameEntity implements Comparable<Card> {
public final boolean isHauntedBy(Card c) { public final boolean isHauntedBy(Card c) {
return FCollection.hasElement(hauntedBy, c); return FCollection.hasElement(hauntedBy, c);
} }
public final void addHauntedBy(Card c) { public final void addHauntedBy(Card c, final boolean update) {
hauntedBy = view.addCard(hauntedBy, c, TrackableProperty.HauntedBy); hauntedBy = view.addCard(hauntedBy, c, TrackableProperty.HauntedBy);
if (c != null) { if (c != null && update) {
c.setHaunting(this); c.setHaunting(this);
} }
} }
public final void addHauntedBy(Card c) {
addHauntedBy(c, true);
}
public final void removeHauntedBy(Card c) { public final void removeHauntedBy(Card c) {
hauntedBy = view.removeCard(hauntedBy, c, TrackableProperty.HauntedBy); hauntedBy = view.removeCard(hauntedBy, c, TrackableProperty.HauntedBy);
} }

View File

@@ -31,6 +31,7 @@ import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Predicates; import com.google.common.base.Predicates;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import forge.GameCommand; import forge.GameCommand;
@@ -59,7 +60,6 @@ import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.Ability;
import forge.game.spellability.AbilityStatic; import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCost; import forge.game.spellability.OptionalCost;
@@ -2230,7 +2230,8 @@ public class CardFactoryUtil {
card.getSpellAbilities().getFirst().setDelve(true); card.getSpellAbilities().getFirst().setDelve(true);
} }
else if (keyword.startsWith("Haunt")) { else if (keyword.startsWith("Haunt")) {
setupHauntSpell(card); addSpellAbility(keyword, card, null);
addTriggerAbility(keyword, card, null);
} }
else if (keyword.startsWith("Annihilator")) { else if (keyword.startsWith("Annihilator")) {
addTriggerAbility(keyword, card, null); addTriggerAbility(keyword, card, null);
@@ -2836,6 +2837,100 @@ public class CardFactoryUtil {
if (!intrinsic) { if (!intrinsic) {
kws.addTrigger(cardTrigger); kws.addTrigger(cardTrigger);
} }
} else if (keyword.startsWith("Haunt")) {
final String[] k = keyword.split(":");
final String hauntSVarName = k[1];
List<Trigger> cardTriggers = Lists.newArrayList();
final StringBuilder sb = new StringBuilder();
if (card.isCreature()) {
sb.append("When ").append(card.getName());
sb.append(" enters the battlefield or the creature it haunts dies, ");
} else {
sb.append("When the creature ").append(card.getName());
sb.append(" haunts dies, ");
}
// use new feature to get the Ability
sb.append("ABILITY");
final String hauntDescription = sb.toString();
// Second, create the trigger that runs when the haunted creature dies
final StringBuilder sbDies = new StringBuilder();
sbDies.append("Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | TriggerZones$ Exile |");
sbDies.append("ValidCard$ Creature.HauntedBy | Execute$ ").append(hauntSVarName);
sbDies.append(" | TriggerDescription$ ").append(hauntDescription);
final Trigger hauntedDies = TriggerHandler.parseTrigger(sbDies.toString(), card, intrinsic);
// Fourth, create a trigger that removes the haunting status if the
// haunter leaves the exile
final StringBuilder sbUnExiled = new StringBuilder();
sbUnExiled.append("Mode$ ChangesZone | Origin$ Exile | Destination$ Any | ");
sbUnExiled.append("ValidCard$ Card.Self | Static$ True | Secondary$ True | ");
sbUnExiled.append("TriggerDescription$ Blank");
final Trigger haunterUnExiled = TriggerHandler.parseTrigger(sbUnExiled.toString(), card,
intrinsic);
final SpellAbility unhaunt = AbilityFactory.getAbility("DB$ Haunt", card);
haunterUnExiled.setOverridingAbility(unhaunt);
cardTriggers.add(card.addTrigger(haunterUnExiled));
// Trigger for when the haunted creature leaves the battlefield
final StringBuilder sbHauntRemoved = new StringBuilder();
sbUnExiled.append("Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ");
sbUnExiled.append("ValidCard$ Creature.HauntedBy | Static$ True | Secondary$ True | ");
sbUnExiled.append("TriggerDescription$ Blank");
final Trigger trigHauntRemoved = TriggerHandler.parseTrigger(sbHauntRemoved.toString(), card,
intrinsic);
trigHauntRemoved.setOverridingAbility(unhaunt);
cardTriggers.add(card.addTrigger(trigHauntRemoved));
// Fifth, add all triggers and abilities to the card.
if (card.isCreature()) {
// Third, create the trigger that runs when the haunting creature
// enters the battlefield
final StringBuilder sbETB = new StringBuilder();
sbETB.append("Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ ");
sbETB.append(hauntSVarName).append(" | Secondary$ True | TriggerDescription$ ");
sbETB.append(hauntDescription);
final Trigger haunterETB = TriggerHandler.parseTrigger(sbETB.toString(), card, intrinsic);
cardTriggers.add(card.addTrigger(haunterETB));
}
// First, create trigger that runs when the haunter goes to the
// graveyard
final StringBuilder sbHaunter = new StringBuilder();
sbHaunter.append("Mode$ ChangesZone | Origin$ ");
sbHaunter.append(card.isCreature() ? "Battlefield" : "Stack");
sbHaunter.append(" | Destination$ Graveyard | ValidCard$ Card.Self");
sbHaunter.append(" | Static$ True | Secondary$ True | TriggerDescription$ Blank");
final Trigger haunterDies = TriggerHandler.parseTrigger(sbHaunter.toString(), card, intrinsic);
final String hauntDiesEffectStr = "DB$ Haunt | ValidTgts$ Creature | TgtPrompt$ Choose target creature to haunt";
final SpellAbility hauntDiesAbility = AbilityFactory.getAbility(hauntDiesEffectStr, card);
haunterDies.setOverridingAbility(hauntDiesAbility);
cardTriggers.add(card.addTrigger(haunterDies));
cardTriggers.add(card.addTrigger(hauntedDies));
if (!intrinsic) {
for (final Trigger cardTrigger : cardTriggers) {
kws.addTrigger(cardTrigger);
}
}
} else if (keyword.equals("Ingest")) { } else if (keyword.equals("Ingest")) {
final String trigStr = "Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True" final String trigStr = "Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True"
+ "| Secondary$ True | TriggerZones$ Battlefield | TriggerDescription$ Ingest (" + "| Secondary$ True | TriggerZones$ Battlefield | TriggerDescription$ Ingest ("
@@ -3476,6 +3571,19 @@ public class CardFactoryUtil {
} }
card.addSpellAbility(newSA); card.addSpellAbility(newSA);
} else if (keyword.startsWith("Haunt")) {
if (!card.isCreature() && intrinsic) {
final String[] k = keyword.split(":");
final String hauntSVarName = k[1];
// no nice way to get the cost
String abString = card.getSVar(hauntSVarName).replace("DB$", "SP$");
abString += " | Cost$ 0 | StackDescription$ SpellDescription";
final SpellAbility sa = AbilityFactory.getAbility(abString, card);
sa.setPayCosts(new Cost(card.getManaCost(), false));
card.addSpellAbility(sa);
}
} else if (keyword.startsWith("Monstrosity")) { } else if (keyword.startsWith("Monstrosity")) {
final String[] k = keyword.split(":"); final String[] k = keyword.split(":");
final String magnitude = k[1]; final String magnitude = k[1];
@@ -3797,129 +3905,6 @@ public class CardFactoryUtil {
} }
} }
/**
* TODO: Write javadoc for this method.
* @param card
*/
private static void setupHauntSpell(final Card card) {
final int hauntPos = card.getKeywordPosition("Haunt");
final String[] splitKeyword = card.getKeywords().get(hauntPos).split(":");
final String hauntSVarName = splitKeyword[1];
final String abilityDescription = splitKeyword[2];
final String hauntAbilityDescription = abilityDescription.substring(0, 1).toLowerCase()
+ abilityDescription.substring(1);
String hauntDescription;
if (card.isCreature()) {
final StringBuilder sb = new StringBuilder();
sb.append("When ").append(card.getName());
sb.append(" enters the battlefield or the creature it haunts dies, ");
sb.append(hauntAbilityDescription);
hauntDescription = sb.toString();
} else {
final StringBuilder sb = new StringBuilder();
sb.append("When the creature ").append(card.getName());
sb.append(" haunts dies, ").append(hauntAbilityDescription);
hauntDescription = sb.toString();
}
card.getKeywords().remove(hauntPos);
// First, create trigger that runs when the haunter goes to the
// graveyard
final StringBuilder sbHaunter = new StringBuilder();
sbHaunter.append("Mode$ ChangesZone | Origin$ Battlefield | ");
sbHaunter.append("Destination$ Graveyard | ValidCard$ Card.Self | ");
sbHaunter.append("Static$ True | Secondary$ True | TriggerDescription$ Blank");
final Trigger haunterDies = TriggerHandler.parseTrigger(sbHaunter.toString(), card, true);
final Ability haunterDiesWork = new Ability(card, ManaCost.ZERO) {
@Override
public void resolve() {
this.getTargets().getFirstTargetedCard().addHauntedBy(card);
card.getGame().getAction().exile(card);
}
};
haunterDiesWork.setDescription(hauntDescription);
haunterDiesWork.setTargetRestrictions(new TargetRestrictions(null, new String[]{"Creature"}, "1", "1")); // not null to make stack preserve targets set
final Ability haunterDiesSetup = new Ability(card, ManaCost.ZERO) {
@Override
public void resolve() {
final Game game = card.getGame();
this.setActivatingPlayer(card.getController());
haunterDiesWork.setActivatingPlayer(card.getController());
CardCollection allCreatures = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
final CardCollection creats = CardLists.getTargetableCards(allCreatures, haunterDiesWork);
if (creats.isEmpty()) {
return;
}
final Card toHaunt = card.getController().getController().chooseSingleEntityForEffect(creats, new SpellAbility.EmptySa(ApiType.InternalHaunt, card), "Choose target creature to haunt.");
haunterDiesWork.setTargetCard(toHaunt);
haunterDiesWork.setActivatingPlayer(card.getController());
game.getStack().add(haunterDiesWork);
}
};
haunterDies.setOverridingAbility(haunterDiesSetup);
// Second, create the trigger that runs when the haunted creature dies
final StringBuilder sbDies = new StringBuilder();
sbDies.append("Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ");
sbDies.append("ValidCard$ Creature.HauntedBy | Execute$ ").append(hauntSVarName);
sbDies.append(" | TriggerDescription$ ").append(hauntDescription);
final Trigger hauntedDies = forge.game.trigger.TriggerHandler.parseTrigger(sbDies.toString(), card, true);
// Third, create the trigger that runs when the haunting creature
// enters the battlefield
final StringBuilder sbETB = new StringBuilder();
sbETB.append("Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ ");
sbETB.append(hauntSVarName).append(" | Secondary$ True | TriggerDescription$ ");
sbETB.append(hauntDescription);
final Trigger haunterETB = forge.game.trigger.TriggerHandler.parseTrigger(sbETB.toString(), card, true);
// Fourth, create a trigger that removes the haunting status if the
// haunter leaves the exile
final StringBuilder sbUnExiled = new StringBuilder();
sbUnExiled.append("Mode$ ChangesZone | Origin$ Exile | Destination$ Any | ");
sbUnExiled.append("ValidCard$ Card.Self | Static$ True | Secondary$ True | ");
sbUnExiled.append("TriggerDescription$ Blank");
final Trigger haunterUnExiled = forge.game.trigger.TriggerHandler.parseTrigger(sbUnExiled.toString(), card,
true);
final Ability haunterUnExiledWork = new Ability(card, ManaCost.ZERO) {
@Override
public void resolve() {
if (card.getHaunting() != null) {
card.getHaunting().removeHauntedBy(card);
card.setHaunting(null);
}
}
};
haunterUnExiled.setOverridingAbility(haunterUnExiledWork);
// Fifth, add all triggers and abilities to the card.
if (card.isCreature()) {
card.addTrigger(haunterETB);
card.addTrigger(haunterDies);
} else {
final String abString = card.getSVar(hauntSVarName).replace("AB$", "SP$")
+ " | SpellDescription$ " + abilityDescription;
final SpellAbility sa = AbilityFactory.getAbility(abString, card);
sa.setPayCosts(new Cost(card.getManaCost(), false));
card.addSpellAbility(sa);
}
card.addTrigger(hauntedDies);
card.addTrigger(haunterUnExiled);
}
/** /**
* TODO: Write javadoc for this method. * TODO: Write javadoc for this method.
* @param card * @param card

View File

@@ -294,7 +294,7 @@ public final class CardUtil {
newCopy.setClones(in.getClones()); newCopy.setClones(in.getClones());
newCopy.setHaunting(in.getHaunting()); newCopy.setHaunting(in.getHaunting());
for (final Card haunter : in.getHauntedBy()) { for (final Card haunter : in.getHauntedBy()) {
newCopy.addHauntedBy(haunter); newCopy.addHauntedBy(haunter, false);
} }
for (final Object o : in.getRemembered()) { for (final Object o : in.getRemembered()) {
newCopy.addRemembered(o); newCopy.addRemembered(o);

View File

@@ -620,26 +620,6 @@ public class CardView extends GameEntityView {
sb.append("]\r\n"); sb.append("]\r\n");
} }
Iterable<CardView> hauntedBy = getHauntedBy();
if (hauntedBy != null) {
sb.append("Haunted by: ");
boolean needDelim = false;
for (final CardView c : hauntedBy) {
if (needDelim) {
sb.append(",");
}
else { needDelim = false; }
sb.append(c);
}
sb.append("\r\n");
}
CardView haunting = getHaunting();
if (haunting != null) {
sb.append("Haunting: ").append(haunting);
sb.append("\r\n");
}
CardView pairedWith = getPairedWith(); CardView pairedWith = getPairedWith();
if (pairedWith != null) { if (pairedWith != null) {
sb.append("\r\n \r\nPaired With: ").append(pairedWith); sb.append("\r\n \r\nPaired With: ").append(pairedWith);

View File

@@ -20,6 +20,9 @@ package forge.game.trigger;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.TriggerReplacementBase; import forge.game.TriggerReplacementBase;
import forge.game.ability.AbilityFactory;
import forge.game.ability.ApiType;
import forge.game.ability.effects.CharmEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
@@ -158,6 +161,51 @@ public abstract class Trigger extends TriggerReplacementBase {
} }
} }
public final String replaceAbilityText(final String desc, SpellAbility sa) {
String result = desc;
// this function is for ABILITY
if (!result.contains("ABILITY")) {
return result;
}
// it has already sa, used in WrappedAbility
if (sa == null) {
sa = getOverridingAbility();
}
if (sa == null && this.mapParams.containsKey("Execute")) {
sa = AbilityFactory.getAbility(hostCard, this.mapParams.get("Execute"));
}
if (sa != null) {
String saDesc;
// if sa is a wrapper, get the Wrapped Ability
if (sa.isWrapper()) {
final WrappedAbility wa = (WrappedAbility) sa;
sa = wa.getWrappedAbility();
// wrapped Charm spells are special,
// only get the selected abilities
if (ApiType.Charm.equals(sa.getApi())) {
saDesc = sa.getStackDescription();
} else {
saDesc = sa.getDescription();
}
} else if (ApiType.Charm.equals(sa.getApi())) {
// use special formating, can be used in Card Description
saDesc = CharmEffect.makeFormatedDescription(sa);
} else {
saDesc = sa.getDescription();
}
if (!saDesc.isEmpty()) {
// string might have leading whitespace
saDesc = saDesc.trim();
saDesc = saDesc.substring(0, 1).toLowerCase() + saDesc.substring(1);
result = result.replace("ABILITY", saDesc);
}
}
return result;
}
/** /**
* <p> * <p>
* phasesCheck. * phasesCheck.

View File

@@ -616,7 +616,7 @@ public class TriggerHandler {
wrapperAbility.setTrigger(true); wrapperAbility.setTrigger(true);
wrapperAbility.setMandatory(isMandatory); wrapperAbility.setMandatory(isMandatory);
//wrapperAbility.setDescription(wrapperAbility.getStackDescription()); //wrapperAbility.setDescription(wrapperAbility.getStackDescription());
wrapperAbility.setDescription(wrapperAbility.toUnsuppressedString()); //wrapperAbility.setDescription(wrapperAbility.toUnsuppressedString());
wrapperAbility.setLastStateBattlefield(game.getLastStateBattlefield()); wrapperAbility.setLastStateBattlefield(game.getLastStateBattlefield());
if (regtrig.isStatic()) { if (regtrig.isStatic()) {

View File

@@ -196,8 +196,8 @@ public class WrappedAbility extends Ability {
@Override @Override
public String getStackDescription() { public String getStackDescription() {
final StringBuilder sb = new StringBuilder(toUnsuppressedString()); final StringBuilder sb = new StringBuilder(regtrig.replaceAbilityText(toUnsuppressedString(), this));
if (this.getTargetRestrictions() != null) { if (usesTargeting()) {
sb.append(" (Targeting "); sb.append(" (Targeting ");
for (final GameObject o : this.getTargets().getTargets()) { for (final GameObject o : this.getTargets().getTargets()) {
sb.append(o.toString()); sb.append(o.toString());

View File

@@ -41,11 +41,8 @@ import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil; import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates; import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost; import forge.game.cost.Cost;
import forge.game.event.EventValueChangeType; import forge.game.event.EventValueChangeType;
import forge.game.event.GameEventCardStatsChanged; import forge.game.event.GameEventCardStatsChanged;
@@ -58,7 +55,6 @@ import forge.game.player.PlayerController.ManaPaymentPurpose;
import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.Ability;
import forge.game.spellability.AbilitySub; import forge.game.spellability.AbilitySub;
import forge.game.spellability.AbilityStatic; import forge.game.spellability.AbilityStatic;
import forge.game.spellability.OptionalCost; import forge.game.spellability.OptionalCost;
@@ -527,10 +523,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
game.fireEvent(new GameEventSpellResolved(sa, thisHasFizzled)); game.fireEvent(new GameEventSpellResolved(sa, thisHasFizzled));
finishResolving(sa, thisHasFizzled); finishResolving(sa, thisHasFizzled);
if (source.hasStartOfKeyword("Haunt") && !source.isCreature() && game.getZoneOf(source).is(ZoneType.Graveyard)) {
handleHauntForNonPermanents(sa);
}
if (isEmpty()) { if (isEmpty()) {
game.copyLastState(); game.copyLastState();
// FIXME: assuming that if the stack is empty, no reason to hold on to old LKI data (everything is a new object). Is this correct? // FIXME: assuming that if the stack is empty, no reason to hold on to old LKI data (everything is a new object). Is this correct?
@@ -538,32 +530,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
} }
} }
private void handleHauntForNonPermanents(final SpellAbility sa) {
final Card source = sa.getHostCard();
final CardCollection creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), Presets.CREATURES);
final Ability haunterDiesWork = new Ability(source, ManaCost.ZERO) {
@Override
public void resolve() {
game.getAction().exile(source);
getTargetCard().addHauntedBy(source);
}
};
for (int i = 0; i < creats.size(); i++) {
haunterDiesWork.setActivatingPlayer(sa.getActivatingPlayer());
if (!creats.get(i).canBeTargetedBy(haunterDiesWork)) {
creats.remove(i);
i--;
}
}
if (!creats.isEmpty()) {
haunterDiesWork.setDescription("");
haunterDiesWork.setTargetRestrictions(new TargetRestrictions("", "Creature".split(" "), "1", "1"));
final Card targetCard = source.getController().getController().chooseSingleEntityForEffect(creats, new SpellAbility.EmptySa(ApiType.InternalHaunt, source), "Choose target creature to haunt.");
haunterDiesWork.setTargetCard(targetCard);
add(haunterDiesWork);
}
}
private final void finishResolving(final SpellAbility sa, final boolean fizzle) { private final void finishResolving(final SpellAbility sa, final boolean fizzle) {
// remove SA and card from the stack // remove SA and card from the stack
removeCardFromStack(sa, fizzle); removeCardFromStack(sa, fizzle);