diff --git a/src/forge/AbilityFactory.java b/src/forge/AbilityFactory.java index 7a50787886e..c46cd9756b4 100644 --- a/src/forge/AbilityFactory.java +++ b/src/forge/AbilityFactory.java @@ -212,6 +212,7 @@ public class AbilityFactory { SA = AbilityFactory_Counters.createDrawbackProliferate(this); } + // do we want to merge Fetch, Retrieve and Bounce into ChangeZone? if (API.equals("Fetch")){ if (isAb) SA = AbilityFactory_Fetch.createAbilityFetch(this); @@ -226,6 +227,15 @@ public class AbilityFactory { SA = AbilityFactory_Fetch.createSpellRetrieve(this); } + if (API.equals("Bounce")){ + if (isAb) + SA = AbilityFactory_Bounce.createAbilityBounce(this); + else if (isSp) + SA = AbilityFactory_Bounce.createSpellBounce(this); + hostCard.setSVar("PlayMain1", "TRUE"); + } + // Above to be merged? If so, merge then do SubAbility for each + if (API.equals("Pump")) { AbilityFactory_Pump afPump = new AbilityFactory_Pump(this); @@ -234,8 +244,11 @@ public class AbilityFactory { SA = afPump.getAbility(); else if (isSp) SA = afPump.getSpell(); + else if (isDb) + SA = afPump.getDrawback(); - hostCard.setSVar("PlayMain1", "TRUE"); + if (isAb || isSp) + hostCard.setSVar("PlayMain1", "TRUE"); } if (API.equals("GainLife")){ @@ -261,14 +274,8 @@ public class AbilityFactory { SA = AbilityFactory_Combat.createAbilityFog(this); else if (isSp) SA = AbilityFactory_Combat.createSpellFog(this); - } - - if (API.equals("Bounce")){ - if (isAb) - SA = AbilityFactory_Bounce.createAbilityBounce(this); - else if (isSp) - SA = AbilityFactory_Bounce.createSpellBounce(this); - hostCard.setSVar("PlayMain1", "TRUE"); + else if (isDb) + SA = AbilityFactory_Combat.createDrawbackFog(this); } if (API.equals("Untap")){ @@ -276,6 +283,8 @@ public class AbilityFactory { SA = AbilityFactory_PermanentState.createAbilityUntap(this); else if (isSp) SA = AbilityFactory_PermanentState.createSpellUntap(this); + else if (isDb) + SA = AbilityFactory_PermanentState.createDrawbackUntap(this); } if (API.equals("Tap")){ @@ -283,6 +292,8 @@ public class AbilityFactory { SA = AbilityFactory_PermanentState.createAbilityTap(this); else if (isSp) SA = AbilityFactory_PermanentState.createSpellTap(this); + else if (isDb) + SA = AbilityFactory_PermanentState.createDrawbackTap(this); } if (API.equals("Regenerate")){ diff --git a/src/forge/AbilityFactory_Combat.java b/src/forge/AbilityFactory_Combat.java index a757febf939..b1c5b1d4a91 100644 --- a/src/forge/AbilityFactory_Combat.java +++ b/src/forge/AbilityFactory_Combat.java @@ -9,19 +9,14 @@ public class AbilityFactory_Combat { final AbilityFactory af = AF; - public boolean canPlay(){ - // super takes care of AdditionalCosts - return super.canPlay(); - } - public boolean canPlayAI() { - return putCanPlayAI(af, this); + return fogCanPlayAI(af, this); } @Override public void resolve() { - putResolve(af, this); + fogResolve(af, this); } }; @@ -34,28 +29,48 @@ public class AbilityFactory_Combat { final AbilityFactory af = AF; - public boolean canPlay(){ - // super takes care of AdditionalCosts - return super.canPlay(); - } - public boolean canPlayAI() { - return putCanPlayAI(af, this); + return fogCanPlayAI(af, this); } @Override public void resolve() { - putResolve(af, this); + fogResolve(af, this); } }; return spFog; } - public static boolean putCanPlayAI(final AbilityFactory af, SpellAbility sa){ - // AI cannot use this properly until he can use SAs during Humans turn - boolean chance = false; + public static SpellAbility createDrawbackFog(final AbilityFactory AF){ + final SpellAbility dbFog = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()){ + private static final long serialVersionUID = -5141246507533353605L; + + final AbilityFactory af = AF; + + @Override + public void resolve() { + fogResolve(af, this); + } + + @Override + public boolean chkAI_Drawback() { + return fogCanPlayAI(af, this); + } + + }; + return dbFog; + } + + public static boolean fogCanPlayAI(final AbilityFactory af, SpellAbility sa){ + // AI should only activate this during Human's Declare Blockers phase + boolean chance = AllZone.Phase.is(Constant.Phase.Combat_Declare_Blockers_InstantAbility, sa.getActivatingPlayer().getOpponent()); + + // Only cast when Stack is empty, so Human uses spells/abilities first + chance &= AllZone.Stack.size() == 0; + + // Some additional checks on how much Damage/Poison AI would take, or how many creatures would be lost Ability_Sub subAb = sa.getSubAbility(); if (subAb != null) @@ -64,7 +79,19 @@ public class AbilityFactory_Combat { return chance; } - public static void putResolve(final AbilityFactory af, final SpellAbility sa){ + public static boolean fogPlayDrawbackAI(final AbilityFactory af, SpellAbility sa){ + // AI should only activate this during Human's turn + boolean chance = AllZone.Phase.isPlayerTurn(sa.getActivatingPlayer().getOpponent()) || + AllZone.Phase.isAfter(Constant.Phase.Combat_Damage); + + Ability_Sub subAb = sa.getSubAbility(); + if (subAb != null) + chance &= subAb.chkAI_Drawback(); + + return chance; + } + + public static void fogResolve(final AbilityFactory af, final SpellAbility sa){ HashMap params = af.getMapParams(); Card card = sa.getSourceCard(); String DrawBack = params.get("SubAbility"); diff --git a/src/forge/AbilityFactory_PermanentState.java b/src/forge/AbilityFactory_PermanentState.java index a316a2e64f5..5bb8696a236 100644 --- a/src/forge/AbilityFactory_PermanentState.java +++ b/src/forge/AbilityFactory_PermanentState.java @@ -17,11 +17,6 @@ public class AbilityFactory_PermanentState { return untapStackDescription(af, this); } - public boolean canPlay(){ - // super takes care of AdditionalCosts - return super.canPlay(); - } - public boolean canPlayAI() { return untapCanPlayAI(af,this); @@ -46,12 +41,7 @@ public class AbilityFactory_PermanentState { public String getStackDescription(){ return untapStackDescription(af, this); } - - public boolean canPlay(){ - // super takes care of AdditionalCosts - return super.canPlay(); - } - + public boolean canPlayAI() { return untapCanPlayAI(af, this); @@ -66,11 +56,39 @@ public class AbilityFactory_PermanentState { return spUntap; } + public static SpellAbility createDrawbackUntap(final AbilityFactory AF){ + final SpellAbility dbUntap = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()){ + private static final long serialVersionUID = -4990932993654533449L; + + final AbilityFactory af = AF; + + @Override + public String getStackDescription(){ + return untapStackDescription(af, this); + } + + @Override + public void resolve() { + untapResolve(af, this); + } + + @Override + public boolean chkAI_Drawback() { + return untapPlayDrawbackAI(af, this); + } + + }; + return dbUntap; + } + public static String untapStackDescription(AbilityFactory af, SpellAbility sa){ // when getStackDesc is called, just build exactly what is happening StringBuilder sb = new StringBuilder(); - sb.append(sa.getSourceCard()).append(" - "); + if (sa instanceof Ability_Sub) + sb.append(" "); + else + sb.append(sa.getSourceCard()).append(" - "); sb.append("Untap "); @@ -161,6 +179,69 @@ public class AbilityFactory_PermanentState { return randomReturn; } + public static boolean untapPlayDrawbackAI(final AbilityFactory af, SpellAbility sa){ + // AI cannot use this properly until he can use SAs during Humans turn + Target tgt = af.getAbTgt(); + Card source = sa.getSourceCard(); + + boolean randomReturn = true; + + if (tgt == null){ + // who cares if its already untapped, it's only a subability? + } + else{ + CardList untapList = AllZoneUtil.getPlayerCardsInPlay(AllZone.ComputerPlayer); + untapList = untapList.filter(AllZoneUtil.tapped); + untapList = untapList.getValidCards(tgt.getValidTgts(), source.getController(), source); + // filter out enchantments and planeswalkers, their tapped state doesn't matter. + String[] tappablePermanents = {"Creature", "Land", "Artifact"}; + untapList = untapList.getValidCards(tappablePermanents, source.getController(), source); + + if (untapList.size() == 0) + return false; + + while(tgt.getNumTargeted() < tgt.getMaxTargets(sa.getSourceCard(), sa)){ + Card choice = null; + + if (untapList.size() == 0){ + 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 (untapList.getNotType("Creature").size() == 0) + choice = CardFactoryUtil.AI_getBestCreature(untapList); //if only creatures take the best + else + choice = CardFactoryUtil.AI_getMostExpensivePermanent(untapList, af.getHostCard(), false); + + if (choice == null){ // can't find anything left + 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; + } + } + + untapList.remove(choice); + tgt.addTarget(choice); + } + } + + Ability_Sub subAb = sa.getSubAbility(); + if (subAb != null) + randomReturn &= subAb.chkAI_Drawback(); + + return randomReturn; + } + public static void untapResolve(final AbilityFactory af, final SpellAbility sa){ HashMap params = af.getMapParams(); Card card = sa.getSourceCard(); @@ -192,8 +273,10 @@ public class AbilityFactory_PermanentState { } } } + // **************************************** + // ************** Tapping ***************** + // **************************************** - // ****** Tapping ******** public static SpellAbility createAbilityTap(final AbilityFactory AF){ final SpellAbility abTap = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()){ private static final long serialVersionUID = 5445572699000471299L; @@ -205,11 +288,6 @@ public class AbilityFactory_PermanentState { return tapStackDescription(af, this); } - public boolean canPlay(){ - // super takes care of AdditionalCosts - return super.canPlay(); - } - public boolean canPlayAI() { return tapCanPlayAI(af,this); @@ -235,11 +313,6 @@ public class AbilityFactory_PermanentState { return tapStackDescription(af, this); } - public boolean canPlay(){ - // super takes care of AdditionalCosts - return super.canPlay(); - } - public boolean canPlayAI() { return tapCanPlayAI(af, this); @@ -254,11 +327,39 @@ public class AbilityFactory_PermanentState { return spTap; } + public static SpellAbility createDrawbackTap(final AbilityFactory AF){ + final SpellAbility dbTap = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()){ + private static final long serialVersionUID = -4990932993654533449L; + + final AbilityFactory af = AF; + + @Override + public String getStackDescription(){ + return tapStackDescription(af, this); + } + + @Override + public void resolve() { + tapResolve(af, this); + } + + @Override + public boolean chkAI_Drawback() { + return tapPlayDrawbackAI(af, this); + } + + }; + return dbTap; + } + public static String tapStackDescription(AbilityFactory af, SpellAbility sa){ // when getStackDesc is called, just build exactly what is happening StringBuilder sb = new StringBuilder(); - sb.append(sa.getSourceCard()).append(" - "); + if (sa instanceof Ability_Sub) + sb.append(" "); + else + sb.append(sa.getSourceCard()).append(" - "); sb.append("Tap "); @@ -349,6 +450,70 @@ public class AbilityFactory_PermanentState { return randomReturn; } + public static boolean tapPlayDrawbackAI(final AbilityFactory af, SpellAbility sa){ + // AI cannot use this properly until he can use SAs during Humans turn + Target tgt = af.getAbTgt(); + Card source = sa.getSourceCard(); + + boolean randomReturn = true; + + if (tgt == null){ + // who cares if its already tapped, it's only a subability? + } + else{ + // target section, maybe pull this out? + CardList tapList = AllZoneUtil.getPlayerCardsInPlay(AllZone.ComputerPlayer); + tapList = tapList.filter(AllZoneUtil.untapped); + tapList = tapList.getValidCards(tgt.getValidTgts(), source.getController(), source); + // filter out enchantments and planeswalkers, their tapped state doesn't matter. + String[] tappablePermanents = {"Creature", "Land", "Artifact"}; + tapList = tapList.getValidCards(tappablePermanents, source.getController(), source); + + if (tapList.size() == 0) + return false; + + while(tgt.getNumTargeted() < tgt.getMaxTargets(sa.getSourceCard(), sa)){ + Card choice = null; + + if (tapList.size() == 0){ + 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 (tapList.getNotType("Creature").size() == 0) + choice = CardFactoryUtil.AI_getBestCreature(tapList); //if only creatures take the best + else + choice = CardFactoryUtil.AI_getMostExpensivePermanent(tapList, af.getHostCard(), false); + + if (choice == null){ // can't find anything left + 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; + } + } + + tapList.remove(choice); + tgt.addTarget(choice); + } + } + + Ability_Sub subAb = sa.getSubAbility(); + if (subAb != null) + randomReturn &= subAb.chkAI_Drawback(); + + return randomReturn; + } + public static void tapResolve(final AbilityFactory af, final SpellAbility sa){ HashMap params = af.getMapParams(); Card card = sa.getSourceCard(); diff --git a/src/forge/AbilityFactory_Pump.java b/src/forge/AbilityFactory_Pump.java index aa1122f052a..2876a34bc5b 100644 --- a/src/forge/AbilityFactory_Pump.java +++ b/src/forge/AbilityFactory_Pump.java @@ -50,7 +50,7 @@ public class AbilityFactory_Pump { private static final long serialVersionUID = 42244224L; public boolean canPlayAI() { - return doTgtAI(this); + return pumpPlayAI(this); } @Override @@ -73,69 +73,7 @@ public class AbilityFactory_Pump { @Override public boolean canPlayAI() { - if (!AllZone.GameAction.isCardInPlay(hostCard)) return false; - - // temporarily disabled until AI is improved - if (AF.getAbCost().getSacCost()) return false; - if (AF.getAbCost().getLifeCost()) return false; - if (AF.getAbCost().getSubCounter()){ - // instead of never removing counters, we will have a random possibility of failure. - // all the other tests still need to pass if a counter will be removed - Counters count = AF.getAbCost().getCounterType(); - double chance = .66; - if (count.equals(Counters.P1P1)){ // 10% chance to remove +1/+1 to pump - chance = .1; - } - else if (count.equals(Counters.CHARGE)){ // 50% chance to remove +1/+1 to pump - chance = .5; - } - Random r = new Random(); - if(r.nextFloat() > chance) - return false; - } - //if (bPumpEquipped && card.getEquippingCard() == null) return false; - - if (!ComputerUtil.canPayCost(this)) - return false; - - //don't risk sacrificing a creature just to pump it - if(this.getRestrictions().getActivationNumberSacrifice() != -1 && - this.getRestrictions().getNumberTurnActivations() >= (this.getRestrictions().getActivationNumberSacrifice() - 1)) { - return false; - } - - int defense = getNumDefense(this); - - if(AllZone.Phase.getPhase().equals(Constant.Phase.Main2)) return false; - - if(AF.getAbTgt() == null || !AF.getAbTgt().doesTarget()) { - Card creature; - //if (bPumpEquipped) - // creature = card.getEquippingCard(); - //else - creature = hostCard; - - if((creature.getNetDefense() + defense > 0) && (!creature.hasAnyKeyword(Keywords))) { - if(creature.hasSickness() && Keywords.contains("Haste")) - return true; - else if (creature.hasSickness() ^ Keywords.contains("Haste")) - return false; - else { - Random r = new Random(); - if(r.nextFloat() <= Math.pow(.6667, hostCard.getAbilityUsed())) - return CardFactoryUtil.AI_doesCreatureAttack(creature); - } - } - } - else - return doTgtAI(this); - - return false; - } - - @Override - public boolean canPlay() { - return super.canPlay(); + return pumpPlayAI(this); } @Override @@ -156,6 +94,33 @@ public class AbilityFactory_Pump { return abPump; } + public SpellAbility getDrawback() + { + SpellAbility dbPump = new Ability_Sub(hostCard, AF.getAbTgt()) { + private static final long serialVersionUID = 42244224L; + + public boolean canPlayAI() { + return pumpPlayAI(this); + } + + @Override + public String getStackDescription(){ + return pumpStackDescription(AF, this); + } + + public void resolve() { + doResolve(this); + }//resolve + + @Override + public boolean chkAI_Drawback() { + return doDrawbackAI(this); + } + };//SpellAbility + + return dbPump; + } + private int getNumAttack(SpellAbility sa) { return AbilityFactory.calculateAmount(hostCard, numAttack, sa); } @@ -232,6 +197,69 @@ public class AbilityFactory_Pump { return list; } + + private boolean pumpPlayAI(SpellAbility sa){ + // if there is no target and host card isn't in play, don't activate + if (AF.getAbTgt() == null && !AllZone.GameAction.isCardInPlay(hostCard)) + return false; + + // temporarily disabled until AI is improved + if (AF.getAbCost().getSacCost()) return false; + if (AF.getAbCost().getLifeCost()) return false; + if (AF.getAbCost().getSubCounter()){ + // instead of never removing counters, we will have a random possibility of failure. + // all the other tests still need to pass if a counter will be removed + Counters count = AF.getAbCost().getCounterType(); + double chance = .66; + if (count.equals(Counters.P1P1)){ // 10% chance to remove +1/+1 to pump + chance = .1; + } + else if (count.equals(Counters.CHARGE)){ // 50% chance to remove +1/+1 to pump + chance = .5; + } + Random r = new Random(); + if(r.nextFloat() > chance) + return false; + } + //if (bPumpEquipped && card.getEquippingCard() == null) return false; + + if (!ComputerUtil.canPayCost(sa)) + return false; + + //don't risk sacrificing a creature just to pump it + if(sa.getRestrictions().getActivationNumberSacrifice() != -1 && + sa.getRestrictions().getNumberTurnActivations() >= (sa.getRestrictions().getActivationNumberSacrifice() - 1)) { + return false; + } + + int defense = getNumDefense(sa); + + if(AllZone.Phase.is(Constant.Phase.Main2)) return false; + + if(AF.getAbTgt() == null || !AF.getAbTgt().doesTarget()) { + Card creature; + //if (bPumpEquipped) + // creature = card.getEquippingCard(); + //else + creature = hostCard; + + if((creature.getNetDefense() + defense > 0) && (!creature.hasAnyKeyword(Keywords))) { + if(creature.hasSickness() && Keywords.contains("Haste")) + return true; + else if (creature.hasSickness() ^ Keywords.contains("Haste")) + return false; + else { + Random r = new Random(); + if(r.nextFloat() <= Math.pow(.6667, hostCard.getAbilityUsed())) + return CardFactoryUtil.AI_doesCreatureAttack(creature); + } + } + } + else + return doTgtAI(sa); + + return false; + } private boolean doTgtAI(SpellAbility sa) { @@ -297,9 +325,25 @@ public class AbilityFactory_Pump { } return true; - } + private boolean doDrawbackAI(SpellAbility sa) + { + if(AF.getAbTgt() == null || !AF.getAbTgt().doesTarget()) { + int defense = getNumDefense(sa); + if (hostCard.isCreature()){ + if (!hostCard.hasKeyword("Indestructible") && hostCard.getNetDefense() + defense <= hostCard.getDamage()) + return false; + if (hostCard.getNetDefense() + defense <= 0) + return false; + } + } + else + return doTgtAI(sa); + + return true; + } + private String pumpStackDescription(AbilityFactory af, SpellAbility sa){ // when damageStackDescription is called, just build exactly what is happening StringBuilder sb = new StringBuilder(); @@ -313,8 +357,12 @@ public class AbilityFactory_Pump { tgtCards = new ArrayList(); tgtCards.add(hostCard); } - - sb.append(name).append(" - "); + + if (sa instanceof Ability_Sub) + sb.append(" "); + else + sb.append(name).append(" - "); + for(Card c : tgtCards){ sb.append(c.getName()); sb.append(" "); @@ -343,7 +391,8 @@ public class AbilityFactory_Pump { } } - sb.append("until end of turn."); + if (!params.containsKey("Permanent")) + sb.append("until end of turn."); Ability_Sub abSub = sa.getSubAbility(); @@ -379,28 +428,7 @@ public class AbilityFactory_Pump { final int a = getNumAttack(sa); final int d = getNumDefense(sa); - - final Command untilEOT = new Command() { - private static final long serialVersionUID = -42244224L; - - public void execute() { - if(AllZone.GameAction.isCardInPlay(tgtC)) { - tgtC.addTempAttackBoost(-1 * a); - tgtC.addTempDefenseBoost(-1 * d); - - if(Keywords.size() > 0) - { - for (int i=0; i 0) @@ -412,8 +440,31 @@ public class AbilityFactory_Pump { } } - AllZone.EndOfTurn.addUntil(untilEOT); - + if (!params.containsKey("Permanent")){ + // If not Permanent, remove Pumped at EOT + final Command untilEOT = new Command() { + private static final long serialVersionUID = -42244224L; + + public void execute() { + if(AllZone.GameAction.isCardInPlay(tgtC)) { + tgtC.addTempAttackBoost(-1 * a); + tgtC.addTempDefenseBoost(-1 * d); + + if(Keywords.size() > 0) + { + for (int i=0; i findIndex(phase); + } private int findIndex(String phase) { for(int i = 0; i < phaseOrder.length; i++) {