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/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/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/LifeGainEffect.java -text
forge-game/src/main/java/forge/game/ability/effects/LifeLoseEffect.java -text

View File

@@ -1,6 +1,5 @@
package forge.ai;
import java.util.EnumMap;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
@@ -13,7 +12,7 @@ import forge.util.ReflectionUtil;
public enum SpellApiToAi {
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)
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.GenericChoice, ChooseGenericEffectAi.class)
.put(ApiType.Goad, GoadAi.class)
.put(ApiType.Haunt, HauntAi.class)
.put(ApiType.LoseLife, LifeLoseAi.class)
.put(ApiType.LosesGame, GameLossAi.class)
.put(ApiType.Mana, ManaEffectAi.class)
@@ -149,7 +149,6 @@ public enum SpellApiToAi {
.put(ApiType.InternalEtbReplacement, CanPlayAsDrawbackAi.class)
.put(ApiType.InternalLegendaryRule, LegendaryRuleAi.class)
.put(ApiType.InternalHaunt, HauntAi.class)
.put(ApiType.InternalIgnoreEffect, CannotPlayAi.class)
.build());

View File

@@ -1,29 +1,35 @@
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 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 {
/* (non-Javadoc)
* @see forge.card.ability.SpellAbilityAi#canPlayAI(forge.game.player.Player, forge.card.spellability.SpellAbility)
*/
@Override
protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) {
return false; // should not get here
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
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());
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.util.FileSection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
/**
* <p>
* AbilityFactory class.
@@ -354,7 +355,7 @@ public final class AbilityFactory {
}
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")) {
origin = ZoneType.listValueOf(params.get("Origin"));
}

View File

@@ -5,7 +5,8 @@ import forge.game.ability.effects.*;
import forge.util.ReflectionUtil;
import java.util.Map;
import java.util.TreeMap;
import com.google.common.collect.Maps;
/**
* TODO: Write javadoc for this type.
@@ -69,11 +70,12 @@ public enum ApiType {
Fight (FightEffect.class),
FlipACoin (FlipCoinEffect.class),
Fog (FogEffect.class),
Goad (GoadEffect.class),
GainControl (ControlGainEffect.class),
GainLife (LifeGainEffect.class),
GainOwnership (OwnershipGainEffect.class),
GenericChoice (ChooseGenericEffect.class),
Goad (GoadEffect.class),
Haunt (HauntEffect.class),
LookAt (LookAtEffect.class),
LoseLife (LifeLoseEffect.class),
LosesGame (GameLossEffect.class),
@@ -146,14 +148,13 @@ public enum ApiType {
InternalEtbReplacement (ETBReplacementEffect.class),
InternalLegendaryRule (CharmEffect.class),
InternalHaunt (CharmEffect.class),
InternalIgnoreEffect (CharmEffect.class);
private final SpellAbilityEffect instanceEffect;
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, 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
for (final Trigger trig : state.getTriggers()) {
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
for (final Trigger trig : state.getTriggers()) {
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) {
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);
if (c != null) {
if (c != null && update) {
c.setHaunting(this);
}
}
public final void addHauntedBy(Card c) {
addHauntedBy(c, true);
}
public final void removeHauntedBy(Card c) {
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.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import forge.GameCommand;
@@ -59,7 +60,6 @@ import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.Ability;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.OptionalCost;
@@ -2230,7 +2230,8 @@ public class CardFactoryUtil {
card.getSpellAbilities().getFirst().setDelve(true);
}
else if (keyword.startsWith("Haunt")) {
setupHauntSpell(card);
addSpellAbility(keyword, card, null);
addTriggerAbility(keyword, card, null);
}
else if (keyword.startsWith("Annihilator")) {
addTriggerAbility(keyword, card, null);
@@ -2836,6 +2837,100 @@ public class CardFactoryUtil {
if (!intrinsic) {
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")) {
final String trigStr = "Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True"
+ "| Secondary$ True | TriggerZones$ Battlefield | TriggerDescription$ Ingest ("
@@ -3476,6 +3571,19 @@ public class CardFactoryUtil {
}
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")) {
final String[] k = keyword.split(":");
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.
* @param card

View File

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

View File

@@ -620,26 +620,6 @@ public class CardView extends GameEntityView {
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();
if (pairedWith != null) {
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.GameEntity;
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.phase.PhaseHandler;
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>
* phasesCheck.

View File

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

View File

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

View File

@@ -41,11 +41,8 @@ import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardFactoryUtil;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost;
import forge.game.event.EventValueChangeType;
import forge.game.event.GameEventCardStatsChanged;
@@ -58,7 +55,6 @@ import forge.game.player.PlayerController.ManaPaymentPurpose;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer;
import forge.game.spellability.Ability;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.OptionalCost;
@@ -527,10 +523,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
game.fireEvent(new GameEventSpellResolved(sa, thisHasFizzled));
finishResolving(sa, thisHasFizzled);
if (source.hasStartOfKeyword("Haunt") && !source.isCreature() && game.getZoneOf(source).is(ZoneType.Graveyard)) {
handleHauntForNonPermanents(sa);
}
if (isEmpty()) {
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?
@@ -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) {
// remove SA and card from the stack
removeCardFromStack(sa, fizzle);