package forge.card.abilityFactory; import forge.*; import forge.card.cardFactory.CardFactoryUtil; import forge.card.spellability.*; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; //AB:GainControl|ValidTgts$Creature|TgtPrompt$Select target legendary creature|LoseControl$Untap,LoseControl|SpellDescription$Gain control of target xxxxxxx //GainControl specific params: // LoseControl - the lose control conditions (as a comma separated list) // -Untap - source card becomes untapped // -LoseControl - you lose control of source card // -LeavesPlay - source card leaves the battlefield // -PowerGT - (not implemented yet for Old Man of the Sea) // AddKWs - Keywords to add to the controlled card (as a "&"-separated list; like Haste, Sacrifice CARDNAME at EOT, any standard keyword) // OppChoice - set to True if opponent chooses creature (for Preacher) - not implemented yet // Untap - set to True if target card should untap when control is taken // DestroyTgt - actions upon which the tgt should be destroyed. same list as LoseControl // NoRegen - set if destroyed creature can't be regenerated. used only with DestroyTgt /** * <p>AbilityFactory_GainControl class.</p> * * @author Forge * @version $Id: $ */ public class AbilityFactory_GainControl { private final Card movedCards[] = new Card[1]; private AbilityFactory af = null; private HashMap<String, String> params = null; private Card hostCard = null; private ArrayList<String> lose = null; private ArrayList<String> destroyOn = null; private boolean bNoRegen = false; private boolean bUntap = false; private boolean bTapOnLose = false; private ArrayList<String> kws = null; /** * <p>Constructor for AbilityFactory_GainControl.</p> * * @param newAF a {@link forge.card.abilityFactory.AbilityFactory} object. */ public AbilityFactory_GainControl(AbilityFactory newAF) { af = newAF; params = af.getMapParams(); hostCard = af.getHostCard(); if (params.containsKey("LoseControl")) { lose = new ArrayList<String>(Arrays.asList(params.get("LoseControl").split(","))); } if (params.containsKey("Untap")) { bUntap = true; } if (params.containsKey("TapOnLose")) { bTapOnLose = true; } if (params.containsKey("AddKWs")) { kws = new ArrayList<String>(Arrays.asList(params.get("AddKWs").split(" & "))); } if (params.containsKey("DestroyTgt")) { destroyOn = new ArrayList<String>(Arrays.asList(params.get("DestroyTgt").split(","))); } if (params.containsKey("NoRegen")) { bNoRegen = true; } } /** * <p>getSpellGainControl.</p> * * @return a {@link forge.card.spellability.SpellAbility} object. * @since 1.0.15 */ public SpellAbility getSpellGainControl() { SpellAbility spControl = new Spell(hostCard, af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = 3125489644424832311L; @Override public boolean canPlayAI() { return gainControlTgtAI(this); } @Override public void resolve() { gainControlResolve(this); }//resolve @Override public String getStackDescription() { return gainControlStackDescription(this); } };//SpellAbility return spControl; } /** * <p>getAbilityGainControl.</p> * * @return a {@link forge.card.spellability.SpellAbility} object. * @since 1.0.15 */ public SpellAbility getAbilityGainControl() { final SpellAbility abControl = new Ability_Activated(hostCard, af.getAbCost(), af.getAbTgt()) { private static final long serialVersionUID = -4384705198674678831L; @Override public boolean canPlayAI() { return gainControlTgtAI(this); } @Override public void resolve() { gainControlResolve(this); hostCard.setAbilityUsed(hostCard.getAbilityUsed() + 1); } @Override public String getStackDescription() { return gainControlStackDescription(this); } @Override public boolean doTrigger(boolean mandatory) { return gainControlTgtAI(this); } };//Ability_Activated return abControl; } /** * <p>getDrawbackGainControl.</p> * * @return a {@link forge.card.spellability.SpellAbility} object. * @since 1.0.15 */ public SpellAbility getDrawbackGainControl() { SpellAbility dbControl = new Ability_Sub(hostCard, af.getAbTgt()) { private static final long serialVersionUID = -5577742598032345880L; @Override public boolean canPlayAI() { return gainControlTgtAI(this); } @Override public String getStackDescription() { return gainControlStackDescription(this); } @Override public void resolve() { gainControlResolve(this); }//resolve @Override public boolean chkAI_Drawback() { return gainControlDrawbackAI(this); } @Override public boolean doTrigger(boolean mandatory) { return gainControlTriggerAI(this, mandatory); } };//SpellAbility return dbControl; } /** * <p>gainControlStackDescription.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a {@link java.lang.String} object. */ private String gainControlStackDescription(SpellAbility sa) { StringBuilder sb = new StringBuilder(); if (!(sa instanceof Ability_Sub)) sb.append(sa.getSourceCard()).append(" - "); else sb.append(" "); ArrayList<Card> tgtCards; Target tgt = af.getAbTgt(); if (tgt != null) tgtCards = tgt.getTargetCards(); else { tgtCards = AbilityFactory.getDefinedCards(hostCard, params.get("Defined"), sa); } ArrayList<Player> newController = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("NewController"), sa); if (newController.size() == 0) newController.add(sa.getActivatingPlayer()); sb.append(newController).append(" gains control of "); for (Card c : tgtCards) { sb.append(" "); if (c.isFaceDown()) sb.append("Morph"); else sb.append(c); } sb.append("."); Ability_Sub abSub = sa.getSubAbility(); if (abSub != null) { sb.append(abSub.getStackDescription()); } return sb.toString(); } /** * <p>gainControlTgtAI.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private boolean gainControlTgtAI(SpellAbility sa) { boolean hasCreature = false; boolean hasArtifact = false; boolean hasEnchantment = false; boolean hasLand = false; Target tgt = af.getAbTgt(); //if Defined, then don't worry about targeting if (tgt == null) { return true; } CardList list = AllZoneUtil.getPlayerCardsInPlay(AllZone.getHumanPlayer()); list = list.getValidCards(tgt.getValidTgts(), hostCard.getController(), hostCard); //AI won't try to grab cards that are filtered out of AI decks on purpose list = list.filter(new CardListFilter() { public boolean addCard(Card c) { Map<String, String> vars = c.getSVars(); return !vars.containsKey("RemAIDeck") && CardFactoryUtil.canTarget(hostCard, c); } }); if (list.isEmpty()) return false; // Don't steal something if I can't Attack without, or prevent it from blocking at least if (lose != null && lose.contains("EOT") && AllZone.getPhase().isAfter(Constant.Phase.Combat_Declare_Blockers)) return false; while (tgt.getNumTargeted() < tgt.getMaxTargets(sa.getSourceCard(), sa)) { Card t = null; for (Card c : list) { if (c.isCreature()) hasCreature = true; if (c.isArtifact()) hasArtifact = true; if (c.isLand()) hasLand = true; if (c.isEnchantment()) hasEnchantment = true; } if (list.isEmpty()) { if (tgt.getNumTargeted() < tgt.getMinTargets(sa.getSourceCard(), sa) || tgt.getNumTargeted() == 0) { tgt.resetTargets(); return false; } else { // TODO is this good enough? for up to amounts? break; } } if (hasCreature) t = CardFactoryUtil.AI_getBestCreature(list); else if (hasArtifact) t = CardFactoryUtil.AI_getBestArtifact(list); else if (hasLand) t = CardFactoryUtil.AI_getBestLand(list); else if (hasEnchantment) t = CardFactoryUtil.AI_getBestEnchantment(list, sa.getSourceCard(), true); else t = CardFactoryUtil.AI_getMostExpensivePermanent(list, sa.getSourceCard(), true); tgt.addTarget(t); list.remove(t); hasCreature = false; hasArtifact = false; hasLand = false; hasEnchantment = false; } return true; } /** * <p>gainControlResolve.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. */ private void gainControlResolve(SpellAbility sa) { ArrayList<Card> tgtCards; Target tgt = af.getAbTgt(); if (tgt != null) tgtCards = tgt.getTargetCards(); else { tgtCards = AbilityFactory.getDefinedCards(hostCard, params.get("Defined"), sa); } //tgtCards.add(hostCard); ArrayList<Player> newController = AbilityFactory.getDefinedPlayers(sa.getSourceCard(), params.get("NewController"), sa); if (newController.size() == 0) newController.add(sa.getActivatingPlayer()); int size = tgtCards.size(); for (int j = 0; j < size; j++) { final Card tgtC = tgtCards.get(j); final Player originalController = tgtC.getController(); movedCards[j] = tgtC; hostCard.addGainControlTarget(tgtC); if (AllZoneUtil.isCardInPlay(tgtC) && CardFactoryUtil.canTarget(hostCard, tgtC)) { AllZone.getGameAction().changeController(new CardList(tgtC), tgtC.getController(), newController.get(0)); if (bUntap) tgtC.untap(); if (null != kws) { for (String kw : kws) { tgtC.addExtrinsicKeyword(kw); } } } //end copied if (lose != null) { if (lose.contains("LeavesPlay")) { hostCard.addLeavesPlayCommand(getLoseControlCommand(j, originalController)); } if (lose.contains("Untap")) { hostCard.addUntapCommand(getLoseControlCommand(j, originalController)); } if (lose.contains("LoseControl")) { hostCard.addChangeControllerCommand(getLoseControlCommand(j, originalController)); } if (lose.contains("EOT")) { AllZone.getEndOfTurn().addAt(getLoseControlCommand(j, originalController)); } } if (destroyOn != null) { if (destroyOn.contains("LeavesPlay")) { hostCard.addLeavesPlayCommand(getDestroyCommand(j)); } if (destroyOn.contains("Untap")) { hostCard.addUntapCommand(getDestroyCommand(j)); } if (destroyOn.contains("LoseControl")) { hostCard.addChangeControllerCommand(getDestroyCommand(j)); } } //for Old Man of the Sea - 0 is hardcoded since it only allows 1 target hostCard.clearGainControlReleaseCommands(); hostCard.addGainControlReleaseCommand(getLoseControlCommand(0, originalController)); }//end foreach target } /** * <p>gainControlTriggerAI.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @param mandatory a boolean. * @return a boolean. */ private boolean gainControlTriggerAI(SpellAbility sa, boolean mandatory) { if (!ComputerUtil.canPayCost(sa)) return false; if (sa.getTarget() == null) { if (mandatory) return true; } else { return gainControlTgtAI(sa); } return true; } /** * <p>gainControlDrawbackAI.</p> * * @param sa a {@link forge.card.spellability.SpellAbility} object. * @return a boolean. */ private boolean gainControlDrawbackAI(SpellAbility sa) { if (af.getAbTgt() == null || !af.getAbTgt().doesTarget()) { //all is good } else return gainControlTgtAI(sa); return true; }//pumpDrawbackAI() /** * <p>getDestroyCommand.</p> * * @param i a int. * @return a {@link forge.Command} object. */ private Command getDestroyCommand(final int i) { final Command destroy = new Command() { private static final long serialVersionUID = 878543373519872418L; public void execute() { final Card c = movedCards[i]; Ability ability = new Ability(hostCard, "0") { public void resolve() { if (bNoRegen) { AllZone.getGameAction().destroyNoRegeneration(c); } else { AllZone.getGameAction().destroy(c); } } }; StringBuilder sb = new StringBuilder(); sb.append(hostCard).append(" - destroy ").append(c.getName()).append("."); if (bNoRegen) sb.append(" It can't be regenerated."); ability.setStackDescription(sb.toString()); AllZone.getStack().addSimultaneousStackEntry(ability); } }; return destroy; } /** * <p>getLoseControlCommand.</p> * * @param i a int. * @param originalController a {@link forge.Player} object. * @return a {@link forge.Command} object. */ private Command getLoseControlCommand(final int i, final Player originalController) { final Command loseControl = new Command() { private static final long serialVersionUID = 878543373519872418L; public void execute() { Card c = movedCards[i]; //ArrayList<Card> c = hostCard.getGainControlTargets(); if (null == c) return; if (AllZoneUtil.isCardInPlay(c)) { AllZone.getGameAction().changeController(new CardList(c), c.getController(), originalController); if (bTapOnLose) c.tap(); if (null != kws) { for (String kw : kws) { c.removeExtrinsicKeyword(kw); } } }//if hostCard.clearGainControlTargets(); hostCard.clearGainControlReleaseCommands(); movedCards[i] = null; }//execute() }; return loseControl; } }//end class AbilityFactory_GainControl