Page 1 of 1

AbilityFactory-based SubAbilities

PostPosted: 18 Nov 2010, 12:53
by Rob Cashwalker
I'm not really sure how this is supposed to work anymore. I've gotten some basics started, but when it actually comes to constructing a SpellAbility object, I run into a bit of a wall. I'm going to paste up my changes so someone else, preferably friarsol, can figure out what to make of it next.

AbilityFactory_DealDamage:
Code: Select all

package forge;

import java.util.ArrayList;
import java.util.Random;

public class AbilityFactory_DealDamage {

   public AbilityFactory_DealDamage(AbilityFactory newAF)
   {
      AF = newAF;
      
        String tmpND = AF.getMapParams().get("NumDmg");
        if (tmpND.length() > 0)
        {
           if (tmpND.matches("[xX]"))
              XDamage = AF.getHostCard().getSVar(tmpND.substring(1));
           
           else if (tmpND.matches("[0-9][0-9]?"))
              nDamage = Integer.parseInt(tmpND);
        }
       
      if(AF.getMapParams().containsKey("Tgt"))
         if (AF.getMapParams().get("Tgt").equals("TgtOpp"))
            TgtOpp = true;
      
        if(AF.hasSubAbility())
        {
           String sSub = AF.getMapParams().get("SubAbility");
           
           if (sSub.matches("[xX]."))
              sSub = AF.getHostCard().getSVar(sSub.substring(1));
           
           if (sSub.startsWith("DB$"))
           {
              AbilityFactory afDB = new AbilityFactory();
              subAbAF = afDB.getAbility(sSub, AF.getHostCard());
              hasSubAbAF = true;
           }
           else
           {
              subAbStr = sSub;
              hasSubAbStr = true;
           }
        }

   }
   
   private AbilityFactory AF = null;
   
   private int nDamage = -1;
   
   private String XDamage = "none";
      
   private boolean TgtOpp = false;
   
   private SpellAbility subAbAF = null;
   private boolean hasSubAbAF = false;
   private String subAbStr = "none";
   private boolean hasSubAbStr = false;
   
   //public SpellAbility getAbility(final AbilityFactory af, final int NumDmg, final String NumDmgX)
   public SpellAbility getAbility()
   {
      //AF = af;
      //nDamage = NumDmg;
      //XDamage = NumDmgX;
      
//      if(af.getMapParams().containsKey("Tgt"))
   //      if (AF.getMapParams().get("Tgt").equals("TgtOpp"))
      //      TgtOpp = true;

            
        final SpellAbility abDamage = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt())
        {
           private static final long serialVersionUID = -7560349014757367722L;
           
            @Override
            public boolean canPlay(){
                return super.canPlay();
            }
           
            @Override
            public boolean canPlayAI() {
               return doCanPlayAI(this);
               
            }
           
         @Override
         public String getStackDescription(){
            return damageStackDescription(AF, this);
         }
           
            @Override
            public void resolve() {
               doResolve(this);
               AF.getHostCard().setAbilityUsed(AF.getHostCard().getAbilityUsed() + 1);
               
            }
        };//Ability_Activated
      
      return abDamage;
   }
   
   //public SpellAbility getSpell(final AbilityFactory af, final int NumDmg, final String NumDmgX)
   public SpellAbility getSpell()
   {
      //AF = af;
      //nDamage = NumDmg;
      //XDamage = NumDmgX;
      
//      if(AF.getMapParams().containsKey("Tgt"))
   //      if (AF.getMapParams().get("Tgt").equals("TgtOpp"))
      //      TgtOpp = true;
      
      final SpellAbility spDealDamage = new Spell(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()) {
            private static final long serialVersionUID = 7239608350643325111L;

            @Override
            public boolean canPlay(){
                return super.canPlay();
            }
           
            @Override
            public boolean canPlayAI() {
               return doCanPlayAI(this);
               
            }
           
         @Override
         public String getStackDescription(){
            return damageStackDescription(AF, this);
         }
           
            @Override
            public void resolve() {
               doResolve(this);
               
            }

       
        }; // Spell
       
      return spDealDamage;
   }

   public SpellAbility getDrawback()
   {
      
      
      final SpellAbility dbDealDamage = new Spell(AF.getHostCard(), null, null) {
            private static final long serialVersionUID = 7239608350643325111L;

            @Override
            public boolean canPlay(){
                return super.canPlay();
            }
           
            @Override
            public boolean chkAI_Drawback() {
               return doCanPlayAI(this);
               
            }
           
         @Override
         public String getStackDescription(){
            return damageStackDescription(AF, this);
         }
           
            @Override
            public void resolve() {
               doResolve(this);
               
            }

       
        }; // Spell
       
      return dbDealDamage;
   }

   
    private int getNumDamage(SpellAbility saMe) {
        if(nDamage != -1) return nDamage;
       
      String calcX[] = XDamage.split("\\$");
      
      if (calcX.length == 1 || calcX[1].equals("none"))
         return 0;
      
      if (calcX[0].startsWith("Count"))
      {
         return CardFactoryUtil.xCount(AF.getHostCard(), calcX[1]);
      }
      else if (calcX[0].startsWith("Sacrificed"))
      {
         return CardFactoryUtil.handlePaid(saMe.getSacrificedCost(), calcX[1]);
      }
      
      return 0;
    }
   
    private boolean shouldTgtP(int d) {
        PlayerZone compHand = AllZone.getZone(Constant.Zone.Hand, AllZone.ComputerPlayer);
        CardList hand = new CardList(compHand.getCards());
       
        if(AF.isSpell() && hand.size() > 7) // anti-discard-at-EOT
           return true;
       
        if(AllZone.HumanPlayer.getLife() - d < 10) // if damage from this spell would drop the human to less than 10 life
           return true;
       
        return false;
    }
   
    private Card chooseTgtC(final int d) {
        // Combo alert!!
        PlayerZone compy = AllZone.getZone(Constant.Zone.Play, AllZone.ComputerPlayer);
        CardList cPlay = new CardList(compy.getCards());
        if(cPlay.size() > 0) for(int i = 0; i < cPlay.size(); i++)
            if(cPlay.get(i).getName().equals("Stuffy Doll")) return cPlay.get(i);
       
        PlayerZone human = AllZone.getZone(Constant.Zone.Play, AllZone.HumanPlayer);
        CardList hPlay = new CardList(human.getCards());
        hPlay = hPlay.filter(new CardListFilter() {
            public boolean addCard(Card c) {
                // will include creatures already dealt damage
                return c.isCreature() && ((c.getNetDefense() + c.getDamage()) <= d)
                        && CardFactoryUtil.canTarget(AF.getHostCard(), c);
            }
        });
       
        if(hPlay.size() > 0) {
            Card best = hPlay.get(0);
           
            if(hPlay.size() > 1) {
                for(int i = 1; i < hPlay.size(); i++) {
                    Card b = hPlay.get(i);
                    // choose best overall creature?
                    if(b.getSpellAbility().length > best.getSpellAbility().length
                            || b.getKeyword().size() > best.getKeyword().size()
                            || b.getNetAttack() > best.getNetAttack()) best = b;
                }
            }
            return best;
        }
        return null;
    }

    private boolean doCanPlayAI(SpellAbility saMe)
    {
       // temporarily disabled until better AI
       if (AF.getAbCost().getSacCost())    return false;
       if (AF.getAbCost().getSubCounter())  return false;
       if (AF.getAbCost().getLifeCost())    return false;
       
       if (!ComputerUtil.canPayCost(saMe))
          return false;

      // TODO handle proper calculation of X values based on Cost
        int damage = getNumDamage(saMe);
       
        boolean rr = AF.isSpell();
       
        if (AF.isAbility())
        {
           Random r = new Random(); // prevent run-away activations
           if(r.nextFloat() <= Math.pow(.6667, AF.getHostCard().getAbilityUsed()))
              rr = true;
        }
       
        Target tgt = AF.getAbTgt();
        // AI handle multi-targeting?
        tgt.resetTargets();
       // target loop
      while(tgt.getNumTargeted() < tgt.getMaxTargets()){
           // TODO: Consider targeting the planeswalker
           if(tgt.canTgtCreatureAndPlayer()) {

               if(shouldTgtP(damage)) {
                  tgt.addTarget(AllZone.HumanPlayer);
                   continue;
               }
              
               Card c = chooseTgtC(damage);
               if(c != null) {
                  tgt.addTarget(c);
                   continue;
               }
           }
          
           if(tgt.canTgtPlayer() || TgtOpp) {
              tgt.addTarget(AllZone.HumanPlayer);
               continue;
           }
          
           if(tgt.canTgtCreature()) {
               Card c = chooseTgtC(damage);
               if(c != null) {
                  tgt.addTarget(c);
                   continue;
               }
           }
           // fell through all the choices, no targets left?
         if (tgt.getNumTargeted() < tgt.getMinTargets() || tgt.getNumTargeted() == 0){
            tgt.resetTargets();
            return false;
         }
         else{
            // todo is this good enough? for up to amounts?
            break;
         }
      }
       
        return rr;
       
    }
   
    private String damageStackDescription(AbilityFactory af, SpellAbility sa){
      // when damageStackDescription is called, just build exactly what is happening
       StringBuilder sb = new StringBuilder();
       String name = af.getHostCard().getName();
       int damage = getNumDamage(sa);

       ArrayList<Object> tgts;
       Target tgt = AF.getAbTgt();
       if (tgt != null)
         tgts = tgt.getTargets();
       else{
         tgts = new ArrayList<Object>();
         if (TgtOpp)
            tgts.add(AF.getHostCard().getController().getOpponent());
       }
          
       sb.append(name).append(" - ");
       sb.append("Deals ").append(damage).append(" damage to ");
       for(int i = 0; i < tgts.size(); i++){
          Object o = tgts.get(0);
          if (o instanceof Player){
             sb.append(((Player)o).getName());
          }
          else{
             sb.append(((Card)o).getName());
          }
          sb.append(" ");
       }

       return sb.toString();
    }
   
    private void doResolve(SpellAbility saMe)
    {
        int damage = getNumDamage(saMe);
       
        ArrayList<Object> tgts;
        Target tgt = AF.getAbTgt();
        if (tgt != null)
           tgts = tgt.getTargets();
        else{
           tgts = new ArrayList<Object>();
           if (TgtOpp)
              tgts.add(AF.getHostCard().getController().getOpponent());
        }

        if (tgts.size() == 0){
           System.out.println("No targets?");
           return;
        }
       
        for(Object o : tgts){
           if (o instanceof Card){
               Card c = (Card)o;
               if(AllZone.GameAction.isCardInPlay(c) && CardFactoryUtil.canTarget(AF.getHostCard(), c))
                     c.addDamage(damage, AF.getHostCard());
           }
           else if (o instanceof Player){
              Player p = (Player) o;
              if (p.canTarget(AF.getHostCard()))
                 p.addDamage(damage, AF.getHostCard());
           }
        }
       
        Object obj = tgts.get(0);
       
        Player pl = null;
        Card c = null;
       
        if (obj instanceof Card){
           c = (Card)obj;
           pl = c.getController();
        }
        else{
           pl = (Player)obj;
        }
       
       if (hasSubAbAF)   
          subAbAF.resolve();
       
       else if (hasSubAbStr)
          CardFactoryUtil.doDrawBack(subAbStr, damage, AF.getHostCard().getController(),
             AF.getHostCard().getController().getOpponent(),   pl, AF.getHostCard(), c, saMe);

    }
}
My test card will be Orcish Cannonade. So my first task was to clean up DealDamage to follow how I initialized AbilityFactory_Pump. This approach ensures that the parameters are parsed in the same uniform way despite being a spell or ability... the important part is
Code: Select all

   public SpellAbility getDrawback()
   {
      
      final SpellAbility dbDealDamage = new Spell(AF.getHostCard(), null, null) {
            private static final long serialVersionUID = 7239608350643325111L;

            @Override
            public boolean canPlay(){
                return super.canPlay();
            }
           
            @Override
            public boolean chkAI_Drawback() {
               return doCanPlayAI(this);
               
            }
           
         @Override
         public String getStackDescription(){
            return damageStackDescription(AF, this);
         }
           
            @Override
            public void resolve() {
               doResolve(this);
               
            }

       
        }; // Spell
       
      return dbDealDamage;
   }
AbilityFactory:
Code: Select all

   private boolean isAb = false;
   private boolean isSp = false;
   private boolean isDb = false;
...

   public boolean isSpell()
   {
      return isSp;
   }
   
   public boolean isDrawback()
   {
      return isDb;
   }
   
   private Ability_Cost abCost = null;
...
      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 (!mapParams.containsKey("Cost") && (!isDb))
         throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + hostCard.getName());
      abCost = new Ability_Cost(mapParams.get("Cost"), hostCard.getName(), isAb);
...
      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();         
           
      }
This includes the clean up to put the parsing of NumDmg into the AF_DealDamage. Essentially it enables checking for DB${API}.

SpellAbility:
Code: Select all

    public boolean canPlayAI() {
        return true;
    }
   
    public boolean chkAI_Drawback() {
       return true;
    }
   
    public void chooseTargetAI() {
        randomTarget.execute(this);
    }
Just creates a new method for the parent ability AI to check.

My roadblock is creating the new Spell or should it be an Ability? Target? Cost? Drawbacks don't necessarily target, but their effect may need to be limited with syntax similar to Target. (need to add "You" as a "target"?) Drawbacks don't have a cost, but I don't know how a null Cost object would affect things.

Re: AbilityFactory-based SubAbilities

PostPosted: 18 Nov 2010, 17:53
by friarsol
I'll take a look this afternoon.

My first thought is maybe SubAbilities need to be their own class that inherit from SpellAbility. This way we don't have the confusion of "is this a spell or an ability?" Also, SubAbilities should never be on the stack by themselves, and don't have Costs. Sometimes they'll have Targets, which will need to be a consideration of Target_Selection.

I'll give you an update if I figure something out.

Re: AbilityFactory-based SubAbilities

PostPosted: 18 Nov 2010, 19:06
by friarsol
Ok. I have two cards working right now:

Code: Select all
Name:Orcish Cannonade
ManaCost:1 R R
Types:Instant
Text:no text
A:SP$DealDamage | Cost$ 1 R R | Tgt$ CP | NumDmg$2 | SubAbility$SVar=DB1 | SpellDescription$ Orcish Cannonade deals 2 damage to target creature or player and 3 damage to you. Draw a card.
SVar:DB1:DB$DealDamage | NumDmg$ 3 | Effected$ You
K:Draw a card.
SVar:Rarity:Common
SVar:Picture:http://www.wizards.com/global/images/magic/general/orcish_cannonade.jpg
End
And
Code: Select all
Name:Psionic Entity
ManaCost:4 U
Types:Creature Illusion
Text:no text
PT:2/2
A:AB$DealDamage | Cost$ T | Tgt$ CP | NumDmg$2 | SubAbility$SVar=DB1 | SpellDescription$ Psionic Entity deals 2 damage to target creature or player and 3 damage to itself.
SVar:DB1:DB$DealDamage | NumDmg$ 3 | Effected$ Self
SVar:Rarity:Rare
SVar:Picture:http://www.wizards.com/global/images/magic/general/psionic_entity.jpg
End
Here's my additions/changes:
Created Ability_Sub for Subabilities. I'm not sold on the name, just needed something I could use. This is used instead of your changes to SpellAbility
Code: Select all
package forge;

abstract public class Ability_Sub extends SpellAbility implements java.io.Serializable {
   private static final long serialVersionUID = 4650634415821733134L;

   private SpellAbility parent = null;
   
   public Ability_Sub(Card sourceCard, Target tgt) {
      super(SpellAbility.Ability, sourceCard);
      
   }

   @Override
   public boolean canPlay() {
      // this should never be on the Stack by itself
      return false;
   }

   abstract public boolean chkAI_Drawback();
   
   public void setParent(SpellAbility parent) {
      this.parent = parent;
      this.setActivatingPlayer(parent.getActivatingPlayer());
   }

   public SpellAbility getParent() {
      return parent;
   }
}
AF_DealDamage (whole file cause I moved a few things around so I could find them better.
Code: Select all

    package forge;

    import java.util.ArrayList;
    import java.util.Random;

    public class AbilityFactory_DealDamage {
        private AbilityFactory AF = null;
       
        private int nDamage = -1;
       
        private String XDamage = "none";
           
        private boolean TgtOpp = false;
       
        private Ability_Sub subAbAF = null;
        private boolean hasSubAbAF = false;
        private String subAbStr = "none";
        private boolean hasSubAbStr = false;
       
       
       public AbilityFactory_DealDamage(AbilityFactory newAF)
       {
          AF = newAF;
         
            String tmpND = AF.getMapParams().get("NumDmg");
            if (tmpND.length() > 0)
            {
               if (tmpND.matches("[xX]"))
                  XDamage = AF.getHostCard().getSVar(tmpND.substring(1));
               
               else if (tmpND.matches("[0-9][0-9]?"))
                  nDamage = Integer.parseInt(tmpND);
            }
           
          if(AF.getMapParams().containsKey("Tgt"))
             if (AF.getMapParams().get("Tgt").equals("TgtOpp"))
                TgtOpp = true;
         
            if(AF.hasSubAbility())
            {
               String sSub = AF.getMapParams().get("SubAbility");
               
               if (sSub.startsWith("SVar="))
                  sSub = AF.getHostCard().getSVar(sSub.split("=")[1]);
               
               if (sSub.startsWith("DB$"))
               {
                  AbilityFactory afDB = new AbilityFactory();
                  subAbAF = (Ability_Sub)afDB.getAbility(sSub, AF.getHostCard());
                  hasSubAbAF = true;
               }
               else
               {
                  subAbStr = sSub;
                  hasSubAbStr = true;
               }
            }

       }
       
       //public SpellAbility getAbility(final AbilityFactory af, final int NumDmg, final String NumDmgX)
       public SpellAbility getAbility()
       {
            final SpellAbility abDamage = new Ability_Activated(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt())
            {
               private static final long serialVersionUID = -7560349014757367722L;
               
                @Override
                public boolean canPlay(){
                    return super.canPlay();
                }
               
                @Override
                public boolean canPlayAI() {
                   return doCanPlayAI(this);
                   
                }
               
             @Override
             public String getStackDescription(){
                return damageStackDescription(AF, this);
             }
               
                @Override
                public void resolve() {
                   doResolve(this);
                   AF.getHostCard().setAbilityUsed(AF.getHostCard().getAbilityUsed() + 1);
                   
                }
            };//Ability_Activated
         
          return abDamage;
       }
       
       //public SpellAbility getSpell(final AbilityFactory af, final int NumDmg, final String NumDmgX)
       public SpellAbility getSpell()
       {
          final SpellAbility spDealDamage = new Spell(AF.getHostCard(), AF.getAbCost(), AF.getAbTgt()) {
                private static final long serialVersionUID = 7239608350643325111L;

                @Override
                public boolean canPlay(){
                    return super.canPlay();
                }
               
                @Override
                public boolean canPlayAI() {
                   return doCanPlayAI(this);
                   
                }
               
             @Override
             public String getStackDescription(){
                return damageStackDescription(AF, this);
             }
               
                @Override
                public void resolve() {
                   doResolve(this);
                   
                }

           
            }; // Spell
           
          return spDealDamage;
       }

       public SpellAbility getDrawback()
       {
         
         
      final SpellAbility dbDealDamage = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()) {
         private static final long serialVersionUID = 7239608350643325111L;

         @Override
         public boolean chkAI_Drawback() {
            return doCanPlayAI(this);
         }

         @Override
         public String getStackDescription() {
            return damageStackDescription(AF, this);
         }

         @Override
         public void resolve() {
            doResolve(this);
         }

      }; // Spell

      return dbDealDamage;
   }

       
        private int getNumDamage(SpellAbility saMe) {
            if(nDamage != -1) return nDamage;
           
          String calcX[] = XDamage.split("\\$");
         
          if (calcX.length == 1 || calcX[1].equals("none"))
             return 0;
         
          if (calcX[0].startsWith("Count"))
          {
             return CardFactoryUtil.xCount(AF.getHostCard(), calcX[1]);
          }
          else if (calcX[0].startsWith("Sacrificed"))
          {
             return CardFactoryUtil.handlePaid(saMe.getSacrificedCost(), calcX[1]);
          }
         
          return 0;
        }
       
        private boolean shouldTgtP(int d) {
            PlayerZone compHand = AllZone.getZone(Constant.Zone.Hand, AllZone.ComputerPlayer);
            CardList hand = new CardList(compHand.getCards());
           
            if(AF.isSpell() && hand.size() > 7) // anti-discard-at-EOT
               return true;
           
            if(AllZone.HumanPlayer.getLife() - d < 10) // if damage from this spell would drop the human to less than 10 life
               return true;
           
            return false;
        }
       
        private Card chooseTgtC(final int d) {
            // Combo alert!!
            PlayerZone compy = AllZone.getZone(Constant.Zone.Play, AllZone.ComputerPlayer);
            CardList cPlay = new CardList(compy.getCards());
            if(cPlay.size() > 0) for(int i = 0; i < cPlay.size(); i++)
                if(cPlay.get(i).getName().equals("Stuffy Doll")) return cPlay.get(i);
           
            PlayerZone human = AllZone.getZone(Constant.Zone.Play, AllZone.HumanPlayer);
            CardList hPlay = new CardList(human.getCards());
            hPlay = hPlay.filter(new CardListFilter() {
                public boolean addCard(Card c) {
                    // will include creatures already dealt damage
                    return c.isCreature() && ((c.getNetDefense() + c.getDamage()) <= d)
                            && CardFactoryUtil.canTarget(AF.getHostCard(), c);
                }
            });
           
            if(hPlay.size() > 0) {
                Card best = hPlay.get(0);
               
                if(hPlay.size() > 1) {
                    for(int i = 1; i < hPlay.size(); i++) {
                        Card b = hPlay.get(i);
                        // choose best overall creature?
                        if(b.getSpellAbility().length > best.getSpellAbility().length
                                || b.getKeyword().size() > best.getKeyword().size()
                                || b.getNetAttack() > best.getNetAttack()) best = b;
                    }
                }
                return best;
            }
            return null;
        }

        private boolean doCanPlayAI(SpellAbility saMe)
        {
           // temporarily disabled until better AI
           if (AF.getAbCost().getSacCost())    return false;
           if (AF.getAbCost().getSubCounter())  return false;
           if (AF.getAbCost().getLifeCost())    return false;
           
           if (!ComputerUtil.canPayCost(saMe))
              return false;

          // TODO handle proper calculation of X values based on Cost
            int damage = getNumDamage(saMe);
           
            boolean rr = AF.isSpell();
           
            if (AF.isAbility())
            {
               Random r = new Random(); // prevent run-away activations
               if(r.nextFloat() <= Math.pow(.6667, AF.getHostCard().getAbilityUsed()))
                  rr = true;
            }
           
            Target tgt = AF.getAbTgt();
            // AI handle multi-targeting?
            tgt.resetTargets();
           // target loop
          while(tgt.getNumTargeted() < tgt.getMaxTargets()){
               // TODO: Consider targeting the planeswalker
               if(tgt.canTgtCreatureAndPlayer()) {

                   if(shouldTgtP(damage)) {
                      tgt.addTarget(AllZone.HumanPlayer);
                       continue;
                   }
                 
                   Card c = chooseTgtC(damage);
                   if(c != null) {
                      tgt.addTarget(c);
                       continue;
                   }
               }
             
               if(tgt.canTgtPlayer() || TgtOpp) {
                  tgt.addTarget(AllZone.HumanPlayer);
                   continue;
               }
             
               if(tgt.canTgtCreature()) {
                   Card c = chooseTgtC(damage);
                   if(c != null) {
                      tgt.addTarget(c);
                       continue;
                   }
               }
               // fell through all the choices, no targets left?
             if (tgt.getNumTargeted() < tgt.getMinTargets() || tgt.getNumTargeted() == 0){
                tgt.resetTargets();
                return false;
             }
             else{
                // todo is this good enough? for up to amounts?
                break;
             }
          }
           
            return rr;
           
        }
       
        private String damageStackDescription(AbilityFactory af, SpellAbility sa){
          // when damageStackDescription is called, just build exactly what is happening
           StringBuilder sb = new StringBuilder();
           String name = af.getHostCard().getName();
           int damage = getNumDamage(sa);

           ArrayList<Object> tgts = findTargets(sa);
           
           if (!(sa instanceof Ability_Sub))
              sb.append(name).append(" - ");
           
           sb.append("Deals ").append(damage).append(" damage to ");
           for(int i = 0; i < tgts.size(); i++){
              if (i != 0)
                 sb.append(" ");
              
              Object o = tgts.get(0);
              if (o instanceof Player){
                 sb.append(((Player)o).getName());
              }
              else{
                 sb.append(((Card)o).getName());
              }
             
           }
           sb.append(". ");
           
           if (hasSubAbAF){
              subAbAF.setParent(sa);
              sb.append(subAbAF.getStackDescription());
           }

           return sb.toString();
        }
       
        private  ArrayList<Object> findTargets(SpellAbility saMe){
            Target tgt = AF.getAbTgt();
            ArrayList<Object> tgts;
            if (tgt != null)
               tgts = tgt.getTargets();
            else{
               tgts = new ArrayList<Object>();
               if (TgtOpp){
                     tgts.add(saMe.getActivatingPlayer().getOpponent());
               }
               else if (AF.getMapParams().containsKey("Effected")){
                  String effected = AF.getMapParams().get("Effected");
                  if (effected.equals("You"))
                     tgts.add(saMe.getActivatingPlayer());
                  else if (effected.equals("Self"))
                     tgts.add(saMe.getSourceCard());
               }
            }
            return tgts;
        }
       
        private void doResolve(SpellAbility saMe)
        {
            int damage = getNumDamage(saMe);

            ArrayList<Object> tgts = findTargets(saMe);
            boolean targeted = (AF.getAbTgt() != null) || TgtOpp;

            if (tgts == null || tgts.size() == 0){
               System.out.println("No targets?");
               return;
            }
           
            for(Object o : tgts){
               if (o instanceof Card){
                   Card c = (Card)o;
                   if(AllZone.GameAction.isCardInPlay(c) && (!targeted || CardFactoryUtil.canTarget(AF.getHostCard(), c)))
                         c.addDamage(damage, AF.getHostCard());
               }
               else if (o instanceof Player){
                  Player p = (Player) o;
                  if (!targeted || p.canTarget(AF.getHostCard()))
                     p.addDamage(damage, AF.getHostCard());
               }
            }
   
           if (hasSubAbAF) {
              if (subAbAF.getParent() == null)
                 subAbAF.setParent(saMe);
            subAbAF.resolve();
           }
           
           else if (hasSubAbStr){
               Object obj = tgts.get(0);
               
               Player pl = null;
               Card c = null;
             
               if (obj instanceof Card){
                  c = (Card)obj;
                  pl = c.getController();
               }
               else{
                  pl = (Player)obj;
               }
              CardFactoryUtil.doDrawBack(subAbStr, damage, AF.getHostCard().getController(),
                 AF.getHostCard().getController().getOpponent(),   pl, AF.getHostCard(), c, saMe);
             
           }

        }
    }

Significant changes:
Code: Select all
if(AF.hasSubAbility())
{
   String sSub = AF.getMapParams().get("SubAbility");
   // Look for SVar= instead of X to allow for multiple Drawbacks
   if (sSub.startsWith("SVar="))
     sSub = AF.getHostCard().getSVar(sSub.split("=")[1]);
   
   if (sSub.startsWith("DB$"))
   {
     AbilityFactory afDB = new AbilityFactory();
     subAbAF = (Ability_Sub)afDB.getAbility(sSub, AF.getHostCard());
     hasSubAbAF = true;
   }
   else
   {
     subAbStr = sSub;
     hasSubAbStr = true;
   }
}

...

final SpellAbility dbDealDamage = new Ability_Sub(AF.getHostCard(), AF.getAbTgt()) {
StackDescription, FindTargets (a full on addition) and Resolve all got some upgrades as well.

AbilityFactory only had one significant change, abCost is just ignored for Drawbacks.
Code: Select all
if (!isDb){
   if (!mapParams.containsKey("Cost"))
      throw new RuntimeException("AbilityFactory : getAbility -- no Cost in " + hostCard.getName());
   abCost = new Ability_Cost(mapParams.get("Cost"), hostCard.getName(), isAb);
}
Lemme know if this is confusing at all.

Re: AbilityFactory-based SubAbilities

PostPosted: 18 Nov 2010, 19:16
by slapshot5
friarsol wrote:
Code: Select all
SVar:DB1:DB$DealDamage | NumDmg$ 3 | Effected$ You
I think the correct word here is "Affected", not "Effected".

-slapshot5

Re: AbilityFactory-based SubAbilities

PostPosted: 18 Nov 2010, 19:37
by friarsol
It's being influenced by an Effect, hence "Effected" proper English grammar be damned.

Re: AbilityFactory-based SubAbilities

PostPosted: 18 Nov 2010, 19:44
by Rob Cashwalker
This is the same sort of argument I had with myself when first doing the AbilityFactory - Tgt, ValidTgt and just Valid. Tgt for the compatability to TgtP & TgtCP. ValidTgt for "TgtV". And Valid is for non-targeted effects that need to be restricted in what they AFFECT. But I didn't want to make the parameter "Affects$" because it would ultimately be linked to isValid.

BTW, good work up there. I had a feeling a subclass of SpellAbility would be needed, but I didn't know how to actually accomplish that.

Re: AbilityFactory-based SubAbilities

PostPosted: 18 Nov 2010, 20:04
by friarsol
Ahhh... I had wondered what Valid was doing, I had removed it because there was a scenario where it conflicted with Shorthand Targeting, and nothing was using it yet, so I figured we could re-add it.

Maybe we should just go with NonTgt$ instead of Affected/Effected. Actually, I think I like that better anyway. Plus it'll be less letters for slap to type ;)

Yea coming from a C++ background it makes handling some of these cases easier.

Re: AbilityFactory-based SubAbilities

PostPosted: 18 Nov 2010, 20:44
by Sloth
friarsol wrote:Ahhh... I had wondered what Valid was doing, I had removed it because there was a scenario where it conflicted with Shorthand Targeting, and nothing was using it yet, so I figured we could re-add it.

Maybe we should just go with NonTgt$ instead of Affected/Effected. Actually, I think I like that better anyway. Plus it'll be less letters for slap to type ;)
I was searching for something like this when adding DestroyAll but couldn't find anything, so I made my own parameter "ValidCards". Obviously just "Valid" would also be ok.

"NonTgt" doesn't sound right to me though. It sounds like adding exceptions to targets or a boolean.

Re: AbilityFactory-based SubAbilities

PostPosted: 18 Nov 2010, 21:13
by friarsol
So I was thinking about this a bit more and right now the SubAbility lives in the specific AbilityFactory right? I think it might be better if we assigned it to the parent SpellAbility as soon as we can. This way in Target_Selection, we have access to the SubAbilities as well and we can do extra targeting as necessary since each SA can chain to the next Child.

I'm testing out some additional changes. I can post them here when I'm done.

Edit: Here are the additional changes, we might have broken this wide open.

Target_Selection
Code: Select all
public boolean chooseTargets(){
   // if not enough targets chosen, reset and cancel Ability
   if (bCancel || (bDoneTarget && !target.isMinTargetsChosen())){
      bCancel = true;
      req.finishedTargeting();
      return false;
   }
   else if (!doesTarget() || bDoneTarget && target.isMinTargetsChosen() || target.isMaxTargetsChosen()){
      Ability_Sub abSub = ability.getSubAbility();
      
      if (abSub == null){
         // if no more SubAbilities finish targeting
         req.finishedTargeting();
         return true;
      }
      else{
         // Has Sub Ability
         Target_Selection ts = new Target_Selection(abSub.getTarget(), abSub);
         ts.setRequirements(req);
         return = ts.chooseTargets();
      }
   }
   
   //targets still needed
   changeInput.stopSetNext(input_targetValid(ability, target.getValidTgts(), target.getVTSelection(), this, req));
   return false;
}
AbilityFactory
Code: Select all
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();
SA.setSubAbility(dd.getSubAbility());
}
Ability_Sub
Code: Select all
public Ability_Sub(Card sourceCard, Target tgt) {
   super(SpellAbility.Ability, sourceCard);
   setTarget(tgt);
}
SpellAbility (wherever makes sense)
Code: Select all
   private Ability_Sub    subAbility          = null;
    public void setSubAbility(Ability_Sub subAbility) {
      this.subAbility = subAbility;      
   }
   
   public Ability_Sub getSubAbility() {
      return this.subAbility;      
   }
And I was able to get Lunge working now too.
Code: Select all
Name:Lunge
ManaCost:2 R
Types:Instant
Text:no text
A:SP$DealDamage | Cost$ 2 R | Tgt$ C | NumDmg$2 | SubAbility$SVar=DB1 | SpellDescription$ Lunge deals 2 damage to target creature and 2 damage to target player.
SVar:DB1:DB$DealDamage | NumDmg$ 2 | Tgt$ P
SVar:Rarity:Common
SVar:Picture:http://www.wizards.com/global/images/magic/general/lunge.jpg
End

Re: AbilityFactory-based SubAbilities

PostPosted: 18 Nov 2010, 22:27
by friarsol
Rob, did you want me to submit this work, since I have it all setup and tested over here? I didn't want to step on your toes again.

Re: AbilityFactory-based SubAbilities

PostPosted: 19 Nov 2010, 04:11
by Rob Cashwalker
hehe...

I'm washing my hands of your genius... I'm going back to sets.

I dunno, lately, I can't keep up with doing abilities and mechanics. I'm almost happier with doing these upgrades to the auxiliary functions.

Re: AbilityFactory-based SubAbilities

PostPosted: 19 Nov 2010, 04:22
by friarsol
I feel bamboozled :-d

Don't worry, there's been a huge onslaught of submissions lately. I'm thinking after Thanksgiving that will all slow down again and things won't be on such a frenetic pace.

Re: AbilityFactory-based SubAbilities

PostPosted: 19 Nov 2010, 05:30
by Rob Cashwalker
BTW, as this gets further refined, don't forget that the Drawback AI may not always be the same AI as if you were to play the same ability as a spell or activated ability.

Take Lunge, for example. There's no reason why the AI would not want to play it, however the normal Damage AI saves target player damage as a last resort. The primary Damage AI would likely play it.

More often than not, the default answer for chkAI_Drawback should be "Yes".

Winston Zeddmore wrote:Ray, when someone asks if you're a god, you say YES!

Re: AbilityFactory-based SubAbilities

PostPosted: 04 Dec 2010, 12:52
by Sloth
I wanted to implement Rite of Consumption via subability GainLife but the cost is not inherited to the subability, so using SVar X depending on the sacCost does not work. Is it possible to also inherit the abcost to the subability without problems?

Her would be the txt of Rite of Consumption:
Code: Select all
Name:Rite of Consumption
ManaCost:1 B
Types:Sorcery
Text:no text
A:SP$DealDamage | Cost$ 1 B Sac<1/Creature> | Tgt$ TgtP | NumDmg$ X | SubAbility$ SVar=DB | SpellDescription$ CARDNAME deals damage equal to the sacrificed creature's power to target player. You gain life equal to the damage dealt this way.
SVar:DB:DB$GainLife | LifeAmount$ X
SVar:X:Sacrificed$CardPower
SVar:RemAIDeck:True
SVar:Rarity:Common
SVar:Picture:http://www.wizards.com/global/images/magic/general/rite_of_consumption.jpg
End

Re: AbilityFactory-based SubAbilities

PostPosted: 04 Dec 2010, 15:04
by friarsol
Sloth wrote:I wanted to implement Rite of Consumption via subability GainLife but the cost is not inherited to the subability, so using SVar X depending on the sacCost does not work. Is it possible to also inherit the abcost to the subability without problems?
I can do the same trick I'm doing with Targeted. If the SubAbility doesn't have a different target, it should look at parents until it finds one. I'm just worried that copying costs down might cause problems. And most spells (with SubAbilities) don't have more than one SubAbility anyway.

Actually at some point next week I'll move those into a Utility section for AbilityFactory so everything can use them.