package forge.card.abilityFactory; import forge.*; import forge.card.cardFactory.CardFactoryUtil; import forge.card.spellability.*; import java.util.ArrayList; import java.util.HashMap; /** * <p>AbilityFactory class.</p> * * @author Forge * @version $Id: $ */ public class AbilityFactory { private Card hostC = null; /** * <p>getHostCard.</p> * * @return a {@link forge.Card} object. */ public Card getHostCard() { return hostC; } private HashMap<String, String> mapParams = new HashMap<String, String>(); /** * <p>Getter for the field <code>mapParams</code>.</p> * * @return a {@link java.util.HashMap} object. */ public HashMap<String, String> getMapParams() { return mapParams; } private boolean isAb = false; private boolean isSp = false; private boolean isDb = false; /** * <p>isAbility.</p> * * @return a boolean. */ public boolean isAbility() { return isAb; } /** * <p>isSpell.</p> * * @return a boolean. */ public boolean isSpell() { return isSp; } /** * <p>isDrawback.</p> * * @return a boolean. */ public boolean isDrawback() { return isDb; } private Cost abCost = null; /** * <p>Getter for the field <code>abCost</code>.</p> * * @return a {@link forge.card.spellability.Cost} object. */ public Cost getAbCost() { return abCost; } private boolean isTargeted = false; private boolean hasValid = false; private Target abTgt = null; /** * <p>isTargeted.</p> * * @return a boolean. */ public boolean isTargeted() { return isTargeted; } /** * <p>hasValid.</p> * * @return a boolean. */ public boolean hasValid() { return hasValid; } /** * <p>Getter for the field <code>abTgt</code>.</p> * * @return a {@link forge.card.spellability.Target} object. */ public Target getAbTgt() { return abTgt; } /** * <p>isCurse.</p> * * @return a boolean. */ public boolean isCurse() { return mapParams.containsKey("IsCurse"); } private boolean hasSubAb = false; /** * <p>hasSubAbility.</p> * * @return a boolean. */ public boolean hasSubAbility() { return hasSubAb; } private boolean hasSpDesc = false; /** * <p>hasSpDescription.</p> * * @return a boolean. */ public boolean hasSpDescription() { return hasSpDesc; } private String API = ""; /** * <p>getAPI.</p> * * @return a {@link java.lang.String} object. */ public String getAPI() { return API; } //******************************************************* /** * <p>Getter for the field <code>mapParams</code>.</p> * * @param abString a {@link java.lang.String} object. * @param hostCard a {@link forge.Card} object. * @return a {@link java.util.HashMap} object. */ public HashMap<String, String> getMapParams(String abString, Card hostCard) { HashMap<String, String> mapParameters = new HashMap<String, String>(); if (!(abString.length() > 0)) throw new RuntimeException("AbilityFactory : getAbility -- abString too short in " + hostCard.getName() + ": [" + abString + "]"); String a[] = abString.split("\\|"); for (int aCnt = 0; aCnt < a.length; aCnt++) a[aCnt] = a[aCnt].trim(); if (!(a.length > 0)) throw new RuntimeException("AbilityFactory : getAbility -- a[] too short in " + hostCard.getName()); for (int i = 0; i < a.length; i++) { String aa[] = a[i].split("\\$"); for (int aaCnt = 0; aaCnt < aa.length; aaCnt++) aa[aaCnt] = aa[aaCnt].trim(); if (aa.length != 2) { StringBuilder sb = new StringBuilder(); sb.append("AbilityFactory Parsing Error in getAbility() : Split length of "); sb.append(a[i]).append(" in ").append(hostCard.getName()).append(" is not 2."); throw new RuntimeException(sb.toString()); } mapParameters.put(aa[0], aa[1]); } return mapParameters; } /** * <p>getAbility.</p> * * @param abString a {@link java.lang.String} object. * @param hostCard a {@link forge.Card} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public SpellAbility getAbility(String abString, Card hostCard) { SpellAbility SA = null; hostC = hostCard; mapParams = getMapParams(abString, hostCard); // parse universal parameters if (mapParams.containsKey("AB")) { isAb = true; API = mapParams.get("AB"); } else if (mapParams.containsKey("SP")) { isSp = true; API = mapParams.get("SP"); } else if (mapParams.containsKey("DB")) { isDb = true; API = mapParams.get("DB"); } else throw new RuntimeException("AbilityFactory : getAbility -- no API in " + hostCard.getName()); if (!isDb) { if (!mapParams.containsKey("Cost")) throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + hostCard.getName()); abCost = new Cost(mapParams.get("Cost"), hostCard.getName(), isAb); } if (mapParams.containsKey("ValidTgts")) { hasValid = true; isTargeted = true; } if (mapParams.containsKey("Tgt")) { isTargeted = true; } if (isTargeted) { String min = mapParams.containsKey("TargetMin") ? mapParams.get("TargetMin") : "1"; String max = mapParams.containsKey("TargetMax") ? mapParams.get("TargetMax") : "1"; if (hasValid) { // TgtPrompt now optional StringBuilder sb = new StringBuilder(); if (hostC != null) sb.append(hostC + " - "); String prompt = mapParams.containsKey("TgtPrompt") ? mapParams.get("TgtPrompt") : "Select target " + mapParams.get("ValidTgts"); sb.append(prompt); abTgt = new Target(hostC, sb.toString(), mapParams.get("ValidTgts").split(","), min, max); } else abTgt = new Target(hostC, mapParams.get("Tgt"), min, max); if (mapParams.containsKey("TgtZone")) // if Targeting something not in play, this Key should be set abTgt.setZone(mapParams.get("TgtZone")); // Target Type mostly for Counter: Spell,Activated,Triggered,Ability (or any combination of) // Ability = both activated and triggered abilities if (mapParams.containsKey("TargetType")) abTgt.setTargetSpellAbilityType(mapParams.get("TargetType")); // TargetValidTargeting most for Counter: e.g. target spell that targets X. if (mapParams.containsKey("TargetValidTargeting")) abTgt.setSAValidTargeting(mapParams.get("TargetValidTargeting")); } hasSubAb = mapParams.containsKey("SubAbility"); hasSpDesc = mapParams.containsKey("SpellDescription"); // *********************************** // Match API keywords if (API.equals("DealDamage")) { AbilityFactory_DealDamage dd = new AbilityFactory_DealDamage(this); if (isAb) SA = dd.getAbility(); else if (isSp) SA = dd.getSpell(); else if (isDb) SA = dd.getDrawback(); } else if (API.equals("DamageAll")) { AbilityFactory_DealDamage dd = new AbilityFactory_DealDamage(this); if (isAb) SA = dd.getAbilityDamageAll(); else if (isSp) SA = dd.getSpellDamageAll(); else if (isDb) SA = dd.getDrawbackDamageAll(); } else if (API.equals("PutCounter")) { if (isAb) SA = AbilityFactory_Counters.createAbilityPutCounters(this); else if (isSp) SA = AbilityFactory_Counters.createSpellPutCounters(this); else if (isDb) SA = AbilityFactory_Counters.createDrawbackPutCounters(this); } else if (API.equals("PutCounterAll")) { if (isAb) SA = AbilityFactory_Counters.createAbilityPutCounterAll(this); else if (isSp) SA = AbilityFactory_Counters.createSpellPutCounterAll(this); else if (isDb) SA = AbilityFactory_Counters.createDrawbackPutCounterAll(this); } else if (API.equals("RemoveCounter")) { if (isAb) SA = AbilityFactory_Counters.createAbilityRemoveCounters(this); else if (isSp) SA = AbilityFactory_Counters.createSpellRemoveCounters(this); else if (isDb) SA = AbilityFactory_Counters.createDrawbackRemoveCounters(this); } else if (API.equals("RemoveCounterAll")) { if (isAb) SA = AbilityFactory_Counters.createAbilityRemoveCounterAll(this); else if (isSp) SA = AbilityFactory_Counters.createSpellRemoveCounterAll(this); else if (isDb) SA = AbilityFactory_Counters.createDrawbackRemoveCounterAll(this); } else if (API.equals("Proliferate")) { if (isAb) SA = AbilityFactory_Counters.createAbilityProliferate(this); else if (isSp) SA = AbilityFactory_Counters.createSpellProliferate(this); else if (isDb) SA = AbilityFactory_Counters.createDrawbackProliferate(this); } else if (API.equals("ChangeZone")) { if (isAb) SA = AbilityFactory_ChangeZone.createAbilityChangeZone(this); else if (isSp) SA = AbilityFactory_ChangeZone.createSpellChangeZone(this); else if (isDb) SA = AbilityFactory_ChangeZone.createDrawbackChangeZone(this); } else if (API.equals("ChangeZoneAll")) { if (isAb) SA = AbilityFactory_ChangeZone.createAbilityChangeZoneAll(this); else if (isSp) SA = AbilityFactory_ChangeZone.createSpellChangeZoneAll(this); else if (isDb) SA = AbilityFactory_ChangeZone.createDrawbackChangeZoneAll(this); } else if (API.equals("Pump")) { AbilityFactory_Pump afPump = new AbilityFactory_Pump(this); if (isAb) SA = afPump.getAbilityPump(); else if (isSp) SA = afPump.getSpellPump(); else if (isDb) SA = afPump.getDrawbackPump(); if (isAb || isSp) hostCard.setSVar("PlayMain1", "TRUE"); } else if (API.equals("PumpAll")) { AbilityFactory_Pump afPump = new AbilityFactory_Pump(this); if (isAb) SA = afPump.getAbilityPumpAll(); else if (isSp) SA = afPump.getSpellPumpAll(); else if (isDb) SA = afPump.getDrawbackPumpAll(); if (isAb || isSp) hostCard.setSVar("PlayMain1", "TRUE"); } else if (API.equals("GainLife")) { if (isAb) SA = AbilityFactory_AlterLife.createAbilityGainLife(this); else if (isSp) SA = AbilityFactory_AlterLife.createSpellGainLife(this); else if (isDb) SA = AbilityFactory_AlterLife.createDrawbackGainLife(this); } else if (API.equals("LoseLife")) { if (isAb) SA = AbilityFactory_AlterLife.createAbilityLoseLife(this); else if (isSp) SA = AbilityFactory_AlterLife.createSpellLoseLife(this); else if (isDb) SA = AbilityFactory_AlterLife.createDrawbackLoseLife(this); } else if (API.equals("SetLife")) { if (isAb) SA = AbilityFactory_AlterLife.createAbilitySetLife(this); else if (isSp) SA = AbilityFactory_AlterLife.createSpellSetLife(this); else if (isDb) SA = AbilityFactory_AlterLife.createDrawbackSetLife(this); } else if (API.equals("Poison")) { if (isAb) SA = AbilityFactory_AlterLife.createAbilityPoison(this); else if (isSp) SA = AbilityFactory_AlterLife.createSpellPoison(this); else if (isDb) SA = AbilityFactory_AlterLife.createDrawbackPoison(this); } else if (API.equals("Fog")) { if (isAb) SA = AbilityFactory_Combat.createAbilityFog(this); else if (isSp) SA = AbilityFactory_Combat.createSpellFog(this); else if (isDb) SA = AbilityFactory_Combat.createDrawbackFog(this); } else if (API.equals("Untap")) { if (isAb) SA = AbilityFactory_PermanentState.createAbilityUntap(this); else if (isSp) SA = AbilityFactory_PermanentState.createSpellUntap(this); else if (isDb) SA = AbilityFactory_PermanentState.createDrawbackUntap(this); } else if (API.equals("UntapAll")) { if (isAb) SA = AbilityFactory_PermanentState.createAbilityUntapAll(this); else if (isSp) SA = AbilityFactory_PermanentState.createSpellUntapAll(this); else if (isDb) SA = AbilityFactory_PermanentState.createDrawbackUntapAll(this); } else if (API.equals("Tap")) { if (isAb) SA = AbilityFactory_PermanentState.createAbilityTap(this); else if (isSp) SA = AbilityFactory_PermanentState.createSpellTap(this); else if (isDb) SA = AbilityFactory_PermanentState.createDrawbackTap(this); } else if (API.equals("TapAll")) { if (isAb) SA = AbilityFactory_PermanentState.createAbilityTapAll(this); else if (isSp) SA = AbilityFactory_PermanentState.createSpellTapAll(this); else if (isDb) SA = AbilityFactory_PermanentState.createDrawbackTapAll(this); } else if (API.equals("TapOrUntap")) { if (isAb) SA = AbilityFactory_PermanentState.createAbilityTapOrUntap(this); else if (isSp) SA = AbilityFactory_PermanentState.createSpellTapOrUntap(this); else if (isDb) SA = AbilityFactory_PermanentState.createDrawbackTapOrUntap(this); } else if (API.equals("PreventDamage")) { if (isAb) SA = AbilityFactory_PreventDamage.getAbilityPreventDamage(this); else if (isSp) SA = AbilityFactory_PreventDamage.getSpellPreventDamage(this); else if (isDb) { SA = AbilityFactory_PreventDamage.createDrawbackPreventDamage(this); } } else if (API.equals("Regenerate")) { if (isAb) SA = AbilityFactory_Regenerate.getAbilityRegenerate(this); else if (isSp) SA = AbilityFactory_Regenerate.getSpellRegenerate(this); else if (isDb) { SA = AbilityFactory_Regenerate.createDrawbackRegenerate(this); } } else if (API.equals("Draw")) { if (isAb) SA = AbilityFactory_ZoneAffecting.createAbilityDraw(this); else if (isSp) SA = AbilityFactory_ZoneAffecting.createSpellDraw(this); else if (isDb) SA = AbilityFactory_ZoneAffecting.createDrawbackDraw(this); } else if (API.equals("Mill")) { if (isAb) SA = AbilityFactory_ZoneAffecting.createAbilityMill(this); else if (isSp) SA = AbilityFactory_ZoneAffecting.createSpellMill(this); else if (isDb) SA = AbilityFactory_ZoneAffecting.createDrawbackMill(this); } else if (API.equals("Scry")) { if (isAb) SA = AbilityFactory_Reveal.createAbilityScry(this); else if (isSp) SA = AbilityFactory_Reveal.createSpellScry(this); else if (isDb) SA = AbilityFactory_Reveal.createDrawbackScry(this); } else if (API.equals("RearrangeTopOfLibrary")) { if (isAb) SA = AbilityFactory_Reveal.createRearrangeTopOfLibraryAbility(this); else if (isSp) SA = AbilityFactory_Reveal.createRearrangeTopOfLibrarySpell(this); else if (isDb) SA = AbilityFactory_Reveal.createRearrangeTopOfLibraryDrawback(this); } else if (API.equals("Sacrifice")) { if (isAb) SA = AbilityFactory_Sacrifice.createAbilitySacrifice(this); else if (isSp) SA = AbilityFactory_Sacrifice.createSpellSacrifice(this); else if (isDb) SA = AbilityFactory_Sacrifice.createDrawbackSacrifice(this); } else if (API.equals("SacrificeAll")) { if (isAb) SA = AbilityFactory_Sacrifice.createAbilitySacrificeAll(this); else if (isSp) SA = AbilityFactory_Sacrifice.createSpellSacrificeAll(this); else if (isDb) SA = AbilityFactory_Sacrifice.createDrawbackSacrificeAll(this); } else if (API.equals("Destroy")) { if (isAb) SA = AbilityFactory_Destroy.createAbilityDestroy(this); else if (isSp) SA = AbilityFactory_Destroy.createSpellDestroy(this); else if (isDb) { SA = AbilityFactory_Destroy.createDrawbackDestroy(this); } } else if (API.equals("DestroyAll")) { if (isAb) SA = AbilityFactory_Destroy.createAbilityDestroyAll(this); else if (isSp) SA = AbilityFactory_Destroy.createSpellDestroyAll(this); else if (isDb) SA = AbilityFactory_Destroy.createDrawbackDestroyAll(this); } else if (API.equals("Mana")) { String produced = mapParams.get("Produced"); if (isAb) SA = AbilityFactory_Mana.createAbilityMana(this, produced); if (isSp) SA = AbilityFactory_Mana.createSpellMana(this, produced); if (isDb) SA = AbilityFactory_Mana.createDrawbackMana(this, produced); } else if (API.equals("ManaReflected")) { // Reflected mana will have a filler for produced of "1" if (isAb) SA = AbilityFactory_Mana.createAbilityManaReflected(this, "1"); if (isSp) { // shouldn't really happen i think? SA = AbilityFactory_Mana.createSpellManaReflected(this, "1"); } } else if (API.equals("Token")) { AbilityFactory_Token AFT = new AbilityFactory_Token(this); if (isAb) SA = AFT.getAbility(); else if (isSp) SA = AFT.getSpell(); else if (isDb) SA = AFT.getDrawback(); } else if (API.equals("GainControl")) { AbilityFactory_GainControl afControl = new AbilityFactory_GainControl(this); if (isAb) SA = afControl.getAbilityGainControl(); else if (isSp) SA = afControl.getSpellGainControl(); else if (isDb) { SA = afControl.getDrawbackGainControl(); } } else if (API.equals("Discard")) { if (isAb) SA = AbilityFactory_ZoneAffecting.createAbilityDiscard(this); else if (isSp) SA = AbilityFactory_ZoneAffecting.createSpellDiscard(this); else if (isDb) SA = AbilityFactory_ZoneAffecting.createDrawbackDiscard(this); } else if (API.equals("Counter")) { AbilityFactory_CounterMagic c = new AbilityFactory_CounterMagic(this); if (isTargeted) // Since all "Counter" ABs Counter things on the Stack no need for it to be everywhere abTgt.setZone("Stack"); if (isAb) SA = c.getAbilityCounter(this); else if (isSp) SA = c.getSpellCounter(this); else if (isDb) SA = c.getDrawbackCounter(this); } else if (API.equals("AddTurn")) { if (isAb) SA = AbilityFactory_Turns.createAbilityAddTurn(this); else if (isSp) SA = AbilityFactory_Turns.createSpellAddTurn(this); else if (isDb) SA = AbilityFactory_Turns.createDrawbackAddTurn(this); } else if (API.equals("Clash")) { if (isAb) SA = AbilityFactory_Clash.getAbilityClash(this); else if (isSp) SA = AbilityFactory_Clash.getSpellClash(this); else if (isDb) SA = AbilityFactory_Clash.getDrawbackClash(this); } else if (API.equals("Animate")) { if (isAb) SA = AbilityFactory_Animate.createAbilityAnimate(this); else if (isSp) SA = AbilityFactory_Animate.createSpellAnimate(this); else if (isDb) SA = AbilityFactory_Animate.createDrawbackAnimate(this); } else if (API.equals("Effect")) { if (isAb) SA = AbilityFactory_Effect.createAbilityEffect(this); else if (isSp) SA = AbilityFactory_Effect.createSpellEffect(this); else if (isDb) SA = AbilityFactory_Effect.createDrawbackEffect(this); } else if (API.equals("WinsGame")) { if (isAb) SA = AbilityFactory_EndGameCondition.createAbilityWinsGame(this); else if (isSp) SA = AbilityFactory_EndGameCondition.createSpellWinsGame(this); else if (isDb) SA = AbilityFactory_EndGameCondition.createDrawbackWinsGame(this); } else if (API.equals("LosesGame")) { if (isAb) SA = AbilityFactory_EndGameCondition.createAbilityLosesGame(this); else if (isSp) SA = AbilityFactory_EndGameCondition.createSpellLosesGame(this); else if (isDb) SA = AbilityFactory_EndGameCondition.createDrawbackLosesGame(this); } else if (API.equals("RevealHand")) { if (isAb) SA = AbilityFactory_Reveal.createAbilityRevealHand(this); else if (isSp) SA = AbilityFactory_Reveal.createSpellRevealHand(this); else if (isDb) SA = AbilityFactory_Reveal.createDrawbackRevealHand(this); } else if (API.equals("Dig")) { if (isAb) SA = AbilityFactory_Reveal.createAbilityDig(this); else if (isSp) SA = AbilityFactory_Reveal.createSpellDig(this); else if (isDb) SA = AbilityFactory_Reveal.createDrawbackDig(this); } else if (API.equals("Shuffle")) { if (isAb) SA = AbilityFactory_ZoneAffecting.createAbilityShuffle(this); else if (isSp) SA = AbilityFactory_ZoneAffecting.createSpellShuffle(this); else if (isDb) SA = AbilityFactory_ZoneAffecting.createDrawbackShuffle(this); } else if (API.equals("ChooseType")) { if (isAb) SA = AbilityFactory_Choose.createAbilityChooseType(this); else if (isSp) SA = AbilityFactory_Choose.createSpellChooseType(this); else if (isDb) SA = AbilityFactory_Choose.createDrawbackChooseType(this); } else if (API.equals("ChooseColor")) { if (isAb) SA = AbilityFactory_Choose.createAbilityChooseColor(this); else if (isSp) SA = AbilityFactory_Choose.createSpellChooseColor(this); else if (isDb) SA = AbilityFactory_Choose.createDrawbackChooseColor(this); } else if (API.equals("CopyPermanent")) { if (isAb) SA = AbilityFactory_Copy.createAbilityCopyPermanent(this); else if (isSp) SA = AbilityFactory_Copy.createSpellCopyPermanent(this); else if (isDb) SA = AbilityFactory_Copy.createDrawbackCopyPermanent(this); } else if (API.equals("CopySpell")) { if (isTargeted) // Since all "CopySpell" ABs copy things on the Stack no need for it to be everywhere abTgt.setZone("Stack"); if (isAb) SA = AbilityFactory_Copy.createAbilityCopySpell(this); else if (isSp) SA = AbilityFactory_Copy.createSpellCopySpell(this); else if (isDb) SA = AbilityFactory_Copy.createDrawbackCopySpell(this); hostCard.setCopiesSpells(true); } else if (API.equals("FlipACoin")) { if (isAb) SA = AbilityFactory_Clash.createAbilityFlip(this); else if (isSp) SA = AbilityFactory_Clash.createSpellFlip(this); else if (isDb) SA = AbilityFactory_Clash.createDrawbackFlip(this); } else if (API.equals("DelayedTrigger")) { if (isAb) SA = AbilityFactory_DelayedTrigger.getAbility(this); else if (isSp) SA = AbilityFactory_DelayedTrigger.getSpell(this); if (isDb) SA = AbilityFactory_DelayedTrigger.getDrawback(this); } else if (API.equals("Cleanup")) { if (isDb) SA = AbilityFactory_Cleanup.getDrawback(this); } else if (API.equals("RegenerateAll")) { if (isAb) SA = AbilityFactory_Regenerate.getAbilityRegenerateAll(this); else if (isSp) SA = AbilityFactory_Regenerate.getSpellRegenerateAll(this); else if (isDb) { SA = AbilityFactory_Regenerate.createDrawbackRegenerateAll(this); } } else if (API.equals("AnimateAll")) { if (isAb) SA = AbilityFactory_Animate.createAbilityAnimateAll(this); else if (isSp) SA = AbilityFactory_Animate.createSpellAnimateAll(this); else if (isDb) SA = AbilityFactory_Animate.createDrawbackAnimateAll(this); } else if (API.equals("Debuff")) { if (isAb) SA = AbilityFactory_Debuff.createAbilityDebuff(this); else if (isSp) SA = AbilityFactory_Debuff.createSpellDebuff(this); else if (isDb) SA = AbilityFactory_Debuff.createDrawbackDebuff(this); } else if (API.equals("DebuffAll")) { if (isAb) SA = AbilityFactory_Debuff.createAbilityDebuffAll(this); else if (isSp) SA = AbilityFactory_Debuff.createSpellDebuffAll(this); else if (isDb) SA = AbilityFactory_Debuff.createDrawbackDebuffAll(this); } else if (API.equals("DrainMana")) { if (isAb) SA = AbilityFactory_Mana.createAbilityDrainMana(this); else if (isSp) SA = AbilityFactory_Mana.createSpellDrainMana(this); else if (isDb) SA = AbilityFactory_Mana.createDrawbackDrainMana(this); } else if (API.equals("Protection")) { if (isAb) SA = AbilityFactory_Protection.createAbilityProtection(this); else if (isSp) SA = AbilityFactory_Protection.createSpellProtection(this); else if (isDb) SA = AbilityFactory_Protection.createDrawbackProtection(this); } else if (API.equals("Attach")) { if (isAb) SA = AbilityFactory_Attach.createAbilityAttach(this); else if (isSp) SA = AbilityFactory_Attach.createSpellAttach(this); else if (isDb) SA = AbilityFactory_Attach.createDrawbackAttach(this); } else if (API.equals("ProtectionAll")) { if (isAb) SA = AbilityFactory_Protection.createAbilityProtectionAll(this); else if (isSp) SA = AbilityFactory_Protection.createSpellProtectionAll(this); else if (isDb) SA = AbilityFactory_Protection.createDrawbackProtectionAll(this); } if (SA == null) throw new RuntimeException("AbilityFactory : SpellAbility was not created for " + hostCard.getName() + ". Looking for API: " + API); // ********************************************* // set universal properties of the SpellAbility SA.setAbilityFactory(this); if (hasSubAbility()) SA.setSubAbility(getSubAbility()); if (SA instanceof Spell_Permanent) SA.setDescription(SA.getSourceCard().getName()); else if (hasSpDesc) { StringBuilder sb = new StringBuilder(); if (!isDb) { // SubAbilities don't have Costs or Cost descriptors if (mapParams.containsKey("PrecostDesc")) sb.append(mapParams.get("PrecostDesc")).append(" "); if (mapParams.containsKey("CostDesc")) sb.append(mapParams.get("CostDesc")).append(" "); else sb.append(abCost.toString()); } sb.append(mapParams.get("SpellDescription")); SA.setDescription(sb.toString()); } else SA.setDescription(""); // StackDescriptions are overwritten by the AF type instead of through this //if (!isTargeted) // SA.setStackDescription(hostCard.getName()); makeRestrictions(SA); makeConditions(SA); return SA; } /** * <p>makeRestrictions.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private void makeRestrictions(SpellAbility sa) { // SpellAbility_Restrictions should be added in here SpellAbility_Restriction restrict = sa.getRestrictions(); if (mapParams.containsKey("Flashback")) { sa.setFlashBackAbility(true); } restrict.setRestrictions(mapParams); } /** * <p>makeConditions.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private void makeConditions(SpellAbility sa) { // SpellAbility_Restrictions should be added in here SpellAbility_Condition condition = sa.getConditions(); if (mapParams.containsKey("Flashback")) { sa.setFlashBackAbility(true); } condition.setConditions(mapParams); } /** * <p>checkConditional.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ public static boolean checkConditional(SpellAbility sa) { return sa.getConditions().checkConditions(sa); } // Easy creation of SubAbilities /** * <p>getSubAbility.</p> * * @return a {@link forge.card.spellability.Ability_Sub} object. */ public Ability_Sub getSubAbility() { Ability_Sub abSub = null; String sSub = getMapParams().get("SubAbility"); if (sSub.startsWith("SVar=")) { sSub = sSub.replace("SVar=", ""); } sSub = getHostCard().getSVar(sSub); if (!sSub.equals("")) { // Older style Drawback no longer supported AbilityFactory afDB = new AbilityFactory(); abSub = (Ability_Sub) afDB.getAbility(sSub, getHostCard()); } else { System.out.println("SubAbility not found for: " + getHostCard()); } return abSub; } /** * <p>playReusable.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ public static boolean playReusable(SpellAbility sa) { // TODO probably also consider if winter orb or similar are out if (sa.getPayCosts() == null) // This is only true for Drawbacks and triggers return true; return (sa.getPayCosts().isReusuableResource() && AllZone.getPhase().is(Constant.Phase.End_Of_Turn) && AllZone.getPhase().isNextTurn(AllZone.getComputerPlayer())); } //returns true if it's better to wait until blockers are declared /** * <p>waitForBlocking.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ public static boolean waitForBlocking(SpellAbility sa) { return (sa.getSourceCard().isCreature() && sa.getPayCosts().getTap() && (AllZone.getPhase().isBefore(Constant.Phase.Combat_Declare_Blockers_InstantAbility) || AllZone.getPhase().isNextTurn(AllZone.getHumanPlayer()))); } /** * <p>isSorcerySpeed.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ public static boolean isSorcerySpeed(SpellAbility sa) { if (sa.isSpell()) return sa.getSourceCard().isSorcery(); else if (sa.isAbility()) return sa.getRestrictions().getSorcerySpeed(); return false; } // Utility functions used by the AFs /** * <p>calculateAmount.</p> * * @param card a {@link forge.Card} object. * @param amount a {@link java.lang.String} object. * @param ability a {@link forge.card.spellability.SpellAbility} object. * @return a int. */ public static int calculateAmount(Card card, String amount, SpellAbility ability) { // amount can be anything, not just 'X' as long as sVar exists // If Amount is -X, strip the minus sign before looking for an SVar of that kind int multiplier = 1; if (amount.startsWith("-")) { multiplier = -1; amount = amount.substring(1); } if (!card.getSVar(amount).equals("")) { String calcX[] = card.getSVar(amount).split("\\$"); if (calcX.length == 1 || calcX[1].equals("none")) return 0; if (calcX[0].startsWith("Count")) return CardFactoryUtil.xCount(card, calcX[1]) * multiplier; if (calcX[0].startsWith("Number")) return CardFactoryUtil.xCount(card, card.getSVar(amount)) * multiplier; else if (ability != null) { //Player attribute counting if (calcX[0].startsWith("TargetedPlayer")) { ArrayList<Player> players = new ArrayList<Player>(); SpellAbility saTargeting = (ability.getTarget() == null) ? findParentsTargetedPlayer(ability) : ability; if (saTargeting.getTarget() != null) { players.addAll(saTargeting.getTarget().getTargetPlayers()); } else { players.addAll(getDefinedPlayers(card, saTargeting.getAbilityFactory().getMapParams().get("Defined"), saTargeting)); } return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; } if (calcX[0].startsWith("TargetedController")) { ArrayList<Player> players = new ArrayList<Player>(); ArrayList<Card> list = getDefinedCards(card, "Targeted", ability); ArrayList<SpellAbility> sas = getDefinedSpellAbilities(card, "Targeted", ability); for (Card c : list) { Player p = c.getController(); if (!players.contains(p)) players.add(p); } for (SpellAbility s : sas) { Player p = s.getSourceCard().getController(); if (!players.contains(p)) players.add(p); } return CardFactoryUtil.playerXCount(players, calcX[1], card) * multiplier; } CardList list = new CardList(); if (calcX[0].startsWith("Sacrificed")) list = findRootAbility(ability).getPaidList("Sacrificed"); else if (calcX[0].startsWith("Discarded")) list = findRootAbility(ability).getPaidList("Discarded"); else if (calcX[0].startsWith("Exiled")) { list = findRootAbility(ability).getPaidList("Exiled"); } else if (calcX[0].startsWith("Tapped")) { list = findRootAbility(ability).getPaidList("Tapped"); } else if (calcX[0].startsWith("Targeted")) { Target t = ability.getTarget(); if (null != t) { ArrayList<Object> all = t.getTargets(); if (!all.isEmpty() && all.get(0) instanceof SpellAbility) { SpellAbility saTargeting = findParentsTargetedSpellAbility(ability); list = new CardList(); ArrayList<SpellAbility> sas = saTargeting.getTarget().getTargetSAs(); for (SpellAbility sa : sas) { list.add(sa.getSourceCard()); } } else { SpellAbility saTargeting = findParentsTargetedCard(ability); list = new CardList(saTargeting.getTarget().getTargetCards().toArray()); } } else { SpellAbility parent = findParentsTargetedCard(ability); ArrayList<Object> all = parent.getTarget().getTargets(); if (!all.isEmpty() && all.get(0) instanceof SpellAbility) { list = new CardList(); ArrayList<SpellAbility> sas = parent.getTarget().getTargetSAs(); for (SpellAbility sa : sas) { list.add(sa.getSourceCard()); } } else { SpellAbility saTargeting = findParentsTargetedCard(ability); list = new CardList(saTargeting.getTarget().getTargetCards().toArray()); } } } else if (calcX[0].startsWith("Triggered")) { SpellAbility root = ability.getRootSpellAbility(); list = new CardList(); list.add((Card) root.getTriggeringObject(calcX[0].substring(9))); } else if (calcX[0].startsWith("Remembered")) { // Add whole Remembered list to handlePaid list = new CardList(); for (Object o : card.getRemembered()) { if (o instanceof Card) list.add(AllZoneUtil.getCardState((Card) o)); } } else if (calcX[0].startsWith("Imprinted")) { // Add whole Imprinted list to handlePaid list = new CardList(); for (Card c : card.getImprinted()) list.add(AllZoneUtil.getCardState(c)); } else if (calcX[0].startsWith("TriggerCount")) { // TriggerCount is similar to a regular Count, but just pulls Integer Values from Trigger objects String[] l = calcX[1].split("/"); String[] m = CardFactoryUtil.parseMath(l); int count = (Integer) ability.getTriggeringObject(l[0]); return CardFactoryUtil.doXMath(count, m, card) * multiplier; } else return 0; return CardFactoryUtil.handlePaid(list, calcX[1], card) * multiplier; } else return 0; } return Integer.parseInt(amount) * multiplier; } // should the three getDefined functions be merged into one? Or better to have separate? // If we only have one, each function needs to Cast the Object to the appropriate type when using // But then we only need update one function at a time once the casting is everywhere. // Probably will move to One function solution sometime in the future /** * <p>getDefinedCards.</p> * * @param hostCard a {@link forge.Card} object. * @param def a {@link java.lang.String} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.util.ArrayList} object. */ public static ArrayList<Card> getDefinedCards(Card hostCard, String def, SpellAbility sa) { ArrayList<Card> cards = new ArrayList<Card>(); String defined = (def == null) ? "Self" : def; // default to Self Card c = null; if (defined.equals("Self")) c = hostCard; else if (defined.equals("Equipped")) c = hostCard.getEquippingCard(); else if (defined.equals("Enchanted")) c = hostCard.getEnchantingCard(); else if (defined.equals("Targeted")) { SpellAbility parent = findParentsTargetedCard(sa); cards.addAll(parent.getTarget().getTargetCards()); } else if (defined.startsWith("Triggered") && sa != null) { SpellAbility root = sa.getRootSpellAbility(); Object crd = root.getTriggeringObject(defined.substring(9)); if (crd instanceof Card) { c = AllZoneUtil.getCardState((Card) crd); } } else if (defined.equals("Remembered")) { for (Object o : hostCard.getRemembered()) { if (o instanceof Card) cards.add(AllZoneUtil.getCardState((Card) o)); } } else if (defined.equals("Clones")) { for (Card clone : hostCard.getClones()) { cards.add(AllZoneUtil.getCardState(clone)); } } else if (defined.equals("Imprinted")) { for (Card imprint : hostCard.getImprinted()) { cards.add(AllZoneUtil.getCardState(imprint)); } } else { CardList list = null; if (defined.startsWith("Sacrificed")) list = findRootAbility(sa).getPaidList("Sacrificed"); else if (defined.startsWith("Discarded")) list = findRootAbility(sa).getPaidList("Discarded"); else if (defined.startsWith("Exiled")) list = findRootAbility(sa).getPaidList("Exiled"); else if (defined.startsWith("Tapped")) list = findRootAbility(sa).getPaidList("Tapped"); else return cards; for (Card cl : list) cards.add(cl); } if (c != null) cards.add(c); return cards; } /** * <p>getDefinedPlayers.</p> * * @param card a {@link forge.Card} object. * @param def a {@link java.lang.String} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.util.ArrayList} object. */ public static ArrayList<Player> getDefinedPlayers(Card card, String def, SpellAbility sa) { ArrayList<Player> players = new ArrayList<Player>(); String defined = (def == null) ? "You" : def; if (defined.equals("Targeted")) { Target tgt = sa.getTarget(); SpellAbility parent = sa; do { if (!(parent instanceof Ability_Sub)) // did not find any targets return players; parent = ((Ability_Sub) parent).getParent(); tgt = parent.getTarget(); } while (tgt == null || tgt.getTargetPlayers().size() == 0); players.addAll(tgt.getTargetPlayers()); } else if (defined.equals("TargetedController")) { ArrayList<Card> list = getDefinedCards(card, "Targeted", sa); ArrayList<SpellAbility> sas = getDefinedSpellAbilities(card, "Targeted", sa); for (Card c : list) { Player p = c.getController(); if (!players.contains(p)) players.add(p); } for (SpellAbility s : sas) { Player p = s.getSourceCard().getController(); if (!players.contains(p)) players.add(p); } } else if (defined.equals("TargetedOwner")) { ArrayList<Card> list = getDefinedCards(card, "Targeted", sa); for (Card c : list) { Player p = c.getOwner(); if (!players.contains(p)) players.add(p); } } else if (defined.equals("Remembered")) { for (Object rem : card.getRemembered()) { if (rem instanceof Player) players.add((Player) rem); } } else if (defined.startsWith("Triggered")) { SpellAbility root = sa.getRootSpellAbility(); Object o = null; if (defined.endsWith("Controller")) { String triggeringType = defined.substring(9); triggeringType = triggeringType.substring(0, triggeringType.length() - 10); Object c = root.getTriggeringObject(triggeringType); if (c instanceof Card) { o = ((Card) c).getController(); } if (c instanceof SpellAbility) { o = ((SpellAbility) c).getSourceCard().getController(); } } else if (defined.endsWith("Owner")) { String triggeringType = defined.substring(9); triggeringType = triggeringType.substring(0, triggeringType.length() - 5); Object c = root.getTriggeringObject(triggeringType); if (c instanceof Card) { o = ((Card) c).getOwner(); } } else { String triggeringType = defined.substring(9); o = root.getTriggeringObject(triggeringType); } if (o != null) { if (o instanceof Player) { Player p = (Player) o; if (!players.contains(p)) players.add(p); } } } else if (defined.equals("EnchantedController")) { Player p = card.getEnchantingCard().getController(); if (!players.contains(p)) players.add(p); } else if (defined.equals("EnchantedOwner")) { Player p = card.getEnchantingCard().getOwner(); if (!players.contains(p)) players.add(p); } else if (defined.equals("AttackingPlayer")) { Player p = AllZone.getCombat().getAttackingPlayer(); if (!players.contains(p)) players.add(p); } else if (defined.equals("DefendingPlayer")) { Player p = AllZone.getCombat().getDefendingPlayer(); if (!players.contains(p)) players.add(p); } else { if (defined.equals("You") || defined.equals("Each")) players.add(sa.getActivatingPlayer()); if (defined.equals("Opponent") || defined.equals("Each")) players.add(sa.getActivatingPlayer().getOpponent()); } return players; } /** * <p>getDefinedSpellAbilities.</p> * * @param card a {@link forge.Card} object. * @param def a {@link java.lang.String} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.util.ArrayList} object. */ public static ArrayList<SpellAbility> getDefinedSpellAbilities(Card card, String def, SpellAbility sa) { ArrayList<SpellAbility> sas = new ArrayList<SpellAbility>(); String defined = (def == null) ? "Self" : def; // default to Self SpellAbility s = null; //TODO - this probably needs to be fleshed out a bit, but the basics work if (defined.equals("Self")) { s = sa; } else if (defined.equals("Targeted")) { SpellAbility parent = findParentsTargetedSpellAbility(sa); sas.addAll(parent.getTarget().getTargetSAs()); } else if (defined.startsWith("Triggered")) { SpellAbility root = sa.getRootSpellAbility(); String triggeringType = defined.substring(9); Object o = root.getTriggeringObject(triggeringType); if (o instanceof SpellAbility) s = (SpellAbility) o; } else if (defined.startsWith("Imprinted")) { for (Card imp : card.getImprinted()) sas.addAll(imp.getSpellAbilities()); } if (s != null) sas.add(s); return sas; } /** * <p>getDefinedObjects.</p> * * @param card a {@link forge.Card} object. * @param def a {@link java.lang.String} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.util.ArrayList} object. */ public static ArrayList<Object> getDefinedObjects(Card card, String def, SpellAbility sa) { ArrayList<Object> objects = new ArrayList<Object>(); String defined = (def == null) ? "Self" : def; objects.addAll(getDefinedPlayers(card, defined, sa)); objects.addAll(getDefinedCards(card, defined, sa)); objects.addAll(getDefinedSpellAbilities(card, defined, sa)); return objects; } /** * <p>findRootAbility.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility findRootAbility(SpellAbility sa) { SpellAbility parent = sa; while (parent instanceof Ability_Sub) parent = ((Ability_Sub) parent).getParent(); return parent; } /** * <p>findParentsTargetedCard.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility findParentsTargetedCard(SpellAbility sa) { SpellAbility parent = sa; do { if (!(parent instanceof Ability_Sub)) return parent; parent = ((Ability_Sub) parent).getParent(); } while (parent.getTarget() == null || parent.getTarget().getTargetCards().size() == 0); return parent; } /** * <p>findParentsTargetedSpellAbility.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ private static SpellAbility findParentsTargetedSpellAbility(SpellAbility sa) { SpellAbility parent = sa; do { if (!(parent instanceof Ability_Sub)) return parent; parent = ((Ability_Sub) parent).getParent(); } while (parent.getTarget() == null || parent.getTarget().getTargetSAs().size() == 0); return parent; } /** * <p>findParentsTargetedPlayer.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public static SpellAbility findParentsTargetedPlayer(SpellAbility sa) { SpellAbility parent = sa; do { if (!(parent instanceof Ability_Sub)) return parent; parent = ((Ability_Sub) parent).getParent(); } while (parent.getTarget() == null || parent.getTarget().getTargetPlayers().size() == 0); return parent; } /** * <p>predictThreatenedObjects.</p> * * @return a {@link java.util.ArrayList} object. * @since 1.0.15 */ public static ArrayList<Object> predictThreatenedObjects() { ArrayList<Object> objects = new ArrayList<Object>(); if (AllZone.getStack().size() == 0) return objects; // check stack for something that will kill this SpellAbility topStack = AllZone.getStack().peekAbility(); objects.addAll(predictThreatenedObjects(topStack)); return objects; } /** * <p>predictThreatenedObjects.</p> * * @param topStack a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.util.ArrayList} object. * @since 1.0.15 */ public static ArrayList<Object> predictThreatenedObjects(SpellAbility topStack) { ArrayList<Object> objects = new ArrayList<Object>(); ArrayList<Object> threatened = new ArrayList<Object>(); if (topStack == null) return objects; Card source = topStack.getSourceCard(); AbilityFactory topAf = topStack.getAbilityFactory(); // Can only Predict things from AFs if (topAf != null) { Target tgt = topStack.getTarget(); if (tgt == null) { objects = getDefinedObjects(source, topAf.getMapParams().get("Defined"), topStack); } else { objects = tgt.getTargets(); } // Determine if Defined Objects are "threatened" will be destroyed due to this SA String api = topAf.getAPI(); if (api.equals("DealDamage")) { // If PredictDamage is >= Lethal Damage int dmg = AbilityFactory.calculateAmount(topStack.getSourceCard(), topAf.getMapParams().get("NumDmg"), topStack); for (Object o : objects) { if (o instanceof Card) { Card c = (Card) o; if (c.predictDamage(dmg, source, false) >= c.getKillDamage()) threatened.add(c); } else if (o instanceof Player) { Player p = (Player) o; if (source.hasKeyword("Infect")) { if (p.predictDamage(dmg, source, false) >= p.getPoisonCounters()) threatened.add(p); } else if (p.predictDamage(dmg, source, false) >= p.getLife()) threatened.add(p); } } } else if (api.equals("DamageAll")) { } else if (api.equals("Destroy")) { } else if (api.equals("DestroyAll")) { } } threatened.addAll(predictThreatenedObjects(topStack.getSubAbility())); return threatened; } /** * <p>handleRemembering.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. */ public static void handleRemembering(AbilityFactory AF) { HashMap<String, String> params = AF.getMapParams(); Card host; if (!params.containsKey("RememberTargets") && !params.containsKey("Imprint")) { return; } host = AF.getHostCard(); if (params.containsKey("ForgetOtherTargets")) { host.clearRemembered(); } if (params.containsKey("Unimprint")) { host.clearImprinted(); } Target tgt = AF.getAbTgt(); if (params.containsKey("RememberTargets")) { ArrayList<Object> tgts = (tgt == null) ? new ArrayList<Object>() : tgt.getTargets(); for (Object o : tgts) { host.addRemembered(o); } } if (params.containsKey("Imprint")) { ArrayList<Card> tgts = (tgt == null) ? new ArrayList<Card>() : tgt.getTargetCards(); host.addImprinted(tgts); } } /** * <p>filterListByType.</p> * * @param list a {@link forge.CardList} object. * @param params a {@link java.util.HashMap} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link forge.CardList} object. */ public static CardList filterListByType(CardList list, String type, SpellAbility sa) { if (type == null) return list; // Filter List Can send a different Source card in for things like Mishra and Lobotomy Card source = sa.getSourceCard(); if (type.contains("Triggered")) { Object o = sa.getTriggeringObject("Card"); // I won't the card attached to the Triggering object if (!(o instanceof Card)) return new CardList(); source = (Card) (o); type = type.replace("Triggered", "Card"); } else if (type.contains("Remembered")) { boolean hasRememberedCard = false; for (Object o : source.getRemembered()) { if (o instanceof Card) { hasRememberedCard = true; source = (Card) o; type = type.replace("Remembered", "Card"); break; } } if (!hasRememberedCard) return new CardList(); } return list.getValidCards(type.split(","), sa.getActivatingPlayer(), source); } /** * <p>passUnlessCost.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @param usedStack a boolean. */ public static void passUnlessCost(final SpellAbility sa, final boolean usedStack) { Card source = sa.getSourceCard(); AbilityFactory af = sa.getAbilityFactory(); final HashMap<String, String> params = af.getMapParams(); //Nothing to do if (params.get("UnlessCost") == null) { sa.resolve(); return; } //The player who has the chance to cancel the ability String pays = params.containsKey("UnlessPayer") ? params.get("UnlessPayer") : "TargetedController"; Player payer = getDefinedPlayers(sa.getSourceCard(), pays, sa).get(0); //The cost String unlessCost = params.get("UnlessCost").trim(); if (unlessCost.equals("X")) unlessCost = Integer.toString(AbilityFactory.calculateAmount(source, params.get("UnlessCost"), sa)); Ability ability = new Ability(source, unlessCost) { @Override public void resolve() { ; } }; final Command paidCommand = new Command() { private static final long serialVersionUID = 8094833091127334678L; public void execute() { resolveSubAbilities(sa); if(usedStack) AllZone.getStack().finishResolving(sa, false); } }; final Command unpaidCommand = new Command() { private static final long serialVersionUID = 8094833091127334678L; public void execute() { sa.resolve(); if (params.containsKey("PowerSink")) GameActionUtil.doPowerSink(AllZone.getHumanPlayer()); resolveSubAbilities(sa); if(usedStack) AllZone.getStack().finishResolving(sa, false); } }; if (payer.isHuman()) { GameActionUtil.payManaDuringAbilityResolve(source + "\r\n", ability.getManaCost(), paidCommand, unpaidCommand); } else { if (ComputerUtil.canPayCost(ability)) { ComputerUtil.playNoStack(ability); //Unless cost was payed - no resolve resolveSubAbilities(sa); if(usedStack) AllZone.getStack().finishResolving(sa, false); } else { sa.resolve(); if (params.containsKey("PowerSink")) GameActionUtil.doPowerSink(AllZone.getComputerPlayer()); resolveSubAbilities(sa); if(usedStack) AllZone.getStack().finishResolving(sa, false); } } } /** * <p>resolve.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @param usedStack a boolean. */ public static void resolve(SpellAbility sa, boolean usedStack) { if (sa == null) return; AbilityFactory af = sa.getAbilityFactory(); if(af == null) { sa.resolve(); return; } HashMap<String, String> params = af.getMapParams(); //check conditions if (AbilityFactory.checkConditional(sa)) { if (params.get("UnlessCost") == null) { sa.resolve(); //try to resolve subabilities (see null check above) resolveSubAbilities(sa); if(usedStack) AllZone.getStack().finishResolving(sa, false); } else passUnlessCost(sa, usedStack); } else resolveSubAbilities(sa); } /** * <p>resolveSubAbilities.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @since 1.0.15 */ public static void resolveSubAbilities(SpellAbility sa) { Ability_Sub abSub = sa.getSubAbility(); if (abSub == null || sa.isWrapper()) return; //check conditions if (AbilityFactory.checkConditional(abSub)) { abSub.resolve(); } resolveSubAbilities(abSub); } }//end class AbilityFactory