package forge.card.abilityFactory; import forge.*; import forge.card.cardFactory.CardFactoryUtil; import forge.card.spellability.*; import java.util.ArrayList; import java.util.HashMap; //Destination - send countered spell to: (only applies to Spells; ignored for Abilities) // -Graveyard (Default) // -Exile // -TopOfLibrary // -Hand // -BottomOfLibrary // -ShuffleIntoLibrary //PowerSink - true if the drawback type part of Power Sink should be used //ExtraActions - this has been removed. All SubAbilitys should now use the standard SubAbility system //Examples: //A:SP$Counter | Cost$ 1 G | TargetType$ Activated | SpellDescription$ Counter target activated ability. //A:AB$Counter | Cost$ G G | TargetType$ Spell | Destination$ Exile | ValidTgts$ Color.Black | SpellDescription$ xxxxx /** * <p>AbilityFactory_CounterMagic class.</p> * * @author Forge * @version $Id: $ */ public class AbilityFactory_CounterMagic { private AbilityFactory af = null; private HashMap<String, String> params = null; private String destination = null; private String unlessCost = null; /** * <p>Constructor for AbilityFactory_CounterMagic.</p> * * @param newAF a {@link forge.card.abilityFactory.AbilityFactory} object. */ public AbilityFactory_CounterMagic(AbilityFactory newAF) { af = newAF; params = af.getMapParams(); destination = params.containsKey("Destination") ? params.get("Destination") : "Graveyard"; if (params.containsKey("UnlessCost")) unlessCost = params.get("UnlessCost").trim(); } /** * <p>getAbilityCounter.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public SpellAbility getAbilityCounter(final AbilityFactory AF) { final SpellAbility abCounter = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()) { private static final long serialVersionUID = -3895990436431818899L; @Override public String getStackDescription() { // when getStackDesc is called, just build exactly what is happening return counterStackDescription(af, this); } @Override public boolean canPlayAI() { return counterCanPlayAI(af, this); } @Override public void resolve() { counterResolve(af, this); } @Override public boolean doTrigger(boolean mandatory) { return counterCanPlayAI(af, this); } }; return abCounter; } /** * <p>getSpellCounter.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public SpellAbility getSpellCounter(final AbilityFactory AF) { final SpellAbility spCounter = new Spell(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()) { private static final long serialVersionUID = -4272851734871573693L; @Override public String getStackDescription() { return counterStackDescription(af, this); } @Override public boolean canPlayAI() { return counterCanPlayAI(af, this); } @Override public void resolve() { counterResolve(af, this); } }; return spCounter; } // Add Counter Drawback /** * <p>getDrawbackCounter.</p> * * @param AF a {@link forge.card.abilityFactory.AbilityFactory} object. * @return a {@link forge.card.spellability.SpellAbility} object. */ public SpellAbility getDrawbackCounter(final AbilityFactory AF) { final SpellAbility dbCounter = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()) { private static final long serialVersionUID = -4272851734871573693L; @Override public String getStackDescription() { return counterStackDescription(af, this); } @Override public boolean canPlayAI() { return counterCanPlayAI(af, this); } @Override public void resolve() { counterResolve(af, this); } @Override public boolean chkAI_Drawback() { return counterDoTriggerAI(af, this, true); } @Override public boolean doTrigger(boolean mandatory) { return counterDoTriggerAI(af, this, mandatory); } }; return dbCounter; } /** * <p>counterCanPlayAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private boolean counterCanPlayAI(final AbilityFactory af, final SpellAbility sa) { boolean toReturn = true; Cost abCost = af.getAbCost(); final Card source = sa.getSourceCard(); if (AllZone.getStack().size() < 1) { return false; } if (abCost != null) { // AI currently disabled for these costs if (abCost.getSacCost() && !abCost.getSacThis()) { //only sacrifice something that's supposed to be sacrificed String type = abCost.getSacType(); CardList typeList = AllZoneUtil.getPlayerCardsInPlay(AllZone.getComputerPlayer()); typeList = typeList.getValidCards(type.split(","), source.getController(), source); if (ComputerUtil.getCardPreference(source, "SacCost", typeList) == null) return false; } if (abCost.getLifeCost()) { if (AllZone.getComputerPlayer().getLife() - abCost.getLifeAmount() < 4) return false; } } Target tgt = sa.getTarget(); if (tgt != null) { SpellAbility topSA = AllZone.getStack().peekAbility(); if (!CardFactoryUtil.isCounterable(topSA.getSourceCard()) || topSA.getActivatingPlayer().isComputer()) return false; tgt.resetTargets(); if (Target_Selection.matchSpellAbility(sa, topSA, tgt)) tgt.addTarget(topSA); else return false; } if (unlessCost != null) { // Is this Usable Mana Sources? Or Total Available Mana? int usableManaSources = CardFactoryUtil.getUsableManaSources(AllZone.getHumanPlayer()); int toPay = 0; boolean setPayX = false; if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) { setPayX = true; toPay = ComputerUtil.determineLeftoverMana(sa); } else toPay = AbilityFactory.calculateAmount(source, unlessCost, sa); if (toPay == 0) return false; if (toPay <= usableManaSources) { // If this is a reusable Resource, feel free to play it most of the time if (!sa.getPayCosts().isReusuableResource() || MyRandom.random.nextFloat() < .4) return false; } if (setPayX) source.setSVar("PayX", Integer.toString(toPay)); } // TODO: Improve AI // Will return true if this spell can counter (or is Reusable and can force the Human into making decisions) // But really it should be more picky about how it counters things Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) toReturn &= subAb.chkAI_Drawback(); return toReturn; } /** * <p>counterDoTriggerAI.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @param mandatory a boolean. * @return a boolean. */ public boolean counterDoTriggerAI(AbilityFactory af, SpellAbility sa, boolean mandatory) { boolean toReturn = true; if (AllZone.getStack().size() < 1) { return false; } Target tgt = sa.getTarget(); if (tgt != null) { SpellAbility topSA = AllZone.getStack().peekAbility(); if (!CardFactoryUtil.isCounterable(topSA.getSourceCard()) || topSA.getActivatingPlayer().isComputer()) return false; tgt.resetTargets(); if (Target_Selection.matchSpellAbility(sa, topSA, tgt)) tgt.addTarget(topSA); else return false; Card source = sa.getSourceCard(); if (unlessCost != null) { // Is this Usable Mana Sources? Or Total Available Mana? int usableManaSources = CardFactoryUtil.getUsableManaSources(AllZone.getHumanPlayer()); int toPay = 0; boolean setPayX = false; if (unlessCost.equals("X") && source.getSVar(unlessCost).equals("Count$xPaid")) { setPayX = true; toPay = ComputerUtil.determineLeftoverMana(sa); } else toPay = AbilityFactory.calculateAmount(source, unlessCost, sa); if (toPay == 0) return false; if (toPay <= usableManaSources) { // If this is a reusable Resource, feel free to play it most of the time if (!sa.getPayCosts().isReusuableResource() || MyRandom.random.nextFloat() < .4) return false; } if (setPayX) source.setSVar("PayX", Integer.toString(toPay)); } } // TODO: Improve AI // Will return true if this spell can counter (or is Reusable and can force the Human into making decisions) // But really it should be more picky about how it counters things Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) toReturn &= subAb.chkAI_Drawback(); return toReturn; } /** * <p>counterResolve.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private void counterResolve(final AbilityFactory af, final SpellAbility sa) { // TODO: Before this resolves we should see if any of our targets are still on the stack ArrayList<SpellAbility> sas; Target tgt = af.getAbTgt(); if (tgt != null) sas = tgt.getTargetSAs(); else sas = AbilityFactory.getDefinedSpellAbilities(sa.getSourceCard(), params.get("Defined"), sa); if (params.containsKey("ForgetOtherTargets")) { if (params.get("ForgetOtherTargets").equals("True")) { af.getHostCard().clearRemembered(); } } for (final SpellAbility tgtSA : sas) { Card tgtSACard = tgtSA.getSourceCard(); if (tgtSA.isSpell() && tgtSACard.keywordsContain("CARDNAME can't be countered.")) continue; SpellAbility_StackInstance si = AllZone.getStack().getInstanceFromSpellAbility(tgtSA); if (si == null) continue; removeFromStack(tgtSA, sa, si); // Destroy Permanent may be able to be turned into a SubAbility if (tgtSA.isAbility() && params.containsKey("DestroyPermanent")) { AllZone.getGameAction().destroy(tgtSACard); } if (params.containsKey("RememberTargets")) { if (params.get("RememberTargets").equals("True")) { af.getHostCard().addRemembered(tgtSACard); } } } }//end counterResolve /** * <p>counterStackDescription.</p> * * @param af a {@link forge.card.abilityFactory.AbilityFactory} object. * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.lang.String} object. */ private String counterStackDescription(AbilityFactory af, SpellAbility sa) { StringBuilder sb = new StringBuilder(); if (!(sa instanceof Ability_Sub)) sb.append(sa.getSourceCard().getName()).append(" - "); else sb.append(" "); ArrayList<SpellAbility> sas; Target tgt = af.getAbTgt(); if (tgt != null) sas = tgt.getTargetSAs(); else sas = AbilityFactory.getDefinedSpellAbilities(sa.getSourceCard(), params.get("Defined"), sa); sb.append("countering"); boolean isAbility = false; for (final SpellAbility tgtSA : sas) { sb.append(" "); sb.append(tgtSA.getSourceCard()); isAbility = tgtSA.isAbility(); if (isAbility) sb.append("'s ability"); } if (isAbility && params.containsKey("DestroyPermanent")) { sb.append(" and destroy it"); } sb.append("."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); }//end counterStackDescription /** * <p>removeFromStack.</p> * * @param tgtSA a {@link forge.card.spellability.SpellAbility} object. * @param srcSA a {@link forge.card.spellability.SpellAbility} object. * @param si a {@link forge.card.spellability.SpellAbility_StackInstance} object. */ private void removeFromStack(SpellAbility tgtSA, SpellAbility srcSA, SpellAbility_StackInstance si) { AllZone.getStack().remove(si); if (tgtSA.isAbility()) { //For Ability-targeted counterspells - do not move it anywhere, even if Destination$ is specified. } else if (destination.equals("Graveyard")) { AllZone.getGameAction().moveToGraveyard(tgtSA.getSourceCard()); } else if (destination.equals("Exile")) { AllZone.getGameAction().exile(tgtSA.getSourceCard()); } else if (destination.equals("TopOfLibrary")) { AllZone.getGameAction().moveToLibrary(tgtSA.getSourceCard()); } else if (destination.equals("Hand")) { AllZone.getGameAction().moveToHand(tgtSA.getSourceCard()); } else if (destination.equals("BottomOfLibrary")) { AllZone.getGameAction().moveToBottomOfLibrary(tgtSA.getSourceCard()); } else if (destination.equals("ShuffleIntoLibrary")) { AllZone.getGameAction().moveToBottomOfLibrary(tgtSA.getSourceCard()); tgtSA.getSourceCard().getController().shuffle(); } else { throw new IllegalArgumentException("AbilityFactory_CounterMagic: Invalid Destination argument for card " + srcSA.getSourceCard().getName()); } if (!tgtSA.isAbility()) System.out.println("Send countered spell to " + destination); } }//end class AbilityFactory_CounterMagic