Merge branch 'spellAbilityAdditionalAbilities' into 'master'

SpellAbility: use SpellAbility for additionalAbilities so Trigger can have cost

See merge request core-developers/forge!4303
This commit is contained in:
Michael Kamensky
2021-03-30 04:47:45 +00:00
26 changed files with 79 additions and 72 deletions

View File

@@ -44,7 +44,6 @@ import forge.game.mana.ManaPool;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilityManaPart; import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.PlayerZone; import forge.game.zone.PlayerZone;
@@ -1346,7 +1345,7 @@ public abstract class GameState {
} }
else if (info.startsWith("OnAdventure")) { else if (info.startsWith("OnAdventure")) {
String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; String abAdventure = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell";
AbilitySub saAdventure = (AbilitySub)AbilityFactory.getAbility(abAdventure, c); SpellAbility saAdventure = AbilityFactory.getAbility(abAdventure, c);
StringBuilder sbPlay = new StringBuilder(); StringBuilder sbPlay = new StringBuilder();
sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure"); sbPlay.append("Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonAdventure");
sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card."); sbPlay.append(" | AffectedZone$ Exile | Description$ You may cast the card.");

View File

@@ -159,8 +159,7 @@ public class SpecialCardAi {
final PhaseHandler ph = ai.getGame().getPhaseHandler(); final PhaseHandler ph = ai.getGame().getPhaseHandler();
final Combat combat = ai.getGame().getCombat(); final Combat combat = ai.getGame().getCombat();
Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa); Card animated = AnimateAi.becomeAnimated(sa.getHostCard(), sa.getSubAbility());
animated.addType("Creature");
if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) { if (sa.getHostCard().canReceiveCounters(CounterEnumType.P1P1)) {
animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null); animated.addCounter(CounterEnumType.P1P1, 2, ai, false, null);
} }
@@ -170,10 +169,6 @@ public class SpecialCardAi {
return isOppEOT || isValuableAttacker || isValuableBlocker; return isOppEOT || isValuableAttacker || isValuableBlocker;
} }
public static SpellAbility considerAnimating(final Player ai, final SpellAbility sa, final List<SpellAbility> options) {
return ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2) ? options.get(0) : options.get(1);
}
} }
// Cursed Scroll // Cursed Scroll

View File

@@ -299,7 +299,7 @@ public abstract class SpellAbilityAi {
final AbilitySub subAb = ab.getSubAbility(); final AbilitySub subAb = ab.getSubAbility();
return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb)); return SpellApiToAi.Converter.get(ab.getApi()).chkAIDrawback(ab, aiPlayer) && (subAb == null || chkDrawbackWithSubs(aiPlayer, subAb));
} }
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method"); System.err.println("Warning: default (ie. inherited from base class) implementation of confirmAction is used by " + sa.getHostCard().getName() + " for " + this.getClass().getName() + ". Consider declaring an overloaded method");
return true; return true;

View File

@@ -30,6 +30,7 @@ import forge.game.cost.CostPutCounter;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbility;
import forge.game.staticability.StaticAbilityContinuous; import forge.game.staticability.StaticAbilityContinuous;
@@ -243,6 +244,11 @@ public class AnimateAi extends SpellAbilityAi {
return true; return true;
} }
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
return player.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2);
}
private boolean animateTgtAI(final SpellAbility sa) { private boolean animateTgtAI(final SpellAbility sa) {
final Player ai = sa.getActivatingPlayer(); final Player ai = sa.getActivatingPlayer();
final PhaseHandler ph = ai.getGame().getPhaseHandler(); final PhaseHandler ph = ai.getGame().getPhaseHandler();

View File

@@ -363,8 +363,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi {
} else if ("Riot".equals(logic)) { } else if ("Riot".equals(logic)) {
SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1); SpellAbility counterSA = spells.get(0), hasteSA = spells.get(1);
return preferHasteForRiot(sa, player) ? hasteSA : counterSA; return preferHasteForRiot(sa, player) ? hasteSA : counterSA;
} else if ("CrawlingBarrens".equals(logic)) {
return SpecialCardAi.CrawlingBarrens.considerAnimating(player, sa, spells);
} }
return spells.get(0); // return first choice if no logic found return spells.get(0); // return first choice if no logic found
} }

View File

@@ -313,7 +313,12 @@ public class CountersPutAi extends SpellAbilityAi {
} else if (logic.startsWith("MoveCounter")) { } else if (logic.startsWith("MoveCounter")) {
return doMoveCounterLogic(ai, sa, ph); return doMoveCounterLogic(ai, sa, ph);
} else if (logic.equals("CrawlingBarrens")) { } else if (logic.equals("CrawlingBarrens")) {
return SpecialCardAi.CrawlingBarrens.consider(ai, sa); boolean willActivate = SpecialCardAi.CrawlingBarrens.consider(ai, sa);
if (willActivate && ph.getPhase().isBefore(PhaseType.MAIN2)) {
// don't use this for mana until after combat
AiCardMemory.rememberCard(ai, source, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2);
}
return willActivate;
} }
if (!sa.metConditions() && sa.getSubAbility() == null) { if (!sa.metConditions() && sa.getSubAbility() == null) {

View File

@@ -16,7 +16,6 @@ import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.TextUtil; import forge.util.TextUtil;
@@ -67,7 +66,7 @@ public class RepeatEachAi extends SpellAbilityAi {
return false; return false;
} else if ("AllPlayerLoseLife".equals(logic)) { } else if ("AllPlayerLoseLife".equals(logic)) {
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
AbilitySub repeat = sa.getAdditionalAbility("RepeatSubAbility"); SpellAbility repeat = sa.getAdditionalAbility("RepeatSubAbility");
String svar = repeat.getSVar(repeat.getParam("LifeAmount")); String svar = repeat.getSVar(repeat.getParam("LifeAmount"));
// replace RememberedPlayerCtrl with YouCtrl // replace RememberedPlayerCtrl with YouCtrl

View File

@@ -53,6 +53,7 @@ public final class AbilityFactory {
static final List<String> additionalAbilityKeys = Lists.newArrayList( static final List<String> additionalAbilityKeys = Lists.newArrayList(
"WinSubAbility", "OtherwiseSubAbility", // Clash "WinSubAbility", "OtherwiseSubAbility", // Clash
"BidSubAbility", // BidLifeEffect
"ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", // ChooseNumber "ChooseNumberSubAbility", "Lowest", "Highest", "NotLowest", // ChooseNumber
"HeadsSubAbility", "TailsSubAbility", "LoseSubAbility", // FlipCoin "HeadsSubAbility", "TailsSubAbility", "LoseSubAbility", // FlipCoin
"TrueSubAbility", "FalseSubAbility", // Branch "TrueSubAbility", "FalseSubAbility", // Branch
@@ -272,7 +273,7 @@ public final class AbilityFactory {
for (final String key : additionalAbilityKeys) { for (final String key : additionalAbilityKeys) {
if (mapParams.containsKey(key) && spellAbility.getAdditionalAbility(key) == null) { if (mapParams.containsKey(key) && spellAbility.getAdditionalAbility(key) == null) {
spellAbility.setAdditionalAbility(key, getSubAbility(state, mapParams.get(key), sVarHolder)); spellAbility.setAdditionalAbility(key, getAbility(state, mapParams.get(key), sVarHolder));
} }
} }

View File

@@ -12,6 +12,8 @@ import forge.game.card.Card;
import forge.game.card.CardUtil; import forge.game.card.CardUtil;
import forge.game.event.GameEventCardStatsChanged; import forge.game.event.GameEventCardStatsChanged;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.Lang;
import forge.util.TextUtil;
public class AnimateEffect extends AnimateEffectBase { public class AnimateEffect extends AnimateEffectBase {
@@ -139,6 +141,17 @@ public class AnimateEffect extends AnimateEffectBase {
List<Card> tgts = getCardsfromTargets(sa); List<Card> tgts = getCardsfromTargets(sa);
if (sa.hasParam("Optional")) {
final String targets = Lang.joinHomogenous(tgts);
final String message = sa.hasParam("OptionQuestion")
? TextUtil.fastReplace(sa.getParam("OptionQuestion"), "TARGETS", targets)
: getStackDescription(sa);
if (!sa.getActivatingPlayer().getController().confirmAction(sa, null, message)) {
return;
}
}
for (final Card c : tgts) { for (final Card c : tgts) {
doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc, doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc,
keywords, removeKeywords, hiddenKeywords, keywords, removeKeywords, hiddenKeywords,
@@ -229,9 +242,7 @@ public class AnimateEffect extends AnimateEffectBase {
final List<Card> tgts = getCardsfromTargets(sa); final List<Card> tgts = getCardsfromTargets(sa);
for (final Card c : tgts) { sb.append(Lang.joinHomogenous(tgts)).append(" ");
sb.append(c).append(" ");
}
// if power is -1, we'll assume it's not just setting toughness // if power is -1, we'll assume it's not just setting toughness
if (power != null && toughness != null) { if (power != null && toughness != null) {

View File

@@ -2,13 +2,11 @@ package forge.game.ability.effects;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.Localizer; import forge.util.Localizer;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
@@ -67,10 +65,10 @@ public class BidLifeEffect extends SpellAbilityEffect {
host.setChosenNumber(bid); host.setChosenNumber(bid);
host.addRemembered(winner); host.addRemembered(winner);
final SpellAbility action = AbilityFactory.getAbility(host.getSVar(sa.getParam("BidSubAbility")), host); final SpellAbility action = sa.getAdditionalAbility("BidSubAbility");
action.setActivatingPlayer(sa.getActivatingPlayer()); if (action != null) {
((AbilitySub) action).setParent(sa); AbilityUtils.resolve(action);
AbilityUtils.resolve(action); }
host.clearRemembered(); host.clearRemembered();
} }
} }

View File

@@ -3,7 +3,6 @@ package forge.game.ability.effects;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.util.Expressions; import forge.util.Expressions;
@@ -24,7 +23,7 @@ public class BranchEffect extends SpellAbilityEffect {
final int svarValue = AbilityUtils.calculateAmount(host, branchSVar, sa); final int svarValue = AbilityUtils.calculateAmount(host, branchSVar, sa);
final int operandValue = AbilityUtils.calculateAmount(host, operand, sa); final int operandValue = AbilityUtils.calculateAmount(host, operand, sa);
AbilitySub sub = null; SpellAbility sub = null;
if (Expressions.compare(svarValue, operator, operandValue)) { if (Expressions.compare(svarValue, operator, operandValue)) {
sub = sa.getAdditionalAbility("TrueSubAbility"); sub = sa.getAdditionalAbility("TrueSubAbility");
} else { } else {

View File

@@ -11,7 +11,6 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.util.Localizer; import forge.util.Localizer;
@@ -106,7 +105,7 @@ public class ChooseNumberEffect extends SpellAbilityEffect {
} }
card.getGame().getAction().notifyOfValue(sa, card, sb.toString(), null); card.getGame().getAction().notifyOfValue(sa, card, sb.toString(), null);
if (sa.hasParam("ChooseNumberSubAbility")) { if (sa.hasParam("ChooseNumberSubAbility")) {
AbilitySub sub = sa.getAdditionalAbility("ChooseNumberSubAbility"); SpellAbility sub = sa.getAdditionalAbility("ChooseNumberSubAbility");
for (Player p : chooseMap.keySet()) { for (Player p : chooseMap.keySet()) {
card.addRemembered(p); card.addRemembered(p);
@@ -117,7 +116,7 @@ public class ChooseNumberEffect extends SpellAbilityEffect {
} }
if (sa.hasParam("Lowest")) { if (sa.hasParam("Lowest")) {
AbilitySub sub = sa.getAdditionalAbility("Lowest"); SpellAbility sub = sa.getAdditionalAbility("Lowest");
for (Player p : lowestNum) { for (Player p : lowestNum) {
card.addRemembered(p); card.addRemembered(p);
@@ -134,7 +133,7 @@ public class ChooseNumberEffect extends SpellAbilityEffect {
notLowestNum.add(p); notLowestNum.add(p);
} }
} }
AbilitySub sub = sa.getAdditionalAbility("NotLowest"); SpellAbility sub = sa.getAdditionalAbility("NotLowest");
for (Player p : notLowestNum) { for (Player p : notLowestNum) {
card.addRemembered(p); card.addRemembered(p);
@@ -144,7 +143,7 @@ public class ChooseNumberEffect extends SpellAbilityEffect {
} }
if (sa.hasParam("Highest")) { if (sa.hasParam("Highest")) {
AbilitySub sub = sa.getAdditionalAbility("Highest"); SpellAbility sub = sa.getAdditionalAbility("Highest");
for (Player p : highestNum) { for (Player p : highestNum) {
card.addRemembered(p); card.addRemembered(p);

View File

@@ -6,7 +6,6 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.util.Aggregates; import forge.util.Aggregates;
@@ -63,7 +62,7 @@ public class ChoosePlayerEffect extends SpellAbilityEffect {
} }
// SubAbility that only fires if a player is chosen // SubAbility that only fires if a player is chosen
AbilitySub chosenSA = sa.getAdditionalAbility("ChooseSubAbility"); SpellAbility chosenSA = sa.getAdditionalAbility("ChooseSubAbility");
if (chosenSA != null) { if (chosenSA != null) {
if (!chosenSA.getHostCard().equals(sa.getHostCard())) { if (!chosenSA.getHostCard().equals(sa.getHostCard())) {
System.out.println("Warning: ChooseSubAbility had the wrong host set (potentially after cloning the root SA), attempting to correct..."); System.out.println("Warning: ChooseSubAbility had the wrong host set (potentially after cloning the root SA), attempting to correct...");
@@ -73,7 +72,7 @@ public class ChoosePlayerEffect extends SpellAbilityEffect {
} }
} else { } else {
// SubAbility that only fires if a player is not chosen // SubAbility that only fires if a player is not chosen
AbilitySub notChosenSA = sa.getAdditionalAbility("CantChooseSubAbility"); SpellAbility notChosenSA = sa.getAdditionalAbility("CantChooseSubAbility");
if (notChosenSA != null) { if (notChosenSA != null) {
if (!notChosenSA.getHostCard().equals(sa.getHostCard())) { if (!notChosenSA.getHostCard().equals(sa.getHostCard())) {
System.out.println("Warning: CantChooseSubAbility had the wrong host set (potentially after cloning the root SA), attempting to correct..."); System.out.println("Warning: CantChooseSubAbility had the wrong host set (potentially after cloning the root SA), attempting to correct...");

View File

@@ -9,7 +9,6 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect; import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.game.zone.PlayerZone; import forge.game.zone.PlayerZone;
@@ -39,14 +38,14 @@ public class ClashEffect extends SpellAbilityEffect {
if (victory) { if (victory) {
AbilitySub sub = sa.getAdditionalAbility("WinSubAbility"); SpellAbility sub = sa.getAdditionalAbility("WinSubAbility");
if (sub != null) { if (sub != null) {
AbilityUtils.resolve(sub); AbilityUtils.resolve(sub);
} }
runParams.put(AbilityKey.Won, "True"); runParams.put(AbilityKey.Won, "True");
} else { } else {
AbilitySub sub = sa.getAdditionalAbility("OtherwiseSubAbility"); SpellAbility sub = sa.getAdditionalAbility("OtherwiseSubAbility");
if (sub != null) { if (sub != null) {
AbilityUtils.resolve(sub); AbilityUtils.resolve(sub);
} }

View File

@@ -82,9 +82,11 @@ public class DelayedTriggerEffect extends SpellAbilityEffect {
} }
if (sa.hasAdditionalAbility("Execute")) { if (sa.hasAdditionalAbility("Execute")) {
AbilitySub overridingSA = (AbilitySub)sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false); SpellAbility overridingSA = sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false);
// need to reset the parent, additionalAbility does set it to this // need to reset the parent, additionalAbility does set it to this
overridingSA.setParent(null); if (overridingSA instanceof AbilitySub) {
((AbilitySub)overridingSA).setParent(null);
}
// Set Transform timestamp when the delayed trigger is created // Set Transform timestamp when the delayed trigger is created
if (ApiType.SetState == overridingSA.getApi()) { if (ApiType.SetState == overridingSA.getApi()) {
overridingSA.setSVar("StoredTransform", String.valueOf(sa.getHostCard().getTransformedTimestamp())); overridingSA.setSVar("StoredTransform", String.valueOf(sa.getHostCard().getTransformedTimestamp()));

View File

@@ -13,7 +13,6 @@ import forge.game.card.Card;
import forge.game.event.GameEventFlipCoin; import forge.game.event.GameEventFlipCoin;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.player.PlayerController; import forge.game.player.PlayerController;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.TriggerType; import forge.game.trigger.TriggerType;
import forge.util.Localizer; import forge.util.Localizer;
@@ -97,7 +96,7 @@ public class FlipCoinEffect extends SpellAbilityEffect {
} }
} }
if (countHeads > 0) { if (countHeads > 0) {
AbilitySub sub = sa.getAdditionalAbility("HeadsSubAbility"); SpellAbility sub = sa.getAdditionalAbility("HeadsSubAbility");
if (sub != null) { if (sub != null) {
if (sa.hasParam("Amount")) { if (sa.hasParam("Amount")) {
sub.setSVar(varName, "Number$" + countHeads); sub.setSVar(varName, "Number$" + countHeads);
@@ -106,7 +105,7 @@ public class FlipCoinEffect extends SpellAbilityEffect {
} }
} }
if (countTails > 0) { if (countTails > 0) {
AbilitySub sub = sa.getAdditionalAbility("TailsSubAbility"); SpellAbility sub = sa.getAdditionalAbility("TailsSubAbility");
if (sub != null) { if (sub != null) {
if (sa.hasParam("Amount")) { if (sa.hasParam("Amount")) {
sub.setSVar(varName, "Number$" + countTails); sub.setSVar(varName, "Number$" + countTails);

View File

@@ -75,9 +75,11 @@ public class ImmediateTriggerEffect extends SpellAbilityEffect {
} }
if (sa.hasAdditionalAbility("Execute")) { if (sa.hasAdditionalAbility("Execute")) {
AbilitySub overridingSA = (AbilitySub)sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false); SpellAbility overridingSA = sa.getAdditionalAbility("Execute").copy(lki, sa.getActivatingPlayer(), false);
// need to set Parent to null, otherwise it might have wrong root ability // need to set Parent to null, otherwise it might have wrong root ability
overridingSA.setParent(null); if (overridingSA instanceof AbilitySub) {
((AbilitySub)overridingSA).setParent(null);
}
if (sa.hasParam("CopyTriggeringObjects")) { if (sa.hasParam("CopyTriggeringObjects")) {
overridingSA.setTriggeringObjects(sa.getTriggeringObjects()); overridingSA.setTriggeringObjects(sa.getTriggeringObjects());

View File

@@ -15,7 +15,6 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -105,7 +104,7 @@ public class MultiplePilesEffect extends SpellAbilityEffect {
} }
} }
AbilitySub sub = sa.getAdditionalAbility("ChosenPile"); SpellAbility sub = sa.getAdditionalAbility("ChosenPile");
if (sub != null) { if (sub != null) {
AbilityUtils.resolve(sub); AbilityUtils.resolve(sub);
} }

View File

@@ -16,7 +16,6 @@ import forge.game.card.CardDamageMap;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.card.CardZoneTable; import forge.game.card.CardZoneTable;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance; import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -33,7 +32,7 @@ public class RepeatEachEffect extends SpellAbilityEffect {
// Things to loop over: Cards, Players, or SAs // Things to loop over: Cards, Players, or SAs
final Card source = sa.getHostCard(); final Card source = sa.getHostCard();
final AbilitySub repeat = sa.getAdditionalAbility("RepeatSubAbility"); final SpellAbility repeat = sa.getAdditionalAbility("RepeatSubAbility");
if (repeat != null && !repeat.getHostCard().equalsWithTimestamp(source)) { if (repeat != null && !repeat.getHostCard().equalsWithTimestamp(source)) {
// TODO: for some reason, the host card of the original additional SA is set to the cloned card when // TODO: for some reason, the host card of the original additional SA is set to the cloned card when

View File

@@ -7,7 +7,6 @@ import forge.game.card.Card;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Expressions; import forge.util.Expressions;
@@ -25,7 +24,7 @@ public class RepeatEffect extends SpellAbilityEffect {
Card source = sa.getHostCard(); Card source = sa.getHostCard();
// setup subability to repeat // setup subability to repeat
AbilitySub repeat = sa.getAdditionalAbility("RepeatSubAbility"); SpellAbility repeat = sa.getAdditionalAbility("RepeatSubAbility");
if (repeat != null && !repeat.getHostCard().equals(source)) { if (repeat != null && !repeat.getHostCard().equals(source)) {
// TODO: for some reason, the host card of the original additional SA is set to the cloned card when // TODO: for some reason, the host card of the original additional SA is set to the cloned card when

View File

@@ -9,7 +9,6 @@ import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView; import forge.game.card.CardCollectionView;
import forge.game.card.CardLists; import forge.game.card.CardLists;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -161,7 +160,7 @@ public class TwoPilesEffect extends SpellAbilityEffect {
card.addRemembered(z); card.addRemembered(z);
} }
AbilitySub sub = sa.getAdditionalAbility("ChosenPile"); SpellAbility sub = sa.getAdditionalAbility("ChosenPile");
if (sub != null) { if (sub != null) {
AbilityUtils.resolve(sub); AbilityUtils.resolve(sub);
} }
@@ -174,7 +173,7 @@ public class TwoPilesEffect extends SpellAbilityEffect {
card.addRemembered(z); card.addRemembered(z);
} }
AbilitySub sub = sa.getAdditionalAbility("UnchosenPile"); SpellAbility sub = sa.getAdditionalAbility("UnchosenPile");
if (sub != null) { if (sub != null) {
AbilityUtils.resolve(sub); AbilityUtils.resolve(sub);
} }

View File

@@ -538,8 +538,8 @@ public class CardFactory {
if (from.getSubAbility() != null) { if (from.getSubAbility() != null) {
to.setSubAbility((AbilitySub) from.getSubAbility().copy(host, p, lki)); to.setSubAbility((AbilitySub) from.getSubAbility().copy(host, p, lki));
} }
for (Map.Entry<String, AbilitySub> e : from.getAdditionalAbilities().entrySet()) { for (Map.Entry<String, SpellAbility> e : from.getAdditionalAbilities().entrySet()) {
to.setAdditionalAbility(e.getKey(), (AbilitySub) e.getValue().copy(host, p, lki)); to.setAdditionalAbility(e.getKey(), e.getValue().copy(host, p, lki));
} }
for (Map.Entry<String, List<AbilitySub>> e : from.getAdditionalAbilityLists().entrySet()) { for (Map.Entry<String, List<AbilitySub>> e : from.getAdditionalAbilityLists().entrySet()) {
to.setAdditionalAbilityList(e.getKey(), Lists.transform(e.getValue(), new Function<AbilitySub, AbilitySub>() { to.setAdditionalAbilityList(e.getKey(), Lists.transform(e.getValue(), new Function<AbilitySub, AbilitySub>() {

View File

@@ -139,7 +139,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
private SpellAbilityCondition conditions = new SpellAbilityCondition(); private SpellAbilityCondition conditions = new SpellAbilityCondition();
private AbilitySub subAbility = null; private AbilitySub subAbility = null;
private Map<String, AbilitySub> additionalAbilities = Maps.newHashMap(); private Map<String, SpellAbility> additionalAbilities = Maps.newHashMap();
private Map<String, List<AbilitySub>> additionalAbilityLists = Maps.newHashMap(); private Map<String, List<AbilitySub>> additionalAbilityLists = Maps.newHashMap();
protected ApiType api = null; protected ApiType api = null;
@@ -238,7 +238,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (subAbility != null) { if (subAbility != null) {
subAbility.setHostCard(c); subAbility.setHostCard(c);
} }
for (AbilitySub sa : additionalAbilities.values()) { for (SpellAbility sa : additionalAbilities.values()) {
if (sa.getHostCard() != c) { if (sa.getHostCard() != c) {
sa.setHostCard(c); sa.setHostCard(c);
} }
@@ -437,7 +437,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (subAbility != null) { if (subAbility != null) {
updated |= subAbility.setActivatingPlayer(player, lki); updated |= subAbility.setActivatingPlayer(player, lki);
} }
for (AbilitySub sa : additionalAbilities.values()) { for (SpellAbility sa : additionalAbilities.values()) {
updated |= sa.setActivatingPlayer(player, lki); updated |= sa.setActivatingPlayer(player, lki);
} }
for (List<AbilitySub> list : additionalAbilityLists.values()) { for (List<AbilitySub> list : additionalAbilityLists.values()) {
@@ -875,10 +875,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
view.updateDescription(this); //description changes when sub-abilities change view.updateDescription(this); //description changes when sub-abilities change
} }
public Map<String, AbilitySub> getAdditionalAbilities() { public Map<String, SpellAbility> getAdditionalAbilities() {
return additionalAbilities; return additionalAbilities;
} }
public AbilitySub getAdditionalAbility(final String name) { public SpellAbility getAdditionalAbility(final String name) {
if (hasAdditionalAbility(name)) { if (hasAdditionalAbility(name)) {
return additionalAbilities.get(name); return additionalAbilities.get(name);
} }
@@ -889,11 +889,13 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return additionalAbilities.containsKey(name); return additionalAbilities.containsKey(name);
} }
public void setAdditionalAbility(final String name, final AbilitySub sa) { public void setAdditionalAbility(final String name, final SpellAbility sa) {
if (sa == null) { if (sa == null) {
additionalAbilities.remove(name); additionalAbilities.remove(name);
} else { } else {
sa.setParent(this); if (sa instanceof AbilitySub) {
((AbilitySub)sa).setParent(this);
}
additionalAbilities.put(name, sa); additionalAbilities.put(name, sa);
} }
view.updateDescription(this); //description changes when sub-abilities change view.updateDescription(this); //description changes when sub-abilities change
@@ -2080,7 +2082,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
subAbility.changeText(); subAbility.changeText();
} }
} }
for (AbilitySub sa : additionalAbilities.values()) { for (SpellAbility sa : additionalAbilities.values()) {
sa.changeText(); sa.changeText();
} }
@@ -2105,7 +2107,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
subAbility.changeTextIntrinsic(colorMap, typeMap); subAbility.changeTextIntrinsic(colorMap, typeMap);
} }
} }
for (AbilitySub sa : additionalAbilities.values()) { for (SpellAbility sa : additionalAbilities.values()) {
sa.changeTextIntrinsic(colorMap, typeMap); sa.changeTextIntrinsic(colorMap, typeMap);
} }
@@ -2122,7 +2124,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
if (subAbility != null) { if (subAbility != null) {
subAbility.setIntrinsic(i); subAbility.setIntrinsic(i);
} }
for (AbilitySub sa : additionalAbilities.values()) { for (SpellAbility sa : additionalAbilities.values()) {
if (sa.isIntrinsic() != i) { if (sa.isIntrinsic() != i) {
sa.setIntrinsic(i); sa.setIntrinsic(i);
} }

View File

@@ -421,7 +421,7 @@ public class WrappedAbility extends Ability {
} }
@Override @Override
public AbilitySub getAdditionalAbility(String ability) { public SpellAbility getAdditionalAbility(String ability) {
return sa.getAdditionalAbility(ability); return sa.getAdditionalAbility(ability);
} }

View File

@@ -2,9 +2,7 @@ Name:Crawling Barrens
ManaCost:no cost ManaCost:no cost
Types:Land Types:Land
A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}.
A:AB$ PutCounter | Cost$ 4 | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBChoice | AILogic$ CrawlingBarrens | SpellDescription$ Put two +1/+1 counters on CARDNAME. Then you may have it become a 0/0 Elemental creature until end of turn. It's still a land. A:AB$ PutCounter | Cost$ 4 | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBAnimate | AILogic$ CrawlingBarrens | SpellDescription$ Put two +1/+1 counters on CARDNAME. Then you may have it become a 0/0 Elemental creature until end of turn. It's still a land.
SVar:DBChoice:DB$ GenericChoice | Defined$ You | Choices$ Animate,NoAnimate | AILogic$ CrawlingBarrens | StackDescription$ Then you may have it become a 0/0 Elemental creature until end of turn. It's still a land. SVar:DBAnimate:DB$ Animate | Defined$ Self | Power$ 0 | Toughness$ 0 | Types$ Creature,Elemental | Optional$ True
SVar:Animate:DB$ Animate | Defined$ Self | Power$ 0 | Toughness$ 0 | Types$ Creature,Elemental | SpellDescription$ CARDNAME becomes a 0/0 Elemental creature until end of turn.
SVar:NoAnimate:DB$ Pump | SpellDescription$ CARDNAME does not become a 0/0 Elemental creature until end of turn.
DeckHas:Ability$Counters DeckHas:Ability$Counters
Oracle:{T}: Add {C}.\n{4}: Put two +1/+1 counters on Crawling Barrens. Then you may have it become a 0/0 Elemental creature until end of turn. It's still a land. Oracle:{T}: Add {C}.\n{4}: Put two +1/+1 counters on Crawling Barrens. Then you may have it become a 0/0 Elemental creature until end of turn. It's still a land.

View File

@@ -5,8 +5,8 @@ PT:1/1
K:Menace K:Menace
K:Lifelink K:Lifelink
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Creature+nonToken+OppOwn | ReplaceWith$ Exile | Description$ If a nontoken creature an opponent controls would die, exile it instead. When you do, you may pay {2}. If you do, create a 1/1 black and green Pest creature token with "When this creature dies, you gain 1 life." R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Creature+nonToken+OppOwn | ReplaceWith$ Exile | Description$ If a nontoken creature an opponent controls would die, exile it instead. When you do, you may pay {2}. If you do, create a 1/1 black and green Pest creature token with "When this creature dies, you gain 1 life."
SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard | SubAbility$ DBImmediateTrigger
T:Mode$ ChangesZone | TriggerZones$ Battlefield | Origin$ Any | Destination$ Exile | ValidCard$ Card.ExiledWithSource | Execute$ TrigToken | Secondary$ True | TriggerDescription$ If a nontoken creature an opponent controls would die, exile it instead. When you do, you may pay {2}. If you do, create a 1/1 black and green Pest creature token with "When this creature dies, you gain 1 life." SVar:DBImmediateTrigger:DB$ ImmediateTrigger | Execute$ TrigToken | TriggerDescription$ If a nontoken creature an opponent controls would die, exile it instead. When you do, you may pay {2}. If you do, create a 1/1 black and green Pest creature token with "When this creature dies, you gain 1 life."
SVar:TrigToken:AB$ Token | Cost$ 2 | TokenAmount$ 1 | TokenScript$ bg_1_1_pest_lifegain | TokenOwner$ You SVar:TrigToken:AB$ Token | Cost$ 2 | TokenAmount$ 1 | TokenScript$ bg_1_1_pest_lifegain | TokenOwner$ You
AlternateMode:Modal AlternateMode:Modal
DeckHas:Ability$Token & Ability$LifeGain DeckHas:Ability$Token & Ability$LifeGain