From ceb3fae1523abc07adf40322d2c6eb5bb9cfd614 Mon Sep 17 00:00:00 2001 From: Hanmac Date: Wed, 9 Nov 2016 15:26:26 +0000 Subject: [PATCH] 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 --- .gitattributes | 1 + .../src/main/java/forge/ai/SpellApiToAi.java | 5 +- .../main/java/forge/ai/ability/HauntAi.java | 44 ++-- .../forge/game/ability/AbilityFactory.java | 17 +- .../main/java/forge/game/ability/ApiType.java | 9 +- .../game/ability/effects/HauntEffect.java | 23 ++ .../src/main/java/forge/game/card/Card.java | 11 +- .../java/forge/game/card/CardFactoryUtil.java | 235 ++++++++---------- .../main/java/forge/game/card/CardUtil.java | 2 +- .../main/java/forge/game/card/CardView.java | 20 -- .../main/java/forge/game/trigger/Trigger.java | 48 ++++ .../forge/game/trigger/TriggerHandler.java | 2 +- .../forge/game/trigger/WrappedAbility.java | 36 +-- .../main/java/forge/game/zone/MagicStack.java | 34 --- 14 files changed, 250 insertions(+), 237 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/ability/effects/HauntEffect.java diff --git a/.gitattributes b/.gitattributes index 9ebb01b4233..2668e0a38b3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index 0d1af06e970..3323da15e9c 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -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 apiToInstance = new EnumMap<>(ApiType.class); + private final Map apiToInstance = Maps.newEnumMap(ApiType.class); // Do the extra copy to make an actual EnumMap (faster) private final Map> 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()); diff --git a/forge-ai/src/main/java/forge/ai/ability/HauntAi.java b/forge-ai/src/main/java/forge/ai/ability/HauntAi.java index 805f94168a0..4cbb50a4f10 100644 --- a/forge-ai/src/main/java/forge/ai/ability/HauntAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/HauntAi.java @@ -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 creats = CardLists.filter(game.getCardsIn(ZoneType.Battlefield), + CardPredicates.Presets.CREATURES); - @Override - public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable creats, boolean isOptional, Player targetedPlayer) { - final List oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents()); - return ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats); - } + // nothing to haunt + if (creats.isEmpty()) { + return false; + } + final List oppCreats = CardLists.filterControlledBy(creats, ai.getOpponents()); + sa.getTargets().add(ComputerUtilCard.getWorstCreatureAI(oppCreats.isEmpty() ? creats : oppCreats)); + } + return true; + } } \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index 2732073b41d..7763a33821c 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -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; + /** *

* AbilityFactory class. @@ -199,14 +200,14 @@ public final class AbilityFactory { if (type != AbilityRecordType.SubAbility) { // SubAbilities don't have Costs or Cost // descriptors - sb.append(spellAbility.getCostDescription()); + sb.append(spellAbility.getCostDescription()); } sb.append(mapParams.get("SpellDescription")); spellAbility.setDescription(sb.toString()); } else if (api == ApiType.Charm) { - spellAbility.setDescription(CharmEffect.makeSpellDescription(spellAbility)); + spellAbility.setDescription(CharmEffect.makeSpellDescription(spellAbility)); } else { spellAbility.setDescription(""); } @@ -354,7 +355,7 @@ public final class AbilityFactory { } public static final void adjustChangeZoneTarget(final Map params, final SpellAbility sa) { - List origin = new ArrayList(); + List origin = Lists.newArrayList(); if (params.containsKey("Origin")) { origin = ZoneType.listValueOf(params.get("Origin")); } @@ -396,12 +397,12 @@ public final class AbilityFactory { } public static final SpellAbility buildEntwineAbility(final SpellAbility sa) { - final Card source = sa.getHostCard(); + final Card source = sa.getHostCard(); final String[] saChoices = sa.getParam("Choices").split(","); if (sa.getApi() != ApiType.Charm || saChoices.length != 2) throw new IllegalStateException("Entwine ability may be built only on charm cards"); final String ab = source.getSVar(saChoices[0]); - Map firstMap = getMapParams(ab); + Map firstMap = getMapParams(ab); AbilityRecordType firstType = AbilityRecordType.getRecordType(firstMap); ApiType firstApi = firstType.getApiTypeOf(firstMap); firstMap.put("StackDecription", firstMap.get("SpellDescription")); @@ -409,7 +410,7 @@ public final class AbilityFactory { SpellAbility entwineSA = getAbility(AbilityRecordType.Spell, firstApi, firstMap, new Cost(sa.getPayCosts().toSimpleString(), false), source); final String ab2 = source.getSVar(saChoices[1]); - Map secondMap = getMapParams(ab2); + Map secondMap = getMapParams(ab2); ApiType secondApi = firstType.getApiTypeOf(secondMap); secondMap.put("StackDecription", secondMap.get("SpellDescription")); secondMap.put("SpellDescription", ""); @@ -419,7 +420,7 @@ public final class AbilityFactory { entwineSA.setBasicSpell(false); entwineSA.setActivatingPlayer(sa.getActivatingPlayer()); entwineSA.setRestrictions(sa.getRestrictions()); - return entwineSA; + return entwineSA; } } // end class AbilityFactory diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 08d5be6897a..31bee3ba532 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -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 clsEffect; - private static final Map allValues = new TreeMap(String.CASE_INSENSITIVE_ORDER); + private static final Map allValues = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); ApiType(Class clsEf) { this(clsEf, true); } ApiType(Class clsEf, final boolean isStateLess) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/HauntEffect.java b/forge-game/src/main/java/forge/game/ability/effects/HauntEffect.java new file mode 100644 index 00000000000..d756519b4d4 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/HauntEffect.java @@ -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); + } + } + +} diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index c089ccfad1c..5d52b5fc6b2 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -1686,7 +1686,7 @@ public class Card extends GameEntity implements Comparable { // 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 { // 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 { 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); } diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index d63b2b255b7..75f60ecf3ca 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -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 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 diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index 2b8455bafb9..641ea9621b3 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -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); diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 5ed18679a15..620fb66293d 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -620,26 +620,6 @@ public class CardView extends GameEntityView { sb.append("]\r\n"); } - Iterable 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); diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java index fba034b38f2..b857f39cb33 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -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; + } + /** *

* phasesCheck. diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 12ec1d4ada6..c14b8c9ea60 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -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()) { diff --git a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java index bde6f1942aa..76607cef8f5 100644 --- a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java +++ b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java @@ -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()); @@ -271,26 +271,26 @@ public class WrappedAbility extends Ability { @Override - public String getSvarWithFallback(String name) { - return sa.getSvarWithFallback(name); - } + public String getSvarWithFallback(String name) { + return sa.getSvarWithFallback(name); + } - @Override - public String getSVar(String name) { - return sa.getSVar(name); - } + @Override + public String getSVar(String name) { + return sa.getSVar(name); + } - @Override - public Integer getSVarInt(String name) { - return sa.getSVarInt(name); - } + @Override + public Integer getSVarInt(String name) { + return sa.getSVarInt(name); + } - @Override - public Set getSVars() { - return sa.getSVars(); - } + @Override + public Set getSVars() { + return sa.getSVars(); + } - @Override + @Override public void resetOnceResolved() { // Fixing an issue with Targeting + Paying Mana // sa.resetOnceResolved(); diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 68beada931f..4d995b5cb42 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -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