mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
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:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user